diff --git a/text-crossrefs/Makefile b/text-crossrefs/Makefile new file mode 100644 index 0000000..d9353c6 --- /dev/null +++ b/text-crossrefs/Makefile @@ -0,0 +1,23 @@ +.PHONY: test-all test-internal + +SHELL=/bin/bash +return_statement_line_number := $(shell grep -nE '^return' text-crossrefs.lua | cut -d ':' -f 1) +line_before_return := $(shell echo $$(($(return_statement_line_number) - 1))) + +test-all: test-internal test-context test-latex test-opendocument + +test-internal: text-crossrefs.lua + -rm --interactive=never test/tmp.lua + sed -n 1,$(line_before_return)p text-crossrefs.lua > test/tmp.lua + cat test/test-functions.lua >> test/tmp.lua + chmod -w test/tmp.lua + pandoc -L test/tmp.lua <<< '' + @echo -e '==========================\nAll internal tests passed.\n==========================\n' + rm --interactive=never test/tmp.lua + +test-%: text-crossrefs.lua sample.md + TESTED_FORMAT=$* pandoc -t native -L text-crossrefs.lua sample.md > test/tmp-$*.native + diff test/tmp-$*.native test/sample-$*.native + @echo -e '\n===============\ntest passed.\n===============\n' + rm test/tmp-$*.native + diff --git a/text-crossrefs/README.md b/text-crossrefs/README.md new file mode 100644 index 0000000..87c9e2c --- /dev/null +++ b/text-crossrefs/README.md @@ -0,0 +1,323 @@ +# text-crossrefs: cross-references to arbitrary portions of text in Pandoc + +This filter extends Pandoc's cross-referencing abilities +with references to any portion of text +by its page number, its note number (when applicable) +or an arbitrary reference type (with ConTeXt or LaTeX output). +It currently supports the following output formats: + + * context + * docx + * latex + * odt + * opendocument + +## Format-specific preliminary notices + +### DOCX and ODT/Opendocument + +When opening for the first time a file produced by Pandoc with _text-crossrefs_, +you should have to refresh the fields in order to get the correct values. +In LibreOffice, press `F9`; in Word, a dialog box should appear when the file opens. + +### ConTeXt specifically + +Your template should include the following directive before `\starttext` +(as in the default template for ConTeXt): + +``` +$for(header-includes)$ +$header-includes$ +$endfor$ +``` + +### All TeX-based formats + +All references are wrapped in a macro named `\crossrefenum`. +It has two optional arguments: +the first one is the reference type, +the second indicates whether the prefix (e.g. “p. ”) should be printed or not +(can be set to `withprefix`, `noprefix`, `yes` or `no`). +The default values for these arguments should match +those of `tcrf-default-reftype` and `tcrf-default-prefixref` +(resp. `page` and `yes`, i.e. `withprefix`). +The mandatory argument of `\crossrefenum` is a group enclosing one or more groups. +Each of them contain a reference (either a single reference or a range). + +Here are some valid invocations: + + * `\crossrefenum[note][withprefix]{{lblone}{lbltwo}{lblthree}}` + * `\crossrefenum[page][noprefix]{{lblone}{lbltwo}{lblthree}}` + * `\crossrefenum[noprefix]{{lblone}{lbltwo}{lblthree}}` (the first argument defaults to `page`) + * `\crossrefenum{{lblone}{lbltwo}{lblthree}}` (the second argument defaults to `withprefix`) + * `\crossrefenum{{only-one}}` (even if the enumeration is limited to one item, it must be in a group) + * `\crossrefenum{{lblone to lbltwo}{lblthree}}` (the first reference points to a range) + +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), +which currently supports ConTeXt and LaTeX. +Here are some hints about the implementation of the `\crossrefenum` macro: + + * [The `\crossrefenum` macro is supposed to output non only the numbers, + but also the prefixes and delimiters (e.g. “p. ” and “–”)]{#prefixes-tex}; + * In ConTeXt, there is no way to retrieve the note number + from a `\reference` or a `\pagereference` contained in the note + as one would from a `\label` in LaTeX. + To work around this, footnotes are labelled automatically with `note:` + followed by the first identifier attached to a span in the note. + Contrary to the ConTeXt syntax, this label is placed _after_ the footnote content, + so the `\footnote` macro has to be redefined. + If your template includes the `header-includes` metadata variable like the default one does, + this redefinition will happen automatically. + Otherwise, you can copy-paste the following code in your preamble: + + ``` {=tex} + \catcode`\@=11 + \let\origfootnote\footnote + \def\footnote#1#2{ + \def\crfnm@secondArg{#2}% + \ifx\crfnm@secondArg\crfnm@bracket + \def\crfnm@todo{\crfnm@footnote@withlabel{#1}#2} % + \else + \def\crfnm@todo{\origfootnote{#1}#2}% + \fi + \crfnm@todo + } + \def\crfnm@bracket{[} + \def\crfnm@footnote@withlabel#1[#2]{\origfootnote[#2]{#1}} + \catcode`\@=13 + + ``` + +## Usage + +### Basics + +Mark the span of text you want to refer to later +with an identifier composed of alphanumeric characters, periods, colons, underscores or hyphens: + +``` markdown +Émile Gaboriau published [_L'Affaire Lerouge_ in +1866]{#publication}.[^1] + +[^1]: It is a very [fine piece of literature]{#my-evaluation}. + +[It was very popular.]{#reception} +``` + +To refer to it, write another span with class `tcrf` containing the target's identifier. +If the span you are referring to is part of a footnote, +you can refer to it either by page or note number +according to the value of the `reftype` attribute (defaults to `page`). +For instance, this: + +``` markdown +See [publication]{.tcrf} for the publication date. I gave my +opinion in [my-evaluation]{.tcrf reftype=note}, [my-evaluation]{.tcrf}. +``` + +will render in ConTeXt or LaTeX output: + +``` tex +See \crossrefenum{{publication}} for the publication date. I expressed +my thoughts about it in \crossrefenum[note]{{my-evaluation}}, +\crossrefenum{{my-evaluation}}. +``` + +If you want to give a reference by note _and_ page number like in the example above, +you can also use the following shorthand: + +```md +[my-evaluation]{.tcrf reftype=pagenote} +``` + +In fact, you can use any identifier, including those automatically set by Pandoc. + +To suppress the prefixes (e.g. “p. ”), you can set the `prefixref` attribute to `no` (defaults to `yes`). +It can be useful, for instance, for small manually formatted indexes[^comma-syntax]: + +``` markdown +Gaboriau: [publication, my-evaluation, reception]{.tcrf prefixref=no} +``` + +[^comma-syntax]: About the comma-delimited syntax used in this example, see [the section on enumerations below](#enums). + +### Controlling where the label is placed + +By default, labels are placed at the beginning of the spans. +You can change this via the attribute `refanchor`, which can be set to: + + * `beg` (default); + * `end`; + * `both`. + +When the value is `both`, _text-crossrefs_ creates two labels +by suffixing `-beg` and `-end` to the identifier. +A typical use case is: + +``` +[A portion of text that may cross a page break.]{#mylbl refanchor=both} + +See [mylbl-beg>mylbl-end]{.tcrf}. +``` + +### Page ranges + +You can refer to a page range like this: + +``` markdown +If you want to know more about _L'Affaire Lerouge_, see [publication>reception]{.tcrf}. +``` + +The separator (here `>`) can be set to any string of characters +other than alphanumeric, period, colon, underscore, hyphen and space +(see the [customization options](#common-opts)). + +In LaTeX and ConTeXt output, the above-mentionned `\crossrefenum` macro +should be defined so that the range is printed as a simple page reference if the page numbers are identical. +The syntax of a range is[^customize-range-tex]: + +``` tex +\crossrefenum{{publication to reception}} +``` + +[^customize-range-tex]: Note that [it can be customized](#tex-options). + +In DOCX and ODT/Opendocument output, +a range will be printed as a range even if the numbers are identical. + +### Enumerations {#enums} + +You can enumerate several references as a comma-delimited list, for instance: + +``` markdown +[ref-one, ref-two>ref-three, ref-four]{.tcrf} +``` + +In DOCX and ODT/Opendocument output, all these references will be printed, +potentially resulting in unnecessary repetitions. +In TeX-based output formats, they will be wrapped in `\crossrefenum` like this +and should be collapsed by the macro when it is desirable: + +``` tex +\crossrefenum{{ref-one}{ref-two to ref-three}{ref-four}} +``` + +## Customization + +### Common options {#common-opts} + +The following metadata fields can be set as strings: + + * `tcrf-references-enum-separator`: + * the string between two references in an enumeration in a reference span; + * defaults to a comma followed by a space. + * `tcrf-references-range-separator`: + * the string used to separate two references in a reference span; + * defaults to `>`. + * `tcrf-only-explicit-labels`: + * set it to `true` if you want _crossrefenum_ to refer to spans with class `label` only; + * defaults to `false`. + * `tcrf-default-prefixref`: + * default value for the `prefixref` attribute; + * defaults to `yes`. + * `tcrf-default-reftype`: + * default value for the `reftype` attribute; + * defaults to `page`. + +### Options specific to DOCX and ODT/Opendocument + +Here are some metadata fields for the `docx`, `odt` and `opendocument` formats only +(see [above](#prefixes-tex) why they are ignored in ConTeXt and LaTeX output): + + * `tcrf-page-prefix`: + * “page” prefix; + * defaults to `p. `. + * `tcrf-pages-prefix`: + * “pages” prefix; + * defaults to `pp. `. + * `tcrf-note-prefix`: + * “note” prefix; + * defaults to `n. `. + * `tcrf-notes-prefix`: + * “notes” prefix; + * defaults to `nn. `. + * `tcrf-pagenote-separator`: + * the separator between the enumerated references in the output file + when `reftype` is set to `pagenote`; + * defaults to a comma followed by a space. + * `tcrf-pagenote-at-end`: + * the string printed at the end of a `pagenote` reference in the output file; + * defaults to the empty string, but it can be used to achieve something like *n. 3 (p. 5)* + (see the sample file). + * `tcrf-pagenote-factorize-first-prefix-in-enum`: + * defines if the prefixes of the type printed first in a reference to page and note + should be repeated (like in “p. 6, n. 1 and p. 9, n. 3”) + or set globally at the beginning of the enumeration (like in “pp. 6, n. 1 and 9, n. 3”); + * defaults to `no`, can be set to `yes`. + * `tcrf-pagenote-first-type`: + * the type of the reference number that is printed first in references to page and note; + * defaults to `page`, can be set to `note`. + * `tcrf-range-separator`: + * the string separating the page numbers in a range; + * defaults to `–`. + * `tcrf-references-enum-separator`: + * the string separating the elements of an enumeration in a reference span; + * defaults to a comma followed by a space. + * `tcrf-multiple-delimiter`: + * the string between two elements (but the two last ones) in an enumeration; + * defaults to a comma followed by a space. + * `tcrf-multiple-before-last`: + * the string inserted between the two last elements in an enumeration; + * defaults to `and` surrounded with spaces. + +### Options specific to the formats based on TeX {#tex-options} + +Since TeX is extensible, you may wish to support types +other than `page`, `note` and `pagenote` for ConTeXt and LaTeX output. +`tcrf-additional-types` can be provided with a list of supplementary accepted types, e.g.: + +``` yaml +tcrf-additional-types: +- line +- figure +``` + +Once declared, these types can be used as the value of `reftype` +in the same way as `page`, `note` and `pagenote`. + +In addition, the following metadata field can be used to control the rendering of ranges of labels in `\crossrefenum`: + + * `tcrf-range-delim-crossrefenum`: + * the delimiter between the labels of a range in the TeX output file; + * defaults to `to` surrounded with spaces. + +## Compatibility with other filters + +_text-crossrefs_ must be run after all filters that may create, delete or move footnotes, such as citeproc. + +In a citation inside square brackets, the span bearing an identifier should not include +a citation key, a locator or a `;` delimiter. +When it follows immediatly the locator, +you should protect the locator with curly brackets. +For example, this should work: + + ``` markdown + [@Jones1973, p. 5-70; @Doe2004[]{#jones-doe}] + + [@Jones1973, p. 5-70; [it was elaborated upon]{#further-elaboration} by @Doe2004] + + [@Jones1973, {p. 5-70}[]{#ref-to-jones}; @Doe2004] + ``` + +not that: + + ``` markdown + [[@Jones1973, p. 5-70]{#ref-to-jones}; @Doe2004] + + [[@Jones1973, p. 5-70; @Doe2004]{#jones-doe}] + + [@Jones1973, p. 5-70[]{#ref-to-jones}; @Doe2004] + ``` diff --git a/text-crossrefs/sample.md b/text-crossrefs/sample.md new file mode 100644 index 0000000..a4a0906 --- /dev/null +++ b/text-crossrefs/sample.md @@ -0,0 +1,41 @@ +--- +tcrf-pagenote-separator: '\ (' +tcrf-pagenote-at-end: ')' +header-includes: | + \input{crossrefenum} +--- + +(About the notes, see [toc-notes-begin>toc-notes-end]{.tcrf}.) + +Émile Gaboriau published [_L'Affaire Lerouge_ in +1866]{#publication}.[^1] + +[^1]: It is a very [fine piece of literature]{#my-evaluation}. + +[It was very popular.]{#reception} + +See [publication]{.tcrf} for the publication date. I expressed +my thoughts about it in [my-evaluation]{.tcrf reftype=pagenote}. + +If you want to know more about _L'Affaire Lerouge_, see [publication>reception]{.tcrf}. + +Here are some precisions.[^2] + +[^2]: [Whatever format]{#format} you choose, you can [refer to a note]{#refer-to-note} by the identifier of [any of its spans. You can even [nest spans]{#nested-spans}!]{#which-identifier} + +[I want to refer to a note]{#toc-notes-begin}: + + * How can I refer to a note by its number? → See [refer-to-note]{.tcrf}. + * What formats are supported? → See [format]{.tcrf}. + * What if the note contains multiple spans with identifiers? → See [which-identifier]{.tcrf}. + * What happens if a span in contained in a span? → See [nested-spans]{.tcrf}. + * What are the notes? → [my-evaluation, format, refer-to-note]{.tcrf reftype=note} + * Where are the notes? → [my-evaluation, format]{.tcrf} + +[]{#toc-notes-end} + +[A portion of text that may cross a page break.]{#doubledlbl refanchor=both} + +[And this one is labelized at the end.]{#lblatend refanchor=end} + +See [doubledlbl-beg>doubledlbl-end]{.tcrf}. diff --git a/text-crossrefs/test/sample-context.native b/text-crossrefs/test/sample-context.native new file mode 100644 index 0000000..30f7c06 --- /dev/null +++ b/text-crossrefs/test/sample-context.native @@ -0,0 +1,456 @@ +[ Para + [ Str "(About" + , Space + , Str "the" + , Space + , Str "notes," + , Space + , Str "see" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "context") + "\\crossrefenum{{toc-notes-begin to toc-notes-end}}" + ] + , Str ".)" + ] +, Para + [ Str "\201mile" + , Space + , Str "Gaboriau" + , Space + , Str "published" + , Space + , Str "" + , Span + ( "publication" , [] , [] ) + [ Emph [ Str "L\8217Affaire" , Space , Str "Lerouge" ] + , Space + , Str "in" + , SoftBreak + , Str "1866" + ] + , Str "." + , RawInline + (Format "context") "\\withfirstopt[note:my-evaluation]" + , Note + [ Para + [ Str "It" + , Space + , Str "is" + , Space + , Str "a" + , Space + , Str "very" + , Space + , Str "" + , Span + ( "my-evaluation" , [] , [] ) + [ Str "fine" + , Space + , Str "piece" + , Space + , Str "of" + , Space + , Str "literature" + ] + , Str "." + ] + ] + , RawInline (Format "context") "" + ] +, Para + [ Str "" + , Span + ( "reception" , [] , [] ) + [ Str "It" + , Space + , Str "was" + , Space + , Str "very" + , Space + , Str "popular." + ] + ] +, Para + [ Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "context") "\\crossrefenum{{publication}}" + ] + , Space + , Str "for" + , Space + , Str "the" + , Space + , Str "publication" + , Space + , Str "date." + , Space + , Str "I" + , Space + , Str "expressed" + , SoftBreak + , Str "my" + , Space + , Str "thoughts" + , Space + , Str "about" + , Space + , Str "it" + , Space + , Str "in" + , Space + , Span + ( "" , [ "tcrf" ] , [ ( "reftype" , "pagenote" ) ] ) + [ RawInline + (Format "context") + "\\crossrefenum[pagenote]{{my-evaluation}}" + ] + , Str "." + ] +, Para + [ Str "If" + , Space + , Str "you" + , Space + , Str "want" + , Space + , Str "to" + , Space + , Str "know" + , Space + , Str "more" + , Space + , Str "about" + , Space + , Emph [ Str "L\8217Affaire" , Space , Str "Lerouge" ] + , Str "," + , Space + , Str "see" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "context") + "\\crossrefenum{{publication to reception}}" + ] + , Str "." + ] +, Para + [ Str "Here" + , Space + , Str "are" + , Space + , Str "some" + , Space + , Str "precisions." + , RawInline (Format "context") "\\withfirstopt[note:format]" + , Note + [ Para + [ Str "" + , Span + ( "format" , [] , [] ) + [ Str "Whatever" , Space , Str "format" ] + , Space + , Str "you" + , Space + , Str "choose," + , Space + , Str "you" + , Space + , Str "can" + , Space + , Str "" + , Span + ( "refer-to-note" , [] , [] ) + [ Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + ] + , Space + , Str "by" + , Space + , Str "the" + , Space + , Str "identifier" + , Space + , Str "of" + , Space + , Str "" + , Span + ( "which-identifier" , [] , [] ) + [ Str "any" + , Space + , Str "of" + , Space + , Str "its" + , Space + , Str "spans." + , Space + , Str "You" + , Space + , Str "can" + , Space + , Str "even" + , Space + , Str "" + , Span + ( "nested-spans" , [] , [] ) + [ Str "nest" , Space , Str "spans" ] + , Str "!" + ] + ] + ] + , RawInline (Format "context") "" + ] +, Para + [ Str "" + , Span + ( "toc-notes-begin" , [] , [] ) + [ Str "I" + , Space + , Str "want" + , Space + , Str "to" + , Space + , Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + ] + , Str ":" + ] +, BulletList + [ [ Plain + [ Str "How" + , Space + , Str "can" + , Space + , Str "I" + , Space + , Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + , Space + , Str "by" + , Space + , Str "its" + , Space + , Str "number?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "context") "\\crossrefenum{{refer-to-note}}" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "formats" + , Space + , Str "are" + , Space + , Str "supported?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline (Format "context") "\\crossrefenum{{format}}" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "if" + , Space + , Str "the" + , Space + , Str "note" + , Space + , Str "contains" + , Space + , Str "multiple" + , Space + , Str "spans" + , Space + , Str "with" + , Space + , Str "identifiers?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "context") + "\\crossrefenum{{which-identifier}}" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "happens" + , Space + , Str "if" + , Space + , Str "a" + , Space + , Str "span" + , Space + , Str "in" + , Space + , Str "contained" + , Space + , Str "in" + , Space + , Str "a" + , Space + , Str "span?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "context") "\\crossrefenum{{nested-spans}}" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "are" + , Space + , Str "the" + , Space + , Str "notes?" + , Space + , Str "\8594" + , Space + , Span + ( "" , [ "tcrf" ] , [ ( "reftype" , "note" ) ] ) + [ RawInline + (Format "context") + "\\crossrefenum[note]{{my-evaluation}{format}{refer-to-note}}" + ] + ] + ] + , [ Plain + [ Str "Where" + , Space + , Str "are" + , Space + , Str "the" + , Space + , Str "notes?" + , Space + , Str "\8594" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "context") + "\\crossrefenum{{my-evaluation}{format}}" + ] + ] + ] + ] +, Para [ Str "" , Span ( "toc-notes-end" , [] , [] ) [] ] +, Para + [ Str "" + , Span + ( "doubledlbl" , [] , [ ( "refanchor" , "both" ) ] ) + [ Str "" + , Span ( "doubledlbl-beg" , [] , [] ) [] + , Str "A" + , Space + , Str "portion" + , Space + , Str "of" + , Space + , Str "text" + , Space + , Str "that" + , Space + , Str "may" + , Space + , Str "cross" + , Space + , Str "a" + , Space + , Str "page" + , Space + , Str "break." + , Str "" + , Span ( "doubledlbl-end" , [] , [] ) [] + ] + ] +, Para + [ Str "" + , Span + ( "lblatend" , [] , [ ( "refanchor" , "end" ) ] ) + [ Str "And" + , Space + , Str "this" + , Space + , Str "one" + , Space + , Str "is" + , Space + , Str "labelized" + , Space + , Str "at" + , Space + , Str "the" + , Space + , Str "end." + , Str "" + , Span ( "lblatend" , [] , [] ) [] + ] + ] +, Para + [ Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "context") + "\\crossrefenum{{doubledlbl-beg to doubledlbl-end}}" + ] + , Str "." + ] +] diff --git a/text-crossrefs/test/sample-latex.native b/text-crossrefs/test/sample-latex.native new file mode 100644 index 0000000..7cfcb88 --- /dev/null +++ b/text-crossrefs/test/sample-latex.native @@ -0,0 +1,454 @@ +[ Para + [ Str "(About" + , Space + , Str "the" + , Space + , Str "notes," + , Space + , Str "see" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "latex") + "\\crossrefenum{{toc-notes-begin to toc-notes-end}}" + ] + , Str ".)" + ] +, Para + [ Str "\201mile" + , Space + , Str "Gaboriau" + , Space + , Str "published" + , Space + , RawInline (Format "latex") "\\label{publication}" + , Span + ( "publication" , [] , [] ) + [ Emph [ Str "L\8217Affaire" , Space , Str "Lerouge" ] + , Space + , Str "in" + , SoftBreak + , Str "1866" + ] + , Str "." + , RawInline (Format "latex") "" + , Note + [ Para + [ Str "It" + , Space + , Str "is" + , Space + , Str "a" + , Space + , Str "very" + , Space + , RawInline (Format "latex") "\\label{my-evaluation}" + , Span + ( "my-evaluation" , [] , [] ) + [ Str "fine" + , Space + , Str "piece" + , Space + , Str "of" + , Space + , Str "literature" + ] + , Str "." + ] + ] + , RawInline (Format "latex") "" + ] +, Para + [ RawInline (Format "latex") "\\label{reception}" + , Span + ( "reception" , [] , [] ) + [ Str "It" + , Space + , Str "was" + , Space + , Str "very" + , Space + , Str "popular." + ] + ] +, Para + [ Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline (Format "latex") "\\crossrefenum{{publication}}" + ] + , Space + , Str "for" + , Space + , Str "the" + , Space + , Str "publication" + , Space + , Str "date." + , Space + , Str "I" + , Space + , Str "expressed" + , SoftBreak + , Str "my" + , Space + , Str "thoughts" + , Space + , Str "about" + , Space + , Str "it" + , Space + , Str "in" + , Space + , Span + ( "" , [ "tcrf" ] , [ ( "reftype" , "pagenote" ) ] ) + [ RawInline + (Format "latex") "\\crossrefenum[pagenote]{{my-evaluation}}" + ] + , Str "." + ] +, Para + [ Str "If" + , Space + , Str "you" + , Space + , Str "want" + , Space + , Str "to" + , Space + , Str "know" + , Space + , Str "more" + , Space + , Str "about" + , Space + , Emph [ Str "L\8217Affaire" , Space , Str "Lerouge" ] + , Str "," + , Space + , Str "see" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "latex") + "\\crossrefenum{{publication to reception}}" + ] + , Str "." + ] +, Para + [ Str "Here" + , Space + , Str "are" + , Space + , Str "some" + , Space + , Str "precisions." + , RawInline (Format "latex") "" + , Note + [ Para + [ RawInline (Format "latex") "\\label{format}" + , Span + ( "format" , [] , [] ) + [ Str "Whatever" , Space , Str "format" ] + , Space + , Str "you" + , Space + , Str "choose," + , Space + , Str "you" + , Space + , Str "can" + , Space + , RawInline (Format "latex") "\\label{refer-to-note}" + , Span + ( "refer-to-note" , [] , [] ) + [ Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + ] + , Space + , Str "by" + , Space + , Str "the" + , Space + , Str "identifier" + , Space + , Str "of" + , Space + , RawInline (Format "latex") "\\label{which-identifier}" + , Span + ( "which-identifier" , [] , [] ) + [ Str "any" + , Space + , Str "of" + , Space + , Str "its" + , Space + , Str "spans." + , Space + , Str "You" + , Space + , Str "can" + , Space + , Str "even" + , Space + , RawInline (Format "latex") "\\label{nested-spans}" + , Span + ( "nested-spans" , [] , [] ) + [ Str "nest" , Space , Str "spans" ] + , Str "!" + ] + ] + ] + , RawInline (Format "latex") "" + ] +, Para + [ RawInline (Format "latex") "\\label{toc-notes-begin}" + , Span + ( "toc-notes-begin" , [] , [] ) + [ Str "I" + , Space + , Str "want" + , Space + , Str "to" + , Space + , Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + ] + , Str ":" + ] +, BulletList + [ [ Plain + [ Str "How" + , Space + , Str "can" + , Space + , Str "I" + , Space + , Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + , Space + , Str "by" + , Space + , Str "its" + , Space + , Str "number?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "latex") "\\crossrefenum{{refer-to-note}}" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "formats" + , Space + , Str "are" + , Space + , Str "supported?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline (Format "latex") "\\crossrefenum{{format}}" ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "if" + , Space + , Str "the" + , Space + , Str "note" + , Space + , Str "contains" + , Space + , Str "multiple" + , Space + , Str "spans" + , Space + , Str "with" + , Space + , Str "identifiers?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "latex") "\\crossrefenum{{which-identifier}}" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "happens" + , Space + , Str "if" + , Space + , Str "a" + , Space + , Str "span" + , Space + , Str "in" + , Space + , Str "contained" + , Space + , Str "in" + , Space + , Str "a" + , Space + , Str "span?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "latex") "\\crossrefenum{{nested-spans}}" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "are" + , Space + , Str "the" + , Space + , Str "notes?" + , Space + , Str "\8594" + , Space + , Span + ( "" , [ "tcrf" ] , [ ( "reftype" , "note" ) ] ) + [ RawInline + (Format "latex") + "\\crossrefenum[note]{{my-evaluation}{format}{refer-to-note}}" + ] + ] + ] + , [ Plain + [ Str "Where" + , Space + , Str "are" + , Space + , Str "the" + , Space + , Str "notes?" + , Space + , Str "\8594" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "latex") + "\\crossrefenum{{my-evaluation}{format}}" + ] + ] + ] + ] +, Para + [ RawInline (Format "latex") "\\label{toc-notes-end}" + , Span ( "toc-notes-end" , [] , [] ) [] + ] +, Para + [ RawInline (Format "latex") "\\label{doubledlbl}" + , Span + ( "doubledlbl" , [] , [ ( "refanchor" , "both" ) ] ) + [ RawInline (Format "latex") "\\label{doubledlbl-beg}" + , Span ( "doubledlbl-beg" , [] , [] ) [] + , Str "A" + , Space + , Str "portion" + , Space + , Str "of" + , Space + , Str "text" + , Space + , Str "that" + , Space + , Str "may" + , Space + , Str "cross" + , Space + , Str "a" + , Space + , Str "page" + , Space + , Str "break." + , RawInline (Format "latex") "\\label{doubledlbl-end}" + , Span ( "doubledlbl-end" , [] , [] ) [] + ] + ] +, Para + [ RawInline (Format "latex") "\\label{lblatend}" + , Span + ( "lblatend" , [] , [ ( "refanchor" , "end" ) ] ) + [ Str "And" + , Space + , Str "this" + , Space + , Str "one" + , Space + , Str "is" + , Space + , Str "labelized" + , Space + , Str "at" + , Space + , Str "the" + , Space + , Str "end." + , RawInline (Format "latex") "\\label{lblatend}" + , Span ( "lblatend" , [] , [] ) [] + ] + ] +, Para + [ Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "latex") + "\\crossrefenum{{doubledlbl-beg to doubledlbl-end}}" + ] + , Str "." + ] +] diff --git a/text-crossrefs/test/sample-opendocument.native b/text-crossrefs/test/sample-opendocument.native new file mode 100644 index 0000000..fbd1f37 --- /dev/null +++ b/text-crossrefs/test/sample-opendocument.native @@ -0,0 +1,460 @@ +[ Para + [ Str "(About" + , Space + , Str "the" + , Space + , Str "notes," + , Space + , Str "see" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "pp.\160000\8211000" + ] + , Str ".)" + ] +, Para + [ Str "\201mile" + , Space + , Str "Gaboriau" + , Space + , Str "published" + , Space + , Str "" + , Span + ( "publication" , [] , [] ) + [ Emph [ Str "L\8217Affaire" , Space , Str "Lerouge" ] + , Space + , Str "in" + , SoftBreak + , Str "1866" + ] + , Str "." + , RawInline (Format "opendocument") "" + , Note + [ Para + [ Str "It" + , Space + , Str "is" + , Space + , Str "a" + , Space + , Str "very" + , Space + , Str "" + , Span + ( "my-evaluation" , [] , [] ) + [ Str "fine" + , Space + , Str "piece" + , Space + , Str "of" + , Space + , Str "literature" + ] + , Str "." + ] + ] + , RawInline (Format "opendocument") "" + ] +, Para + [ Str "" + , Span + ( "reception" , [] , [] ) + [ Str "It" + , Space + , Str "was" + , Space + , Str "very" + , Space + , Str "popular." + ] + ] +, Para + [ Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "p.\160000" + ] + , Space + , Str "for" + , Space + , Str "the" + , Space + , Str "publication" + , Space + , Str "date." + , Space + , Str "I" + , Space + , Str "expressed" + , SoftBreak + , Str "my" + , Space + , Str "thoughts" + , Space + , Str "about" + , Space + , Str "it" + , Space + , Str "in" + , Space + , Span + ( "" , [ "tcrf" ] , [ ( "reftype" , "pagenote" ) ] ) + [ RawInline + (Format "opendocument") + "p.\160000\160(n.\160000)" + ] + , Str "." + ] +, Para + [ Str "If" + , Space + , Str "you" + , Space + , Str "want" + , Space + , Str "to" + , Space + , Str "know" + , Space + , Str "more" + , Space + , Str "about" + , Space + , Emph [ Str "L\8217Affaire" , Space , Str "Lerouge" ] + , Str "," + , Space + , Str "see" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "pp.\160000\8211000" + ] + , Str "." + ] +, Para + [ Str "Here" + , Space + , Str "are" + , Space + , Str "some" + , Space + , Str "precisions." + , RawInline (Format "opendocument") "" + , Note + [ Para + [ Str "" + , Span + ( "format" , [] , [] ) + [ Str "Whatever" , Space , Str "format" ] + , Space + , Str "you" + , Space + , Str "choose," + , Space + , Str "you" + , Space + , Str "can" + , Space + , Str "" + , Span + ( "refer-to-note" , [] , [] ) + [ Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + ] + , Space + , Str "by" + , Space + , Str "the" + , Space + , Str "identifier" + , Space + , Str "of" + , Space + , Str "" + , Span + ( "which-identifier" , [] , [] ) + [ Str "any" + , Space + , Str "of" + , Space + , Str "its" + , Space + , Str "spans." + , Space + , Str "You" + , Space + , Str "can" + , Space + , Str "even" + , Space + , Str "" + , Span + ( "nested-spans" , [] , [] ) + [ Str "nest" , Space , Str "spans" ] + , Str "!" + ] + ] + ] + , RawInline (Format "opendocument") "" + ] +, Para + [ Str "" + , Span + ( "toc-notes-begin" , [] , [] ) + [ Str "I" + , Space + , Str "want" + , Space + , Str "to" + , Space + , Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + ] + , Str ":" + ] +, BulletList + [ [ Plain + [ Str "How" + , Space + , Str "can" + , Space + , Str "I" + , Space + , Str "refer" + , Space + , Str "to" + , Space + , Str "a" + , Space + , Str "note" + , Space + , Str "by" + , Space + , Str "its" + , Space + , Str "number?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "p.\160000" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "formats" + , Space + , Str "are" + , Space + , Str "supported?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "p.\160000" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "if" + , Space + , Str "the" + , Space + , Str "note" + , Space + , Str "contains" + , Space + , Str "multiple" + , Space + , Str "spans" + , Space + , Str "with" + , Space + , Str "identifiers?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "p.\160000" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "happens" + , Space + , Str "if" + , Space + , Str "a" + , Space + , Str "span" + , Space + , Str "in" + , Space + , Str "contained" + , Space + , Str "in" + , Space + , Str "a" + , Space + , Str "span?" + , Space + , Str "\8594" + , Space + , Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "p.\160000" + ] + , Str "." + ] + ] + , [ Plain + [ Str "What" + , Space + , Str "are" + , Space + , Str "the" + , Space + , Str "notes?" + , Space + , Str "\8594" + , Space + , Span + ( "" , [ "tcrf" ] , [ ( "reftype" , "note" ) ] ) + [ RawInline + (Format "opendocument") + "nn.\160000, 000 and 000" + ] + ] + ] + , [ Plain + [ Str "Where" + , Space + , Str "are" + , Space + , Str "the" + , Space + , Str "notes?" + , Space + , Str "\8594" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "pp.\160000 and 000" + ] + ] + ] + ] +, Para [ Str "" , Span ( "toc-notes-end" , [] , [] ) [] ] +, Para + [ Str "" + , Span + ( "doubledlbl" , [] , [ ( "refanchor" , "both" ) ] ) + [ Str "" + , Span ( "doubledlbl-beg" , [] , [] ) [] + , Str "A" + , Space + , Str "portion" + , Space + , Str "of" + , Space + , Str "text" + , Space + , Str "that" + , Space + , Str "may" + , Space + , Str "cross" + , Space + , Str "a" + , Space + , Str "page" + , Space + , Str "break." + , Str "" + , Span ( "doubledlbl-end" , [] , [] ) [] + ] + ] +, Para + [ Str "" + , Span + ( "lblatend" , [] , [ ( "refanchor" , "end" ) ] ) + [ Str "And" + , Space + , Str "this" + , Space + , Str "one" + , Space + , Str "is" + , Space + , Str "labelized" + , Space + , Str "at" + , Space + , Str "the" + , Space + , Str "end." + , Str "" + , Span ( "lblatend" , [] , [] ) [] + ] + ] +, Para + [ Str "See" + , Space + , Span + ( "" , [ "tcrf" ] , [] ) + [ RawInline + (Format "opendocument") + "pp.\160000\8211000" + ] + , Str "." + ] +] diff --git a/text-crossrefs/test/test-functions.lua b/text-crossrefs/test/test-functions.lua new file mode 100644 index 0000000..e38bc95 --- /dev/null +++ b/text-crossrefs/test/test-functions.lua @@ -0,0 +1,36 @@ +local refs + +refs = parse_references_enum('mylabel') +assert(#refs == 1) +assert(refs[1].anchor == 'mylabel') +assert(not refs[1].end_of_range) +assert(make_raw_content_tex(refs, 'page', true) + == '\\crossrefenum{{mylabel}}') +assert(make_raw_content_tex(refs, 'page', false) + == '\\crossrefenum[noprefix]{{mylabel}}') +assert(make_raw_content_tex(refs, 'note', true) + == '\\crossrefenum[note]{{mylabel}}') +assert(make_raw_content_tex(refs, 'note', false) + == '\\crossrefenum[note][noprefix]{{mylabel}}') + +refs = parse_references_enum('rangebeg>rangeend') +assert(#refs == 1) +assert(refs[1].anchor == 'rangebeg') +assert(refs[1].end_of_range == 'rangeend') +assert(make_raw_content_tex(refs, 'page', true) + == '\\crossrefenum{{rangebeg to rangeend}}') + +refs = parse_references_enum('first, second') +assert(#refs == 2) +assert(refs[1].anchor == 'first') +assert(refs[2].anchor == 'second') +assert(make_raw_content_tex(refs, 'page', true) + == '\\crossrefenum{{first}{second}}') + +refs = parse_references_enum('first, rangebeg>rangeend') +assert(#refs == 2) +assert(refs[1].anchor == 'first') +assert(refs[2].anchor == 'rangebeg') +assert(refs[2].end_of_range == 'rangeend') +assert(make_raw_content_tex(refs, 'page', true) + == '\\crossrefenum{{first}{rangebeg to rangeend}}') diff --git a/text-crossrefs/text-crossrefs.lua b/text-crossrefs/text-crossrefs.lua new file mode 100644 index 0000000..d9f631f --- /dev/null +++ b/text-crossrefs/text-crossrefs.lua @@ -0,0 +1,611 @@ +local stringify = pandoc.utils.stringify + +local TEXT_CROSSREF_CLASS = 'tcrf' +local REF_TYPE_ATTR = 'reftype' +local PREFIXED_ATTR = 'prefixref' +local PLACE_LABEL_ATTR = 'refanchor' +local IS_CONFIG_ARRAY = { ['additional_types'] = true } +local RAW_ATTRIBUTE + +-- ConTeXt-specific tweak in order to add the label to the footnote +--[[ + Placing the label in square brackets immediatly after \footnote + in the regular way would require unpacking the content + of the Note and wrapping them with the RawInlines + '\footnote[note:' .. label .. ']{' and '}'. + However, Notes have the strange property of being Inlines + that contain Blocks, so this would result in Blocks being + brought into the content of the object that contains the Note, + which would be invalid. + That's why we place the label at the end of the \footnote + and redefine the macro so that it takes it into account. +]]-- + +local function support_footnote_label_ConTeXt(metadata) + if RAW_ATTRIBUTE == 'context' then + local label_macro_def = '\n\\def\\withfirstopt[#1]#2{#2[#1]}\n' + if not metadata['header-includes'] then + metadata['header-includes'] = pandoc.MetaBlocks(pandoc.RawBlock('context', '')) + end + metadata['header-includes']:insert(pandoc.RawBlock('context', label_macro_def)) + end + return metadata +end + +-- Configuration + +local function define_raw_attribute() + if FORMAT == 'native' then + RAW_ATTRIBUTE = pandoc.system.environment().TESTED_FORMAT + elseif FORMAT == 'docx' then + RAW_ATTRIBUTE = 'openxml' + elseif FORMAT == 'odt' or FORMAT == 'opendocument' then + RAW_ATTRIBUTE = 'opendocument' + elseif FORMAT == 'context' or FORMAT == 'latex' then + RAW_ATTRIBUTE = FORMAT + else + error(FORMAT .. + ' output not supported by text-crossrefs.lua.') + end +end + +local function define_label_template() + if RAW_ATTRIBUTE == 'opendocument' or RAW_ATTRIBUTE == 'openxml' then + IS_LABEL_SET_BY_PANDOC = true + elseif RAW_ATTRIBUTE == 'context' then + if PANDOC_VERSION < pandoc.types.Version('2.14') then + LABEL_TEMPLATE = '\\pagereference[{{label}}]' + else + IS_LABEL_SET_BY_PANDOC = true + end + elseif RAW_ATTRIBUTE == 'latex' then + LABEL_TEMPLATE = '\\label{{{label}}}' + end +end + +local config = { + page_prefix = 'p. ', + pages_prefix = 'pp. ', + note_prefix = 'n. ', + notes_prefix = 'nn. ', + pagenote_first_type = 'page', + pagenote_separator = ', ', + pagenote_at_end = '', + pagenote_factorize_first_prefix_in_enum = 'no', + multiple_delimiter = ', ', + multiple_before_last = ' and ', + references_range_separator = '>', + range_separator = '–', + references_enum_separator = ', ', + only_explicit_labels = 'false', + default_reftype = 'page', + default_prefixref = 'yes', + filelabel_ref_separator = '::', + range_delim_crossrefenum = ' to ', + additional_types = {} +} + +local accepted_types = { + page = true, + note = true, + pagenote = true +} + +local function format_config_to_openxml() + local to_format = { 'page_prefix', + 'pages_prefix', + 'note_prefix', + 'notes_prefix', + 'pagenote_separator', + 'pagenote_at_end', + 'range_separator', + 'multiple_delimiter', + 'multiple_before_last' } + for i = 1, #to_format do + config[to_format[i]] = '' .. + config[to_format[i]] .. '' + end +end + +local function set_configuration_item_from_metadata(item, metamap) + metakey = 'tcrf-' .. string.gsub(item, '_', '-') + if metamap[metakey] then + if IS_CONFIG_ARRAY[item] then + -- The metadata values is a list of MetaInlines, + -- each of them contains a single Str. + for _, value_metalist in ipairs(metamap[metakey]) do + table.insert(config[item], value_metalist[1].text) + end + else + -- The metadata value is a single Str in a MetaInlines. + config[item] = metamap[metakey][1].text + end + end +end + +local function configure(metadata) + define_raw_attribute() + define_label_template() + for item, _ in pairs(config) do + set_configuration_item_from_metadata(item, metadata) + end + if RAW_ATTRIBUTE == 'openxml' then + format_config_to_openxml() + end + if RAW_ATTRIBUTE == 'context' or RAW_ATTRIBUTE == 'latex' then + for _, additional_type in ipairs(config.additional_types) do + accepted_types[additional_type] = true + end + end +end + +-- End of configuration + +-- Preprocessing of identifiers on notes +-- Necessary for those output format where a note can be referred to +-- only via an identifier directly attached to it, not to its content + +local spans_to_note_labels = {} +local current_odt_note_index = 0 +local is_first_span_in_note = true +local current_note_label +local text_to_note_labels = {} + +local function map_span_to_label(span) + if RAW_ATTRIBUTE == 'opendocument' then + spans_to_note_labels[span.identifier] = 'ftn' .. current_odt_note_index + elseif RAW_ATTRIBUTE == 'openxml' or RAW_ATTRIBUTE == 'context' then + if is_first_span_in_note then + current_note_label = span.identifier + is_first_span_in_note = false + end + spans_to_note_labels[span.identifier] = current_note_label + end +end + +local function map_spans_to_labels(container) + for i = 1, #container.content do + -- The tests must be separate in order to support spans inside spans. + if container.content[i].t == 'Span' + and container.content[i].identifier + then + map_span_to_label(container.content[i]) + end + if container.content[i].content then + map_spans_to_labels(container.content[i]) + end + end +end + +local function map_spans_to_notelabels(note) + if RAW_ATTRIBUTE == 'context' + or RAW_ATTRIBUTE == 'opendocument' + or RAW_ATTRIBUTE == 'openxml' + then + is_first_span_in_note = true + map_spans_to_labels(note) + current_odt_note_index = current_odt_note_index + 1 + end +end + +local function control_label_placement(span) + local label_placement = span.attributes[PLACE_LABEL_ATTR] + if label_placement then + local id = span.identifier + if label_placement == 'end' then + span.content:insert(pandoc.Span({}, { id = id })) + span.identifier = nil + elseif label_placement == 'both' then + span.content:insert(1, pandoc.Span({}, { id = id .. '-beg' })) -- for DOCX/ODT + 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”.') + end + end + return span +end + +local function make_label(label) + -- pandoc.Null() cannot be used here because it is a Block element. + local label_pandoc_object = pandoc.Str('') + if not IS_LABEL_SET_BY_PANDOC then + local label_rawcode = string.gsub(LABEL_TEMPLATE, '{{label}}', label) + label_pandoc_object = pandoc.RawInline(RAW_ATTRIBUTE, label_rawcode) + end + return label_pandoc_object +end + +local function labelize_span(span) + if span.identifier ~= '' then + local label = span.identifier + local label_begin = make_label(label, 'begin') + return { label_begin, span } + end +end + +local current_note_labels = {} + +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) + end + end +} + +local function make_notelabel(pos) + -- About the strategy followed with ConTeXt, + -- see above support_footnote_label_ConTeXt. + local raw_code = '' + if pos == 'begin' then + if RAW_ATTRIBUTE == 'openxml' then + raw_code = string.gsub( + '', + '{{label}}', current_note_labels[1]) + elseif RAW_ATTRIBUTE == 'context' then + raw_code = '\\withfirstopt[note:' .. current_note_labels[1] .. ']' + end + elseif pos == 'end' then + if RAW_ATTRIBUTE == 'openxml' then + raw_code = string.gsub('', + '{{label}}', current_note_labels[1]) + end + end + return pandoc.RawInline(RAW_ATTRIBUTE, raw_code) +end + +local function labelize_note(note) + local labelized_note + local label_begin = make_notelabel('begin') + local label_end = make_notelabel('end') + 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 + end +end + +function set_notelabels(note) + current_note_labels = {} + pandoc.walk_inline(note, collect_note_labels) + if #current_note_labels > 0 then + map_text_to_note_labels(current_note_labels) + return labelize_note(note) + end +end + +-- End of preprocessing of identifiers on notes + +-- Gathering of data from the references span + +local function new_ref(anchor, end_of_range) + -- A ref is a string-indexed table containing an "anchor" field + -- and an optionnal "end_of_range" field. + -- When "end_of_range" is non-nil, the ref is a range. + local ref = {} + ref.anchor = anchor + ref.end_of_range = end_of_range + return ref +end + +local function is_ref_external(raw_references) + if string.find(raw_references, config.filelabel_ref_separator, 1, true) then + return true + else + return false + end +end + +local function parse_possible_range(reference) + -- If reference is a string representing a range, + -- returns the strings representing the boundaries of the range. + -- Else, returns the string. + local range_first, range_second = nil + local delim_beg, delim_end = string.find(reference, + config.references_range_separator, + 1, true) + if delim_beg then + range_first = string.sub(reference, 1, delim_beg - 1) + range_second = string.sub(reference, delim_end + 1) + end + return (range_first or reference), range_second +end + +local function parse_next_reference(raw_references, beg_of_search) + -- Returns the ref corresponding to the next reference string + -- and the index which the parsing should be resumed at. + local current_ref = false + local next_ref_beg = nil + 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) + if delim_beg then + reference = string.sub(raw_references, beg_of_search, delim_beg - 1) + next_ref_beg = delim_end + 1 + else + reference = string.sub(raw_references, beg_of_search) + next_ref_beg = #raw_references + end + current_ref = new_ref(parse_possible_range(reference)) + end + return current_ref, next_ref_beg +end + +local function parse_references_enum(raw_references) + -- raw_refs is a string consisting of a list of single references or ranges. + -- Returns an array of refs produced by "new_ref" above. + local parsed_refs = {} + local current_ref, next_ref_beg = parse_next_reference(raw_references, 1) + while current_ref do + table.insert(parsed_refs, current_ref) + current_ref, next_ref_beg = + parse_next_reference(raw_references, next_ref_beg) + end + return parsed_refs +end + +local function error_on_attr(attr_key, attr_value, span_content) + error('Invalid value "' .. attr_value .. '" for attribute "' .. attr_key .. + '" in the span with class "' .. TEXT_CROSSREF_CLASS .. + '" whose content is "' .. stringify(span_content) .. '".') +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) + end + return ref_type +end + +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) + end + local is_prefixed = true + if prefixed_attr_value == 'no' then is_prefixed = false end + return is_prefixed +end + +-- End of gathering of data from the references span + +-- Formatting references as raw inlines. + +local function make_crossrefenum_first_arg(ref_type) + local ref_type_is_explicit = ref_type ~= config.default_reftype + local crossrefenum_first_arg = '' + if ref_type_is_explicit then + crossrefenum_first_arg = '[' .. ref_type .. ']' + end + return crossrefenum_first_arg +end + +local function make_crossrefenum_second_arg(is_prefixed) + local is_prefixed_is_explicit = is_prefixed ~= (config.default_prefixref == 'yes') + local crossrefenum_second_arg = '' + local is_prefixed_string = '' + if is_prefixed_is_explicit then + if is_prefixed then + is_prefixed_string = 'withprefix' + else + is_prefixed_string = 'noprefix' + end + crossrefenum_second_arg = '[' .. is_prefixed_string .. ']' + end + return crossrefenum_second_arg +end + +local function make_crossrefenum_references_list(refs, ref_type) + local crossrefenum_references_list = '' + for i = 1, #refs do + local ref = refs[i] + local anchor = ref.anchor + if FORMAT == 'context' + and (ref_type == 'note' or ref_type == 'pagenote') + then + anchor = text_to_note_labels[anchor] + end + local texified_ref = '{' .. anchor + if ref.end_of_range then + texified_ref = texified_ref .. config.range_delim_crossrefenum .. ref.end_of_range + end + texified_ref = texified_ref .. '}' + crossrefenum_references_list = crossrefenum_references_list .. texified_ref + end + return crossrefenum_references_list +end + +local function make_raw_content_tex(refs, ref_type, is_prefixed) + local texified_references = '' + texified_references = '\\crossrefenum' + .. make_crossrefenum_first_arg(ref_type) + .. make_crossrefenum_second_arg(is_prefixed) + .. '{' .. make_crossrefenum_references_list(refs, ref_type) .. '}' + return texified_references +end + +local function make_prefix_xml(ref_type, is_plural) + local prefix = '' + if is_plural then + prefix = config[ref_type .. 's_prefix'] + else + prefix = config[ref_type .. '_prefix'] + end + return prefix +end + +local function make_page_reference_xml(target, is_prefixed) + local xml_page_ref = '' + if is_prefixed then + xml_page_ref = make_prefix_xml('page', false) + end + if RAW_ATTRIBUTE == 'opendocument' then + xml_page_ref = xml_page_ref .. + '000' + elseif RAW_ATTRIBUTE == 'openxml' then + xml_page_ref = xml_page_ref .. + '' .. + ' PAGEREF ' .. + target .. ' \\h ' .. + '' .. + '000' .. + '' + end + return xml_page_ref +end + +local function make_pagerange_reference_xml(first, second, is_prefixed) + local prefix = '' + if is_prefixed then prefix = make_prefix_xml('page', true) end + return prefix .. make_page_reference_xml(first, false) .. + config.range_separator .. make_page_reference_xml(second, false) +end + +local function make_note_reference_xml(target, is_prefixed) + local note_ref_xml = '' + if is_prefixed then + note_ref_xml = make_prefix_xml('note', false) + end + if RAW_ATTRIBUTE == 'opendocument' then + note_ref_xml = note_ref_xml .. + '000' + elseif RAW_ATTRIBUTE == 'openxml' then + note_ref_xml = note_ref_xml .. + '' .. + ' NOTEREF ' .. + (spans_to_note_labels[target] or '') .. '_Note' .. ' \\h ' .. + '' .. + '000' .. + '' + end + return note_ref_xml +end + +local function make_pagenote_reference_xml(target, is_prefixed) + local pagenote_ref_xml = '' + if is_prefixed then + pagenote_ref_xml = make_prefix_xml(config.pagenote_first_type, false) + end + if config.pagenote_first_type == 'page' then + pagenote_ref_xml = pagenote_ref_xml .. + make_page_reference_xml(target, false) .. + config.pagenote_separator .. make_note_reference_xml(target, true) .. + config.pagenote_at_end + elseif config.pagenote_first_type == 'note' then + pagenote_ref_xml = pagenote_ref_xml .. + make_note_reference_xml(target, false) .. + config.pagenote_separator .. make_page_reference_xml(target, true) .. + config.pagenote_at_end + else + error('“tcrf-pagenote-first-type” must be set either to “page” or “note”.') + end + return pagenote_ref_xml +end + +local function make_reference_xml(ref, ref_type, is_prefixed) + local reference_xml = '' + if ref_type == 'page' and ref.end_of_range then + reference_xml = + make_pagerange_reference_xml(ref.anchor, ref.end_of_range, is_prefixed) + elseif ref_type == 'page' then + reference_xml = make_page_reference_xml(ref.anchor, is_prefixed) + elseif ref_type == 'note' then + reference_xml = make_note_reference_xml(ref.anchor, is_prefixed) + elseif ref_type == 'pagenote' then + reference_xml = make_pagenote_reference_xml(ref.anchor, is_prefixed) + end + return reference_xml +end + +local function make_global_prefix_xml(ref_type) + local global_prefix_xml = '' + local prefix_type = ref_type + if ref_type == 'pagenote' then + prefix_type = config.pagenote_first_type + end + global_prefix_xml = make_prefix_xml(prefix_type, true) + return global_prefix_xml +end + +local function make_references_xml(refs, ref_type, is_prefixed) + local references_xml = '' + for i = 1, #refs do + references_xml = references_xml .. + make_reference_xml(refs[i], ref_type, is_prefixed) + if i < #refs then + if i < #refs - 1 then + references_xml = references_xml .. config.multiple_delimiter + else + references_xml = references_xml .. config.multiple_before_last + end + end + end + return references_xml +end + +local function make_raw_content_xml(refs, ref_type, is_prefixed) + local is_enumeration = #refs > 1 + local global_prefix = '' + if is_enumeration and is_prefixed + and (ref_type ~= 'pagenote' + or pagenote_factorize_first_prefix_in_enum == 'yes') + then + global_prefix = make_global_prefix_xml(ref_type) + is_prefixed = false + end + local references_raw_xml = make_references_xml(refs, ref_type, is_prefixed) + return global_prefix .. references_raw_xml +end + +local function make_raw_content(refs, ref_type, is_prefixed) + local raw_content = '' + if RAW_ATTRIBUTE == 'context' or RAW_ATTRIBUTE == 'latex' then + raw_content = make_raw_content_tex(refs, ref_type, is_prefixed) + else + raw_content = make_raw_content_xml(refs, ref_type, is_prefixed) + end + return raw_content +end + +local function format_references(refs, ref_type, is_prefixed) + local raw_content = make_raw_content(refs, ref_type, is_prefixed) + return pandoc.RawInline(RAW_ATTRIBUTE, raw_content) +end + +local function format_enum(span) + -- A reference is a Str contained in a span representing a label or a range of labels. + -- A ref is a ref object produced by the function "new_ref" defined above. + if span.classes:includes(TEXT_CROSSREF_CLASS) + and not(is_ref_external(stringify(span.content))) + then + local refs = parse_references_enum(stringify(span.content)) + local ref_type = get_ref_type(span) + local is_prefixed = if_prefixed(span) + span.content = format_references(refs, ref_type, is_prefixed) + end + return span +end + +return { + { Meta = configure }, + { Meta = support_footnote_label_ConTeXt }, + { Note = set_notelabels }, + { Note = map_spans_to_notelabels }, + { Span = control_label_placement }, + { Span = labelize_span }, + { Span = format_enum } +}