Skip to content

Commit 53cb757

Browse files
cdervclaude
andauthored
Fix crash with crossref div and conditional visibility (#13995)
* Fix crash with crossref table div and conditional visibility When a div has both a cross-reference ID (e.g., #tbl-) and conditional visibility class (.content-visible), the slot content gets transformed by parsefiguredivs from Div to FloatRefTarget to Table. The render function in content-hidden.lua assumed the slot was always a Div and crashed when calling el.content on a Table. The fix checks if the rendered slot is still a Div before accessing .content. For transformed elements (Table, etc.), return the element wrapped in pandoc.Blocks. Fixes #13992 * Add test coverage for content-hidden custom node scenarios (#13992) Tests for conditional visibility with various custom nodes that were affected by the fix in content-hidden.lua: - Figure (FloatRefTarget) - Listing (FloatRefTarget) - Theorem (Theorem custom node) - Proof (Proof custom node) - Plain div (baseline) - Nested callout (Callout custom node) - Nested tabset (HTML-specific) - Rename table test to reflect its focus on Table (Table custom node) Each test verifies PDF, HTML, and Typst output with structure checks. * Add explanatory comment for defensive clearHiddenVisibleAttributes call Document that the clearHiddenVisibleAttributes() call in ConditionalBlock render is defensive (typically a no-op) since parse() already strips visibility attrs. Kept as safety net for potential future code changes. --------- Co-authored-by: Claude Opus 4.5 <[email protected]>
1 parent 762a4c3 commit 53cb757

File tree

10 files changed

+256
-2
lines changed

10 files changed

+256
-2
lines changed

news/changelog-1.9.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,5 @@ All changes included in 1.9:
163163
- ([#13890](https://github.com/quarto-dev/quarto-cli/issues/13890)): Fix render failure when using `embed-resources: true` with input path through a symlinked directory. The cleanup now resolves symlinks before comparing paths.
164164
- ([#13907](https://github.com/quarto-dev/quarto-cli/issues/13907)): Ignore AI assistant configuration files (`CLAUDE.md`, `AGENTS.md`) when scanning for project input files and in extension templates, similar to how `README.md` is handled.
165165
- ([#13935](https://github.com/quarto-dev/quarto-cli/issues/13935)): Fix `quarto install`, `quarto update`, and `quarto uninstall` interactive tool selection.
166+
- ([#13992](https://github.com/quarto-dev/quarto-cli/issues/13992)): Fix crash when rendering div with both cross-reference ID and conditional visibility to PDF.
166167
- ([#13998](https://github.com/quarto-dev/quarto-cli/issues/13998)): Fix YAML validation error with CR-only line terminators (old Mac format). Documents using `\r` line endings no longer fail with "Expected YAML front matter to contain at least 2 lines".

src/resources/filters/customnodes/content-hidden.lua

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,18 @@ _quarto.ast.add_handler({
5959
local visible = is_visible(node)
6060
if visible then
6161
local el = node.node
62-
clearHiddenVisibleAttributes(el)
63-
return el.content
62+
-- Handle case where slot content was transformed (e.g., Div → FloatRefTarget → Table)
63+
if is_regular_node(el, "Div") then
64+
-- Defensive: parse() already stripped visibility attrs (lines 46-47), so this is
65+
-- typically a no-op. Kept as safety net in case future code adds attrs between
66+
-- parse and render. See issue #13992 investigation for AST trace evidence.
67+
clearHiddenVisibleAttributes(el)
68+
return el.content
69+
else
70+
-- Slot was transformed to another type (Table, etc.)
71+
-- Return the rendered element wrapped in Blocks
72+
return pandoc.Blocks({el})
73+
end
6474
else
6575
return {}
6676
end
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
title: "Conditional visibility with figure (#13992)"
3+
keep-tex: true
4+
keep-typ: true
5+
_quarto:
6+
tests:
7+
pdf:
8+
ensureLatexFileRegexMatches:
9+
- ['Figure content visible', 'fig-cond', '\\begin{figure}', 'Figure~\\ref\{fig-cond\}']
10+
- []
11+
html:
12+
ensureHtmlElements:
13+
- []
14+
- ['#fig-cond']
15+
ensureFileRegexMatches:
16+
- []
17+
- ['Figure content visible']
18+
typst:
19+
ensureTypstFileRegexMatches:
20+
- ['#figure', 'Figure content visible', '#ref\(<fig-cond>']
21+
- []
22+
native: default
23+
---
24+
25+
See @fig-cond for conditional figure.
26+
27+
::: {#fig-cond .content-visible unless-format="html"}
28+
![]({{< placeholder 300 >}})
29+
30+
Figure content visible
31+
:::
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
title: "Conditional visibility with listing (#13992)"
3+
keep-tex: true
4+
keep-typ: true
5+
_quarto:
6+
tests:
7+
pdf:
8+
ensureLatexFileRegexMatches:
9+
- ['Listing content visible', 'lst-cond', '\\begin{codelisting}', 'Listing~\\ref\{lst-cond\}']
10+
- []
11+
html:
12+
ensureHtmlElements:
13+
- []
14+
- ['#lst-cond']
15+
ensureFileRegexMatches:
16+
- []
17+
- ['Listing content visible']
18+
typst:
19+
ensureTypstFileRegexMatches:
20+
- ['Listing content visible', '#ref\(<lst-cond>']
21+
- []
22+
native: default
23+
---
24+
25+
See @lst-cond for conditional listing.
26+
27+
::: {#lst-cond .content-visible unless-format="html"}
28+
```python
29+
# Listing content visible
30+
print("hello")
31+
```
32+
:::
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: "Conditional visibility with nested callout (#13992)"
3+
keep-tex: true
4+
keep-typ: true
5+
_quarto:
6+
tests:
7+
pdf:
8+
ensureLatexFileRegexMatches:
9+
- ['Nested callout visible', 'tcolorbox', 'quarto-callout-note']
10+
- []
11+
html:
12+
ensureHtmlElements:
13+
- []
14+
- ['div.callout']
15+
ensureFileRegexMatches:
16+
- []
17+
- ['Nested callout visible']
18+
typst:
19+
ensureTypstFileRegexMatches:
20+
- ['Nested callout visible', '#callout']
21+
- []
22+
---
23+
24+
This tests a callout nested inside a conditional visibility div.
25+
26+
::: {.content-visible unless-format="html"}
27+
::: {.callout-note}
28+
Nested callout visible.
29+
:::
30+
:::
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
title: "Conditional visibility with nested tabset (#13992)"
3+
keep-tex: true
4+
keep-typ: true
5+
_quarto:
6+
tests:
7+
pdf:
8+
ensureLatexFileRegexMatches:
9+
- []
10+
- ['Nested tabset visible', 'Tab A']
11+
html:
12+
ensureHtmlElements:
13+
- ['div.panel-tabset']
14+
- []
15+
ensureFileRegexMatches:
16+
- ['Nested tabset visible', 'Tab A']
17+
- []
18+
typst:
19+
ensureTypstFileRegexMatches:
20+
- []
21+
- ['Nested tabset visible', 'Tab A']
22+
---
23+
24+
This tests a tabset nested inside a conditional visibility div.
25+
26+
::: {.content-visible when-format="html"}
27+
::: {.panel-tabset}
28+
### Tab A
29+
Nested tabset visible.
30+
:::
31+
:::
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
title: "Conditional visibility with plain div (#13992)"
3+
keep-tex: true
4+
keep-typ: true
5+
_quarto:
6+
tests:
7+
pdf:
8+
ensureLatexFileRegexMatches:
9+
- ['Plain div content visible']
10+
- []
11+
html:
12+
ensureFileRegexMatches:
13+
- []
14+
- ['Plain div content visible']
15+
typst:
16+
ensureTypstFileRegexMatches:
17+
- ['Plain div content visible']
18+
- []
19+
---
20+
21+
This tests the baseline case of a plain div with `.content-visible`.
22+
23+
::: {.content-visible unless-format="html"}
24+
Plain div content visible.
25+
:::
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
title: "Conditional visibility with proof (#13992)"
3+
keep-tex: true
4+
keep-typ: true
5+
_quarto:
6+
tests:
7+
pdf:
8+
ensureLatexFileRegexMatches:
9+
- ['Proof content visible', '\\begin{proof}', '\\end{proof}']
10+
- []
11+
html:
12+
ensureFileRegexMatches:
13+
- []
14+
- ['Proof content visible']
15+
typst:
16+
ensureTypstFileRegexMatches:
17+
- ['#emph\[Proof\]\. Proof content visible']
18+
- []
19+
---
20+
21+
This tests a proof div with `.proof` class and `.content-visible`.
22+
23+
::: {.proof .content-visible unless-format="html"}
24+
Proof content visible.
25+
:::
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
title: "Crossref table with conditional visibility (#13992)"
3+
keep-tex: true
4+
keep-typ: true
5+
_quarto:
6+
tests:
7+
pdf:
8+
ensureLatexFileRegexMatches:
9+
- ['Type 1', 'Type 2', 'Item 1', 'Item 2', 'tbl-mytypes']
10+
- []
11+
html:
12+
ensureHtmlElements:
13+
- []
14+
- ['#tbl-mytypes', 'table']
15+
ensureFileRegexMatches:
16+
- []
17+
- ['Type 1', 'Type 2', 'Item 1', 'Item 2']
18+
typst:
19+
ensureTypstFileRegexMatches:
20+
- ['#ref\(<tbl-mytypes>', 'Type 1', 'Type 2', 'Item 1', 'Item 2']
21+
- []
22+
native: default
23+
---
24+
25+
This tests that a table div with both a cross-reference ID and conditional visibility renders correctly.
26+
27+
See @tbl-mytypes for the table.
28+
29+
::: {#tbl-mytypes .content-visible unless-format="html"}
30+
31+
| Type 1 | Type 2 |
32+
| ------ | ------ |
33+
| Item 1 | Item 2 |
34+
35+
: Test table caption
36+
37+
:::
38+
39+
The table above should appear in PDF and Typst but not HTML.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
title: "Conditional visibility with theorem (#13992)"
3+
keep-tex: true
4+
keep-typ: true
5+
_quarto:
6+
tests:
7+
pdf:
8+
ensureLatexFileRegexMatches:
9+
- ['Theorem content visible', 'thm-cond', '\\begin\{theorem\}']
10+
- []
11+
html:
12+
ensureHtmlElements:
13+
- []
14+
- ['#thm-cond']
15+
ensureFileRegexMatches:
16+
- []
17+
- ['Theorem content visible']
18+
typst:
19+
ensureTypstFileRegexMatches:
20+
- ['Theorem content visible', '#ref\(<thm-cond>', '#theorem']
21+
- []
22+
---
23+
24+
See @thm-cond for conditional theorem.
25+
26+
::: {#thm-cond .content-visible unless-format="html"}
27+
## Test Theorem
28+
29+
Theorem content visible.
30+
:::

0 commit comments

Comments
 (0)