Compare commits

..

7 Commits

6 changed files with 167 additions and 38 deletions

View File

@ -0,0 +1,21 @@
MIT License
Copyright © 2024 Bastien Dumont
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,4 @@
This filter converts nested parentheses to brackets (and conversely).
I use it mostly to post-process citeproc output.

View File

@ -0,0 +1,91 @@
-- inner-parens-to-brackets.lua
-- A Pandoc Lua filter that converts nested parentheses to brackets (and conversely).
-- Copyright 2024 Bastien Dumont (bastien.dumont [at] posteo.net)
-- This file is under the MIT License: see LICENSE for more details.
local previously_signalled = {}
local function signal_if_new(id, msg)
if not previously_signalled[id] then
io.stdout:write('INFO: ' .. msg .. '\n')
previously_signalled[id] = true
end
end
function Para(para)
local whole_para = pandoc.utils.stringify(para)
local parens_trace = {}
local i_str = 0
local isolate_parens = {
-- In case there are several level of parentheses in the same Str object
-- (could theoretically happen due to non-breaking spaces).
-- Also simplifies check_and_replace() below.
Str = function(str)
local text = str.text
if string.match(text, '[][()]') then
local substrings = {}
for a, b, c in string.gmatch(text, '([^][()]*)([][()]?)([^][()]*)') do
for _, sub in ipairs({ a, b, c }) do
if sub ~= '' then table.insert(substrings, pandoc.Str(sub)) end
end
end
return substrings
end
end
}
local check_and_replace = {
traverse = 'topdown',
Str = function(str)
i_str = i_str + 1
local text = str.text
if text == '(' then
if #parens_trace > 0 then
if parens_trace[#parens_trace] == '(' then
str.text = '['
signal_if_new(whole_para .. i_str,
'Replacing a left parenthesis with a bracket ' ..
'in the following item: ' .. whole_para)
end
end
table.insert(parens_trace, str.text)
elseif text == ')' then
if #parens_trace > 0 then
if parens_trace[#parens_trace] == '[' then
str.text = ']'
signal_if_new(whole_para .. i_str,
'Replacing a right parenthesis with a bracket ' ..
'in the following item: ' .. whole_para)
end
end
table.remove(parens_trace)
elseif text == '[' then
if #parens_trace > 0 then
if parens_trace[#parens_trace] == '[' then
str.text = '('
signal_if_new(whole_para .. i_str,
'Replacing a left bracket with a parenthesis ' ..
'in the following item: ' .. whole_para)
end
end
table.insert(parens_trace, str.text)
elseif text == ']' then
if #parens_trace > 0 then
if parens_trace[#parens_trace] == '(' then
str.text = ')'
signal_if_new(whole_para .. i_str,
'Replacing a right bracket with a parenthesis ' ..
'in the following item: ' .. whole_para)
end
end
table.remove(parens_trace)
end
return str
end
}
local processed_para = para:walk(isolate_parens):walk(check_and_replace)
if #parens_trace > 0 then
io.stdout:write('WARNING: Unbalanced parentheses or brackets found in ' .. whole_para .. '\n')
end
return processed_para
end

View File

@ -56,7 +56,7 @@ Here are some valid invocations:
It is up to you to define `\crossrefenum` in your preamble.
If your target format is LaTeX, it should be possible to define it as a wrapper
for the `\zcref` macro provided by [the zref-clever package](https://ctan.org/pkg/zref-clever).
Alternatively, you can use [my implementation](TODO),
Alternatively, you can use [my implementation](https://ctan.org/pkg/crossrefenum),
which currently supports ConTeXt and LaTeX.
Here are some hints about the implementation of the `\crossrefenum` macro:

View File

@ -22,7 +22,7 @@
, Space
, Str "published"
, Space
, RawInline (Format "latex") "\\label{publication}"
, Str ""
, Span
( "publication" , [] , [] )
[ Emph [ Str "L\8217Affaire" , Space , Str "Lerouge" ]
@ -43,7 +43,7 @@
, Space
, Str "very"
, Space
, RawInline (Format "latex") "\\label{my-evaluation}"
, Str ""
, Span
( "my-evaluation" , [] , [] )
[ Str "fine"
@ -60,7 +60,7 @@
, RawInline (Format "latex") ""
]
, Para
[ RawInline (Format "latex") "\\label{reception}"
[ Str ""
, Span
( "reception" , [] , [] )
[ Str "It"
@ -148,7 +148,7 @@
, RawInline (Format "latex") ""
, Note
[ Para
[ RawInline (Format "latex") "\\label{format}"
[ Str ""
, Span
( "format" , [] , [] )
[ Str "Whatever" , Space , Str "format" ]
@ -161,7 +161,7 @@
, Space
, Str "can"
, Space
, RawInline (Format "latex") "\\label{refer-to-note}"
, Str ""
, Span
( "refer-to-note" , [] , [] )
[ Str "refer"
@ -181,7 +181,7 @@
, Space
, Str "of"
, Space
, RawInline (Format "latex") "\\label{which-identifier}"
, Str ""
, Span
( "which-identifier" , [] , [] )
[ Str "any"
@ -198,7 +198,7 @@
, Space
, Str "even"
, Space
, RawInline (Format "latex") "\\label{nested-spans}"
, Str ""
, Span
( "nested-spans" , [] , [] )
[ Str "nest" , Space , Str "spans" ]
@ -209,7 +209,7 @@
, RawInline (Format "latex") ""
]
, Para
[ RawInline (Format "latex") "\\label{toc-notes-begin}"
[ Str ""
, Span
( "toc-notes-begin" , [] , [] )
[ Str "I"
@ -384,15 +384,12 @@
]
]
]
, Para [ Str "" , Span ( "toc-notes-end" , [] , [] ) [] ]
, Para
[ RawInline (Format "latex") "\\label{toc-notes-end}"
, Span ( "toc-notes-end" , [] , [] ) []
]
, Para
[ RawInline (Format "latex") "\\label{doubledlbl}"
[ Str ""
, Span
( "doubledlbl" , [] , [ ( "refanchor" , "both" ) ] )
[ RawInline (Format "latex") "\\label{doubledlbl-beg}"
[ Str ""
, Span ( "doubledlbl-beg" , [] , [] ) []
, Str "A"
, Space
@ -413,12 +410,12 @@
, Str "page"
, Space
, Str "break."
, RawInline (Format "latex") "\\label{doubledlbl-end}"
, Str ""
, Span ( "doubledlbl-end" , [] , [] ) []
]
]
, Para
[ RawInline (Format "latex") "\\label{lblatend}"
[ Str ""
, Span
( "lblatend" , [] , [ ( "refanchor" , "end" ) ] )
[ Str "And"
@ -436,7 +433,7 @@
, Str "the"
, Space
, Str "end."
, RawInline (Format "latex") "\\label{lblatend}"
, Str ""
, Span ( "lblatend" , [] , [] ) []
]
]

View File

@ -15,6 +15,10 @@ local PLACE_LABEL_ATTR = 'refanchor'
local IS_CONFIG_ARRAY = { ['additional_types'] = true }
local RAW_ATTRIBUTE
local function warning(message)
io.stderr:write('WARNING [text-crossrefs]: ' .. message .. '\n')
end
-- ConTeXt-specific tweak in order to add the label to the footnote
--[[
Placing the label in square brackets immediatly after \footnote
@ -67,7 +71,11 @@ local function define_label_template()
IS_LABEL_SET_BY_PANDOC = true
end
elseif RAW_ATTRIBUTE == 'latex' then
LABEL_TEMPLATE = '\\label{{{label}}}'
if PANDOC_VERSION < pandoc.types.Version('3.1.7') then
LABEL_TEMPLATE = '\\label{{{label}}}'
else
IS_LABEL_SET_BY_PANDOC = true
end
end
end
@ -208,8 +216,8 @@ local function control_label_placement(span)
span.content:insert(pandoc.Span({}, { id = id .. '-end' }))
span.identifier = nil
elseif label_placement ~= 'beg' then
error('Invalid value ' .. label_placement .. ' on attribute ' .. PLACE_LABEL_ATTR .. ': ' ..
'shoud be “beg” (default), “end” of “both”.')
warning('Invalid value ' .. label_placement .. ' on attribute ' .. PLACE_LABEL_ATTR .. ': ' ..
'shoud be “beg” (default), “end” of “both”. Using the defaults.')
end
end
return span
@ -233,14 +241,14 @@ local function labelize_span(span)
end
end
local current_note_labels = {}
local labels_in_current_note = {}
local collect_note_labels = {
Span = function(span)
if span.identifier ~= ''
and (config.only_explicit_labels == 'false' or span.classes:includes('label'))
then
table.insert(current_note_labels, span.identifier)
table.insert(labels_in_current_note, span.identifier)
end
end
}
@ -253,14 +261,14 @@ local function make_notelabel(pos)
if RAW_ATTRIBUTE == 'openxml' then
raw_code = string.gsub(
'<w:bookmarkStart w:id="{{label}}_Note" w:name="{{label}}_Note"/>',
'{{label}}', current_note_labels[1])
'{{label}}', labels_in_current_note[1])
elseif RAW_ATTRIBUTE == 'context' then
raw_code = '\\withfirstopt[note:' .. current_note_labels[1] .. ']'
raw_code = '\\withfirstopt[note:' .. labels_in_current_note[1] .. ']'
end
elseif pos == 'end' then
if RAW_ATTRIBUTE == 'openxml' then
raw_code = string.gsub('<w:bookmarkEnd w:id="{{label}}_Note"/>',
'{{label}}', current_note_labels[1])
'{{label}}', labels_in_current_note[1])
end
end
return pandoc.RawInline(RAW_ATTRIBUTE, raw_code)
@ -273,18 +281,18 @@ local function labelize_note(note)
return { label_begin, note, label_end }
end
local function map_text_to_note_labels(current_note_labels)
local note_label = 'note:' .. current_note_labels[1]
for _, text_label in ipairs(current_note_labels) do
text_to_note_labels[text_label] = note_label
local function map_text_to_note_labels(labels_in_current_note)
local note_label = 'note:' .. labels_in_current_note[1]
for _, label in ipairs(labels_in_current_note) do
text_to_note_labels[label] = note_label
end
end
function set_notelabels(note)
current_note_labels = {}
labels_in_current_note = {}
pandoc.walk_inline(note, collect_note_labels)
if #current_note_labels > 0 then
map_text_to_note_labels(current_note_labels)
if #labels_in_current_note > 0 then
map_text_to_note_labels(labels_in_current_note)
return labelize_note(note)
end
end
@ -334,8 +342,8 @@ local function parse_next_reference(raw_references, beg_of_search)
if beg_of_search < #raw_references then
-- The delimiter can be composed of more than one character.
local delim_beg, delim_end = string.find(raw_references,
config.references_enum_separator,
beg_of_search, true)
config.references_enum_separator,
beg_of_search, true)
if delim_beg then
reference = string.sub(raw_references, beg_of_search, delim_beg - 1)
next_ref_beg = delim_end + 1
@ -362,15 +370,17 @@ local function parse_references_enum(raw_references)
end
local function error_on_attr(attr_key, attr_value, span_content)
error('Invalid value "' .. attr_value .. '" for attribute "' .. attr_key ..
warning('Invalid value "' .. attr_value .. '" for attribute "' .. attr_key ..
'" in the span with class "' .. TEXT_CROSSREF_CLASS ..
'" whose content is "' .. stringify(span_content) .. '".')
'" whose content is "' .. stringify(span_content) .. '". ' ..
'Using the defaults.')
end
local function get_ref_type(span)
local ref_type = span.attributes[REF_TYPE_ATTR] or config.default_reftype
if not accepted_types[ref_type] then
error_on_attr(REF_TYPE_ATTR, ref_type, span.content)
ref_type = config.default_reftype
end
return ref_type
end
@ -379,6 +389,7 @@ local function if_prefixed(span)
local prefixed_attr_value = span.attributes[PREFIXED_ATTR] or config.default_prefixref
if prefixed_attr_value ~= 'yes' and prefixed_attr_value ~= 'no' then
error_on_attr(PREFIXED_ATTR, prefixed_attr_value, span.content)
prefixed_attr_value = config.default_prefixref
end
local is_prefixed = true
if prefixed_attr_value == 'no' then is_prefixed = false end
@ -421,7 +432,12 @@ local function make_crossrefenum_references_list(refs, ref_type)
if FORMAT == 'context'
and (ref_type == 'note' or ref_type == 'pagenote')
then
anchor = text_to_note_labels[anchor]
local note_label = text_to_note_labels[anchor]
if note_label then
anchor = note_label
else
warning('Wrong reference to non-existent label "' .. anchor .. '".')
end
end
local texified_ref = '{' .. anchor
if ref.end_of_range then