Skip to content

Commit deaee80

Browse files
authored
Merge pull request #11921 from quarto-dev/lua/latex-table-pattern
2 parents 38cc537 + b5ad58e commit deaee80

File tree

11 files changed

+276
-141
lines changed

11 files changed

+276
-141
lines changed

news/changelog-1.7.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ All changes included in 1.7:
3737

3838
- ([#11835](https://github.com/quarto-dev/quarto-cli/issues/11835)): Take markdown structure into account when detecting minimum heading level.
3939
- ([#11903](https://github.com/quarto-dev/quarto-cli/issues/11903)): `crossref` configuration like `fig-title` or `tbl-title` now correctly supports multi word values, e.g. `fig-title: 'Supplementary Figure'`.
40+
- ([#11878](https://github.com/quarto-dev/quarto-cli/issues/11878)): Correctly fixup raw LaTeX table having an unexpected table env with options (e.g `\begin{table}[c]`) to be handled as crossref table.
4041

4142
## `typst` format
4243

src/resources/filters/common/tables.lua

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,12 @@ end
130130

131131
function hasRawLatexTable(raw)
132132
if _quarto.format.isRawLatex(raw) and _quarto.format.isLatexOutput() then
133-
for i,pattern in ipairs(_quarto.patterns.latexTablePatterns) do
134-
if _quarto.modules.patterns.match_all_in_table(pattern)(raw.text) then
135-
return true
136-
end
133+
local matched, _ = _quarto.modules.patterns.match_in_list_of_patterns(raw.text, _quarto.patterns.latexAllTableEnvPatterns)
134+
if matched then
135+
return true
137136
end
138-
return false
139-
else
140-
return false
141137
end
138+
return false
142139
end
143140

144141
local tableCheckers = {

src/resources/filters/customnodes/floatreftarget.lua

Lines changed: 74 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -448,81 +448,84 @@ end, function(float)
448448
local made_fix = false
449449
local function fix_raw(is_star_env)
450450
local function set_raw(el)
451-
if _quarto.format.isRawLatex(el) and _quarto.modules.patterns.match_all_in_table(_quarto.patterns.latexLongtablePattern)(el.text) then
452-
made_fix = true
453-
local raw = el
454-
-- special case for longtable floats in LaTeX
455-
local extended_pattern = {".-"}
456-
for _, pattern in ipairs(_quarto.patterns.latexLongtablePattern) do
457-
table.insert(extended_pattern, pattern)
458-
end
459-
table.insert(extended_pattern, ".*")
460-
local longtable_preamble, longtable_begin, longtable_content, longtable_end, longtable_postamble = _quarto.modules.patterns.match_all_in_table(extended_pattern)(raw.text)
461-
if longtable_preamble == nil or longtable_begin == nil or longtable_content == nil or longtable_end == nil or longtable_postamble == nil then
462-
warn("Could not parse longtable parameters. This could happen because the longtable parameters\n" ..
463-
"are not well-formed or because of a bug in quarto. Please consider filing a bug report at\n" ..
464-
"https://github.com/quarto-dev/quarto-cli/issues/, and make sure to include the document that\n" ..
465-
"triggered this error.")
466-
return {}
467-
end
468-
-- split the content into params and actual content
469-
-- params are everything in the first line of longtable_content
470-
-- actual content is everything else
471-
local start, content = split_longtable_start(longtable_begin .. longtable_content)
472-
if start == nil or content == nil then
473-
warn("Could not parse longtable parameters. This could happen because the longtable parameters\n" ..
474-
"are not well-formed or because of a bug in quarto. Please consider filing a bug report at\n" ..
475-
"https://github.com/quarto-dev/quarto-cli/issues/, and make sure to include the document that\n" ..
476-
"triggered this error.")
477-
return {}
478-
end
479-
local cap_loc = cap_location(float)
480-
if float.parent_id then
481-
-- need to fixup subtables because longtables don't support subcaptions,
482-
-- and longtable captions increment the wrong counter
483-
-- we try our best here
484-
485-
fatal("longtables are not supported in subtables.\n" ..
486-
"This is not a Quarto bug - the LaTeX longtable environment doesn't support subcaptions.\n")
487-
return {}
488-
end
489-
if is_star_env then
490-
-- content: table payload
491-
-- start: \\begin{longtable}... command
492-
-- longtable_preamble: everything that came before the \\begin{longtable} command
493-
-- longtable_postamble: everything that came after the \\end{longtable} command
494-
local result = pandoc.Blocks({
495-
pandoc.RawBlock("latex", longtable_preamble),
496-
pandoc.RawBlock("latex", "\\begin{table*}"),
497-
-- caption here if cap_loc == "top"
498-
pandoc.RawBlock("latex", start .. "\n" .. content .. "\n\\end{longtable}"),
499-
-- caption here if cap_loc ~= "top"
500-
pandoc.RawBlock("latex", "\\end{table*}"),
501-
pandoc.RawBlock("latex", longtable_postamble),
502-
})
503-
if cap_loc == "top" then
504-
result:insert(3, latex_caption)
505-
-- gets around the padding that longtable* adds
506-
result:insert(4, pandoc.RawBlock("latex", "\\vspace{-1em}"))
507-
else
508-
result:insert(4, latex_caption)
451+
if _quarto.format.isRawLatex(el) then
452+
local longtable_match, longtable_pattern = _quarto.modules.patterns.match_in_list_of_patterns(el.text, _quarto.patterns.latexLongtableEnvPatterns)
453+
if longtable_match and longtable_pattern then
454+
made_fix = true
455+
local raw = el
456+
-- special case for longtable floats in LaTeX
457+
local extended_pattern = {".-"}
458+
for _, pattern in ipairs(longtable_pattern) do
459+
table.insert(extended_pattern, pattern)
509460
end
510-
return result
511-
else
512-
local result = pandoc.Blocks({latex_caption, pandoc.RawInline("latex", "\\tabularnewline")})
513-
-- if cap_loc is top, insert content on bottom
514-
if cap_loc == "top" then
515-
result:insert(pandoc.RawBlock("latex", content))
461+
table.insert(extended_pattern, ".*")
462+
local longtable_preamble, longtable_begin, longtable_content, longtable_end, longtable_postamble = _quarto.modules.patterns.match_all_in_table(extended_pattern)(raw.text)
463+
if longtable_preamble == nil or longtable_begin == nil or longtable_content == nil or longtable_end == nil or longtable_postamble == nil then
464+
warn("Could not parse longtable parameters. This could happen because the longtable parameters\n" ..
465+
"are not well-formed or because of a bug in quarto. Please consider filing a bug report at\n" ..
466+
"https://github.com/quarto-dev/quarto-cli/issues/, and make sure to include the document that\n" ..
467+
"triggered this error.")
468+
return {}
469+
end
470+
-- split the content into params and actual content
471+
-- params are everything in the first line of longtable_content
472+
-- actual content is everything else
473+
local start, content = split_longtable_start(longtable_begin .. longtable_content)
474+
if start == nil or content == nil then
475+
warn("Could not parse longtable parameters. This could happen because the longtable parameters\n" ..
476+
"are not well-formed or because of a bug in quarto. Please consider filing a bug report at\n" ..
477+
"https://github.com/quarto-dev/quarto-cli/issues/, and make sure to include the document that\n" ..
478+
"triggered this error.")
479+
return {}
480+
end
481+
local cap_loc = cap_location(float)
482+
if float.parent_id then
483+
-- need to fixup subtables because longtables don't support subcaptions,
484+
-- and longtable captions increment the wrong counter
485+
-- we try our best here
486+
487+
fatal("longtables are not supported in subtables.\n" ..
488+
"This is not a Quarto bug - the LaTeX longtable environment doesn't support subcaptions.\n")
489+
return {}
490+
end
491+
if is_star_env then
492+
-- content: table payload
493+
-- start: \\begin{longtable}... command
494+
-- longtable_preamble: everything that came before the \\begin{longtable} command
495+
-- longtable_postamble: everything that came after the \\end{longtable} command
496+
local result = pandoc.Blocks({
497+
pandoc.RawBlock("latex", longtable_preamble),
498+
pandoc.RawBlock("latex", "\\begin{table*}"),
499+
-- caption here if cap_loc == "top"
500+
pandoc.RawBlock("latex", start .. "\n" .. content .. "\n\\end{longtable}"),
501+
-- caption here if cap_loc ~= "top"
502+
pandoc.RawBlock("latex", "\\end{table*}"),
503+
pandoc.RawBlock("latex", longtable_postamble),
504+
})
505+
if cap_loc == "top" then
506+
result:insert(3, latex_caption)
507+
-- gets around the padding that longtable* adds
508+
result:insert(4, pandoc.RawBlock("latex", "\\vspace{-1em}"))
509+
else
510+
result:insert(4, latex_caption)
511+
end
512+
return result
516513
else
517-
result:insert(1, pandoc.RawBlock("latex", content))
514+
local result = pandoc.Blocks({latex_caption, pandoc.RawInline("latex", "\\tabularnewline")})
515+
-- if cap_loc is top, insert content on bottom
516+
if cap_loc == "top" then
517+
result:insert(pandoc.RawBlock("latex", content))
518+
else
519+
result:insert(1, pandoc.RawBlock("latex", content))
520+
end
521+
result:insert(1, pandoc.RawBlock("latex", start))
522+
result:insert(1, pandoc.RawBlock("latex", longtable_preamble))
523+
result:insert(pandoc.RawBlock("latex", "\\end{longtable}"))
524+
result:insert(pandoc.RawBlock("latex", longtable_postamble))
525+
return result
518526
end
519-
result:insert(1, pandoc.RawBlock("latex", start))
520-
result:insert(1, pandoc.RawBlock("latex", longtable_preamble))
521-
result:insert(pandoc.RawBlock("latex", "\\end{longtable}"))
522-
result:insert(pandoc.RawBlock("latex", longtable_postamble))
523-
return result
524527
end
525-
end
528+
end
526529
end
527530
return set_raw
528531
end

src/resources/filters/modules/patterns.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,17 @@ local function match_all_in_table(pattern_table)
6565
return inner
6666
end
6767

68+
-- return the pattern, and matched content for the first pattern in the list that matches
69+
local function match_in_list_of_patterns(raw_tex, list_of_patterns)
70+
for _, pattern in ipairs(list_of_patterns) do
71+
local matched = { match_all_in_table(pattern)(raw_tex) }
72+
if matched and #matched > 0 then
73+
return matched, pattern
74+
end
75+
end
76+
return nil
77+
end
78+
6879
return {
6980
attr_identifier = attr_identifier,
7081
engine_escape = engine_escape,
@@ -93,5 +104,6 @@ return {
93104
latex_table_star = latex_table_star,
94105

95106
match_all_in_table = match_all_in_table,
107+
match_in_list_of_patterns = match_in_list_of_patterns,
96108
combine_patterns = combine_patterns
97109
}

src/resources/filters/normalize/flags.lua

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,12 @@ function compute_flags()
5757
end
5858

5959
if _quarto.format.isRawLatex(el) then
60-
local long_table_match = _quarto.modules.patterns.match_all_in_table(_quarto.patterns.latexLongtablePattern)
61-
local caption_match = _quarto.modules.patterns.match_all_in_table(_quarto.patterns.latexCaptionPattern)
62-
if (long_table_match(el.text) and
63-
not caption_match(el.text)) then
64-
flags.has_longtable_no_caption_fixup = true
60+
local long_table_match, _ = _quarto.modules.patterns.match_in_list_of_patterns(el.text, _quarto.patterns.latexLongtableEnvPatterns)
61+
if long_table_match then
62+
local caption_match, _= _quarto.modules.patterns.match_in_list_of_patterns(el.text, _quarto.patterns.latexCaptionPatterns)
63+
if not caption_match then
64+
flags.has_longtable_no_caption_fixup = true
65+
end
6566
end
6667
end
6768

src/resources/filters/quarto-post/latex.lua

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -573,12 +573,16 @@ function render_latex_fixups()
573573
return {{
574574
RawBlock = function(raw)
575575
if _quarto.format.isRawLatex(raw) then
576-
local long_table_match = _quarto.modules.patterns.match_all_in_table(_quarto.patterns.latexLongtablePattern)
577-
local caption_match = _quarto.modules.patterns.match_all_in_table(_quarto.patterns.latexCaptionPattern)
578-
if long_table_match(raw.text) and not caption_match(raw.text) then
579-
raw.text = raw.text:gsub(
580-
_quarto.modules.patterns.combine_patterns(_quarto.patterns.latexLongtablePattern), "\\begin{longtable*}%2\\end{longtable*}", 1)
581-
return raw
576+
local longtable_match, _ = _quarto.modules.patterns.match_in_list_of_patterns(raw.text, _quarto.patterns.latexLongtableEnvPatterns)
577+
if longtable_match then
578+
local caption_match = _quarto.modules.patterns.match_in_list_of_patterns(raw.text, _quarto.patterns.latexCaptionPatterns)
579+
if not caption_match then
580+
-- We need to use the most generic pattern (last of the list) as we want to replace the environment and keep any options
581+
-- (e.g. `\begin{longtable}[c]{ll}` -> \begin{longtable*}[c]{ll} in flextable)
582+
local longtable_pattern = _quarto.patterns.latexLongtableEnvPatterns[#_quarto.patterns.latexLongtableEnvPatterns]
583+
raw.text = raw.text:gsub(_quarto.modules.patterns.combine_patterns(longtable_pattern), "\\begin{longtable*}%2\\end{longtable*}", 1)
584+
return raw
585+
end
582586
end
583587
end
584588
end,

src/resources/filters/quarto-pre/parsefiguredivs.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -742,11 +742,11 @@ function parse_floatreftargets()
742742
end
743743

744744
-- finally, if the user passed a \\begin{table} float environment
745-
-- we just remove it because we'll re-emit later ourselves
746-
747-
local b, e, begin_table, table_body, end_table = raw.text:find(patterns.latex_table)
748-
if b ~= nil then
749-
raw.text = table_body
745+
-- we just remove it because we'll re-emit later ourselves
746+
local matched, _ = _quarto.modules.patterns.match_in_list_of_patterns(raw.text, _quarto.patterns.latexTableEnvPatterns)
747+
if matched then
748+
-- table_body is second matched element.
749+
raw.text = matched[2]
750750
end
751751

752752
return quarto.FloatRefTarget({

src/resources/filters/quarto-pre/table-captions.lua

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,16 @@ function table_captions()
2929
el = _quarto.ast.walk(el, {
3030
RawBlock = function(raw)
3131
if _quarto.format.isRawLatex(raw) then
32-
local tabular_match = _quarto.modules.patterns.match_all_in_table(_quarto.patterns.latexTabularPattern)
33-
local table_match = _quarto.modules.patterns.match_all_in_table(_quarto.patterns.latexTablePattern)
34-
if tabular_match(raw.text) and not table_match(raw.text) then
35-
raw.text = raw.text:gsub(
36-
_quarto.modules.patterns.combine_patterns(_quarto.patterns.latexTabularPattern),
37-
"\\begin{table}\n\\centering\n%1%2%3\n\\end{table}\n",
38-
1)
39-
return raw
32+
local tabular_match, tabular_pattern = _quarto.modules.patterns.match_in_list_of_patterns(raw.text, _quarto.patterns.latexTabularEnvPatterns)
33+
if tabular_match then
34+
local table_match, _ = _quarto.modules.patterns.match_in_list_of_patterns(raw.text, _quarto.patterns.latexTableEnvPatterns)
35+
if not table_match then
36+
raw.text = raw.text:gsub(
37+
_quarto.modules.patterns.combine_patterns(tabular_pattern),
38+
"\\begin{table}\n\\centering\n%1%2%3\n\\end{table}\n",
39+
1)
40+
return raw
41+
end
4042
end
4143
end
4244
end
@@ -169,13 +171,10 @@ function applyTableCaptions(el, tblCaptions, tblLabels)
169171
raw.text = raw.text:gsub(captionPattern, "%1" .. captionText:gsub("%%", "%%%%") .. "%3", 1)
170172
idx = idx + 1
171173
elseif hasRawLatexTable(raw) then
172-
for i,pattern in ipairs(_quarto.patterns.latexTablePatterns) do
173-
local match_fun = _quarto.modules.patterns.match_all_in_table(pattern)
174-
if match_fun(raw.text) then
175-
local combined_pattern = _quarto.modules.patterns.combine_patterns(pattern)
174+
local matched_env, pattern_env = _quarto.modules.patterns.match_in_list_of_patterns(raw.text, _quarto.patterns.latexAllTableEnvPatterns)
175+
if matched_env then
176+
local combined_pattern = _quarto.modules.patterns.combine_patterns(pattern_env)
176177
raw.text = applyLatexTableCaption(raw.text, tblCaptions[idx], tblLabels[idx], combined_pattern)
177-
break
178-
end
179178
end
180179
idx = idx + 1
181180
elseif hasPagedHtmlTable(raw) then
@@ -200,24 +199,31 @@ end
200199

201200

202201
function applyLatexTableCaption(latex, tblCaption, tblLabel, tablePattern)
203-
local latexCaptionPattern = _quarto.patterns.latexCaptionPattern
204-
local latex_caption_match = _quarto.modules.patterns.match_all_in_table(latexCaptionPattern)
202+
local latex_caption_match, _ = _quarto.modules.patterns.match_in_list_of_patterns(latex, _quarto.patterns.latexCaptionPatterns)
205203
-- insert caption if there is none
206-
local beginCaption, caption = latex_caption_match(latex)
207-
if not beginCaption then
204+
if not latex_caption_match then
208205
latex = latex:gsub(tablePattern, "%1" .. "\n\\caption{ }\\tabularnewline\n" .. "%2%3", 1)
209206
end
207+
-- caption will be matched
208+
latex_caption_match, latex_caption_pattern = _quarto.modules.patterns.match_in_list_of_patterns(latex, _quarto.patterns.latexCaptionPatterns)
210209
-- apply table caption and label
211-
local beginCaption, captionText, endCaption = latex_caption_match(latex)
212-
if #tblCaption > 0 then
213-
captionText = stringEscape(tblCaption, "latex")
214-
end
215-
if #tblLabel > 0 then
216-
captionText = captionText .. " {#" .. tblLabel .. "}"
210+
if not latex_caption_match then
211+
-- should never happen as we add the caption command to latex string above
212+
-- added to make linter happy too.
213+
fatal("Internal Error: \\caption not correctly added in " .. latex)
214+
else
215+
-- caption text is second element of matched pattern
216+
local captionText = latex_caption_match[2]
217+
if #tblCaption > 0 then
218+
captionText = stringEscape(tblCaption, "latex")
219+
end
220+
if #tblLabel > 0 then
221+
captionText = captionText .. " {#" .. tblLabel .. "}"
222+
end
223+
assert(captionText)
224+
latex = latex:gsub(_quarto.modules.patterns.combine_patterns(latex_caption_pattern), "%1" .. captionText:gsub("%%", "%%%%") .. "%3", 1)
225+
return latex
217226
end
218-
assert(captionText)
219-
latex = latex:gsub(_quarto.modules.patterns.combine_patterns(latexCaptionPattern), "%1" .. captionText:gsub("%%", "%%%%") .. "%3", 1)
220-
return latex
221227
end
222228

223229

0 commit comments

Comments
 (0)