├── img └── fig-1.png ├── expected ├── demo.docx ├── refnos.docx ├── table.md ├── table.tex ├── demo.md ├── demo.tex ├── refnos.md └── refnos.tex ├── samples ├── table.md ├── demo.md └── refnos.md ├── test_output.sh ├── LICENSE ├── README.md └── refnos.lua /img/fig-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tstenner/luarefnos/HEAD/img/fig-1.png -------------------------------------------------------------------------------- /expected/demo.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tstenner/luarefnos/HEAD/expected/demo.docx -------------------------------------------------------------------------------- /expected/refnos.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tstenner/luarefnos/HEAD/expected/refnos.docx -------------------------------------------------------------------------------- /expected/table.md: -------------------------------------------------------------------------------- 1 | Revision Date Change Description 2 | ---------- ------------ -------------------- 3 | 0.5 2022-07-05 Initial Draft 4 | -------------------------------------------------------------------------------- /samples/table.md: -------------------------------------------------------------------------------- 1 | Revision Date Change Description 2 | ---------- ------------ -------------------- 3 | 0.5 2022-07-05 Initial Draft 4 | -------------------------------------------------------------------------------- /expected/table.tex: -------------------------------------------------------------------------------- 1 | \begin{longtable}[]{@{}ccl@{}} 2 | \toprule\noalign{} 3 | Revision & Date & Change Description \\ 4 | \midrule\noalign{} 5 | \endhead 6 | \bottomrule\noalign{} 7 | \endlastfoot 8 | 0.5 & 2022-07-05 & Initial Draft \\ 9 | \end{longtable} 10 | -------------------------------------------------------------------------------- /test_output.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/bash 2 | 3 | set -e 4 | 5 | for f in samples/*; do 6 | for outfmt in md tex; do 7 | pandoc -L refnos.lua $f -o expected/$(basename ${f%%.*}).$outfmt 8 | done 9 | done 10 | discrepancies="$(git diff -- expected)" 11 | echo $discrepancies 12 | if [[ "$discrepancies" != "" ]]; then 13 | echo "Expected output doesn't match actual output!" 14 | echo $discrepandies 15 | exit 1 16 | fi 17 | -------------------------------------------------------------------------------- /samples/demo.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pandoc-fignos Demo 3 | lang: de 4 | luarefnos: 5 | strs: 6 | de: 7 | tab: {ref: 'Tabelle', plural: 'Tabellen'} 8 | tbl: {ref: 'Tableu', plural: 'Tableus'} 9 | fig: {ref: 'Plot', abbrev: 'Plot'} 10 | ... 11 | 12 | ![Plot eins](img/fig-1.png){#fig:1 width=1in} 13 | 14 | ![Plot zwei](img/fig-1.png){#fig:2 width=1in} 15 | 16 | Wir verweisen auf @fig:1 und [@fig:1; die @fig:2 mit Postfix; fehlenden @fig:3]. 17 | 18 | Foo Bar 19 | --- --- 20 | 1 2 21 | 22 | Table: Caption {#tbl:foobar} 23 | 24 | Note that @tbl:foobar has a special namespace name. 25 | -------------------------------------------------------------------------------- /expected/demo.md: -------------------------------------------------------------------------------- 1 |
2 | Plot eins 3 |
Plot 1: Plot eins
4 |
5 | 6 |
7 | Plot zwei 8 |
Plot 2: Plot zwei
9 |
10 | 11 | Wir verweisen auf Plot [1](#fig:1) und Plots [1](#fig:1), die 12 | [2](#fig:2) mit Postfix & fehlenden **fig:3?**. 13 | 14 | Foo Bar 15 | ----- ----- 16 | 1 2 17 | 18 | : [Tableu 1: ]{#tbl:foobar}Caption 19 | 20 | Note that Tableu [1](#tbl:foobar) has a special namespace name. 21 | -------------------------------------------------------------------------------- /expected/demo.tex: -------------------------------------------------------------------------------- 1 | \begin{figure} 2 | \centering 3 | \includegraphics[width=1in,height=\textheight]{img/fig-1.png} 4 | \caption{Plot 1: Plot eins}\label{fig:1} 5 | \end{figure} 6 | 7 | \begin{figure} 8 | \centering 9 | \includegraphics[width=1in,height=\textheight]{img/fig-1.png} 10 | \caption{Plot 2: Plot zwei}\label{fig:2} 11 | \end{figure} 12 | 13 | Wir verweisen auf Plot \ref{fig:1} und Plots \ref{fig:1}, die 14 | \ref{fig:2} mit Postfix \& fehlenden \ref{fig:3}. 15 | 16 | \begin{longtable}[]{@{}ll@{}} 17 | \caption{Caption}\label{tbl:foobar}\tabularnewline 18 | \toprule\noalign{} 19 | Foo & Bar \\ 20 | \midrule\noalign{} 21 | \endfirsthead 22 | \toprule\noalign{} 23 | Foo & Bar \\ 24 | \midrule\noalign{} 25 | \endhead 26 | \bottomrule\noalign{} 27 | \endlastfoot 28 | 1 & 2 \\ 29 | \end{longtable} 30 | 31 | Note that Tableu \ref{tbl:foobar} has a special namespace name. 32 | -------------------------------------------------------------------------------- /samples/refnos.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Pandoc-fignos Demo 3 | test: bar 4 | ... 5 | 6 | # Chapter 1 7 | 8 | In-text references to @fig:1 [B] and @tab:foobar results in 9 | **Figure 1B and Table 1**. 10 | 11 | Multi-reference to [@fig:1 (A); the cool part of @fig:1; @fig:three] 12 | and [@tab:foobar (very cool); *especially* @tab:foobar] is rendered as 13 | **Figures 1 (A), the cool part of 1 & 2 and Tables 1 (verycool) & *especially* 1** 14 | 15 | ![The number one.](img/fig-1.png){#fig:1 width=1in} 16 | 17 | ![The unlabeled number two.](img/fig-1.png){#fig: width=1in} 18 | 19 | ![The number three.](img/fig-1.png){#fig:three width=1in} 20 | 21 | Plot [-@fig:three] is given above, without adding the "Figure" prefix. 22 | 23 | As seen in @tab:foobar, commas are handled properly. 24 | 25 | ## Equations {#equationchapter} 26 | 27 | Equations, such as [@eq:pythagoras; @eq:einstein (however short it is)], have to be put into a span with an id: 28 | 29 | [$$a^2 + b^2 = c^2$$]{#eq:pythagoras} 30 | 31 | They can also be inline: [$$e=mc^2$$]{#eq:einstein}. 32 | 33 | Most people prefer @eq:pythagoras, but know [@eq:einstein]. 34 | 35 | ## Tables 36 | 37 | Foo Bar 38 | --- --- 39 | 1 2 40 | 41 | Table: Caption {#tab:foobar width=1em attr2=foo} 42 | 43 | # Another chapter 44 | 45 | References to @sec:equationchapter and the unnamed @sec:another-chapter 46 | 47 | Text at end. 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, Tristan Stenner 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | 27 | -------------------------------------------------------------------------------- /expected/refnos.md: -------------------------------------------------------------------------------- 1 | # Chapter 1 2 | 3 | In-text references to Figure [1](#fig:1)B and Table [1](#tab:foobar) 4 | results in **Figure 1B and Table 1**. 5 | 6 | Multi-reference to Figures [1](#fig:1) (A), the cool part of 7 | [1](#fig:1) & [3](#fig:three) and Tables [1](#tab:foobar) (very 8 | cool) & *especially* [1](#tab:foobar) is rendered as **Figures 1 (A), 9 | the cool part of 1 & 2 and Tables 1 (verycool) & *especially* 1** 10 | 11 |
12 | The number one. 13 |
Figure 1: The number one.
14 |
15 | 16 |
17 | The unlabeled number two. 19 |
Figure 2: The unlabeled number two.
20 |
21 | 22 |
23 | The number three. 24 |
Figure 3: The number three.
25 |
26 | 27 | Plot [3](#fig:three) is given above, without adding the "Figure" prefix. 28 | 29 | As seen in Table [1](#tab:foobar), commas are handled properly. 30 | 31 | ## Equations {#equationchapter} 32 | 33 | Equations, such as Equations [1](#eq:pythagoras) & [2](#eq:einstein) 34 | (however short it is), have to be put into a span with an id: 35 | 36 | [$$a^2 + b^2 = c^2$$ (1)]{#eq:pythagoras} 37 | 38 | They can also be inline: [$$e=mc^2$$ (2)]{#eq:einstein}. 39 | 40 | Most people prefer Equation [1](#eq:pythagoras), but know 41 | Equation [2](#eq:einstein). 42 | 43 | ## Tables 44 | 45 | Foo Bar 46 | ----- ----- 47 | 1 2 48 | 49 | : [Table 1: ]{#tab:foobar}Caption 50 | 51 | # Another chapter 52 | 53 | References to Section [1.1](#equationchapter) and the unnamed 54 | Section [2](#another-chapter) 55 | 56 | Text at end. 57 | -------------------------------------------------------------------------------- /expected/refnos.tex: -------------------------------------------------------------------------------- 1 | \section{Chapter 1}\label{chapter-1} 2 | 3 | In-text references to Figure \ref{fig:1}B and Table \ref{tab:foobar} 4 | results in \textbf{Figure 1B and Table 1}. 5 | 6 | Multi-reference to Figures \ref{fig:1} (A), the cool part of 7 | \ref{fig:1} \& \ref{fig:three} and Tables \ref{tab:foobar} (very 8 | cool) \& \emph{especially} \ref{tab:foobar} is rendered as 9 | \textbf{Figures 1 (A), the cool part of 1 \& 2 and Tables 1 (verycool) 10 | \& \emph{especially} 1} 11 | 12 | \begin{figure} 13 | \centering 14 | \includegraphics[width=1in,height=\textheight]{img/fig-1.png} 15 | \caption{Figure 1: The number one.}\label{fig:1} 16 | \end{figure} 17 | 18 | \begin{figure} 19 | \centering 20 | \includegraphics[width=1in,height=\textheight]{img/fig-1.png} 21 | \caption{Figure 2: The unlabeled number two.}\label{fig:} 22 | \end{figure} 23 | 24 | \begin{figure} 25 | \centering 26 | \includegraphics[width=1in,height=\textheight]{img/fig-1.png} 27 | \caption{Figure 3: The number three.}\label{fig:three} 28 | \end{figure} 29 | 30 | Plot \ref{fig:three} is given above, without adding the ``Figure'' 31 | prefix. 32 | 33 | As seen in Table \ref{tab:foobar}, commas are handled properly. 34 | 35 | \subsection{Equations}\label{equationchapter} 36 | 37 | Equations, such as Equations \ref{eq:pythagoras} \& \ref{eq:einstein} 38 | (however short it is), have to be put into a span with an id: 39 | 40 | \phantomsection\label{eq:pythagoras}{\[a^2 + b^2 = c^2\]} 41 | 42 | They can also be inline: \phantomsection\label{eq:einstein}{\[e=mc^2\]}. 43 | 44 | Most people prefer Equation \ref{eq:pythagoras}, but know 45 | Equation \ref{eq:einstein}. 46 | 47 | \subsection{Tables}\label{tables} 48 | 49 | \begin{longtable}[]{@{}ll@{}} 50 | \caption{Caption}\label{tab:foobar}\tabularnewline 51 | \toprule\noalign{} 52 | Foo & Bar \\ 53 | \midrule\noalign{} 54 | \endfirsthead 55 | \toprule\noalign{} 56 | Foo & Bar \\ 57 | \midrule\noalign{} 58 | \endhead 59 | \bottomrule\noalign{} 60 | \endlastfoot 61 | 1 & 2 \\ 62 | \end{longtable} 63 | 64 | \section{Another chapter}\label{another-chapter} 65 | 66 | References to Section \ref{equationchapter} and the unnamed 67 | Section \ref{another-chapter} 68 | 69 | Text at end. 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # luarefnos 2 | 3 | Pure lua implementation of table, figure, section and equation 4 | cross-references. 5 | 6 | ## Sample usage 7 | 8 | ``` markdown 9 | # Chapter 1 10 | 11 | In-text references to @fig:1 [B] and @tab:foobar. 12 | 13 | Multi-reference to [@fig:1 (A); the cool part of @fig:1; @fig:three] 14 | and [@tab:foobar (very cool); *especially* @tab:foobar]. 15 | 16 | ![The number one.](img/fig-1.png){#fig:1 width=1in} 17 | 18 | ![The unlabeled number two.](img/fig-1.png){#fig: width=1in} 19 | 20 | ![The number three.](img/fig-1.png){#fig:three width=1in} 21 | 22 | Plot [-@fig:three] is given above, without adding the "Figure" prefix. 23 | 24 | As seen in @tab:foobar, commas are handled properly. 25 | 26 | ## Equations {#equationchapter} 27 | 28 | Equations, such as @eq:pythagoras, have to be put into a span with an id: 29 | 30 | [$$a^2 + b^2 = c^2$$]{#eq:pythagoras} 31 | 32 | ## Tables 33 | 34 | Foo Bar 35 | --- --- 36 | 1 2 37 | 38 | Table: With caption string {#tab:foobar border=1 attr2=foo} 39 | ``` 40 | 41 | ## Sample output 42 | 43 | ### Chapter 1 44 | 45 | In-text references to Figure [1](#fig:1)B and Table [1](#tab:foobar). 46 | 47 | Multi-reference to Figures [1](#fig:1) (A), the cool part of [1](#fig:1) 48 | & [3](#fig:three) and Tables [1](#tab:foobar) (very cool) & *especially* 49 | [1](#tab:foobar). 50 | 51 |
52 | Figure 1: The number one. 53 |
54 | 55 |
56 | Figure 2: The unlabeled number two. 57 |
58 | 59 |
60 | Figure 3: The number three. 61 |
62 | 63 | Plot [3](#fig:three) is given above, without adding the “Figure” prefix. 64 | 65 | As seen in Table [1](#tab:foobar), commas are handled properly. 66 | 67 | #### Equations 68 | 69 | Equations, such as Equation [1](#eq:pythagoras), have to be put into a 70 | span with an id: 71 | 72 | 73 | *a*2 + *b*2 = *c*2 74 | 75 | 76 | #### Tables 77 | 78 | | Foo | Bar | 79 | |:----|:----| 80 | | 1 | 2 | 81 | 82 | Table 1:With caption string 83 | 84 | # What about pandoc-xnos 85 | 86 | | Feature | luarefnos | pandoc-xnos | 87 | |:-----------------------|:---------:|:-------------------:| 88 | | Figure references | ✔ | ✔ | 89 | | Subfigures | (✗) | ✔ | 90 | | Table references | ✔ | ✔ | 91 | | Equation references | ✔ | ✔ | 92 | | Section references | (✔) | ✔ | 93 | | Clever references | ✗ | ✔ | 94 | | Customization | ✗ | ✔ | 95 | | Maturity | 👶 | ✔ | 96 | | Corner cases | Many | Probably none | 97 | | Installation | One file | Python + 3 packages | 98 | | Native docx references | ✔ | ✗ | 99 | 100 | # Usage 101 | 102 | Before anything can be referenced, it has to have an ID. 103 | 104 | ## Figures 105 | 106 | The native attribute syntax is reused: 107 | 108 | ``` markdown 109 | ![Figure caption here](fig.png){#fig:example} 110 | ``` 111 | 112 | ## Tables 113 | 114 | A (not yet) native attribute block is at the end of the caption is 115 | applied to the table: 116 | 117 | ``` markdown 118 | Col A Col B 119 | ----- ----- 120 | 1 2 121 | 122 | : A table caption {#tab:example} 123 | ``` 124 | 125 | ## Equations 126 | 127 | Display math in a span with an ID is counted: 128 | 129 | ``` markdown 130 | [$$a^2 + b^2 = c^2$$]{#eq:pythagoras} 131 | ``` 132 | 133 | ## Sections 134 | 135 | Section IDs are re-used, the `sec:` is added automatically! 136 | 137 | ``` markdown 138 | # Section A 139 | 140 | The section below has the number @sec:someotherid 141 | 142 | # Section B {#someotherid} 143 | ``` 144 | 145 | ## Referencing 146 | 147 | Citations with a namespace (e.g. `sec:`, `fig:` etc.) are replaced with 148 | the reference: 149 | 150 | ``` markdown 151 | We reference @tab:example and @fig:example. 152 | ``` 153 | -------------------------------------------------------------------------------- /refnos.lua: -------------------------------------------------------------------------------- 1 | local pandoc = require 'pandoc' 2 | ---@type table 3 | local Counter = {} -- counter for each namespace 4 | ---@type table 5 | local Targets = {} -- maps reference IDs to reference numbers, e.g. {@fig:A=1, @fig:B=2, @tab:A=1} 6 | ---@type table> 7 | local Cap = {} -- localized caption strings 8 | 9 | 10 | local debugmsg = function(logstr) end 11 | -- local debugmsg = print 12 | local warnmsg = print 13 | 14 | --- returns the namespace portion from an id (i.e. 'fig' in 'fig:foobar') or nil 15 | local function getnamespace(id) 16 | if id == nil then return nil end 17 | local colonidx = id:find(':') 18 | if colonidx ~= nil then 19 | return id:sub(1, colonidx - 1) 20 | else 21 | return nil 22 | end 23 | end 24 | 25 | --- to be overridden for some formats; escapes the reference ID if needed 26 | ---@param id string 27 | local function EscapeId(id) 28 | return id 29 | end 30 | 31 | local function Link(text, id) 32 | return pandoc.Link(text, '#' .. id) 33 | end 34 | 35 | --- increments the counter for a namespace, saves and returns the index of refid 36 | ---@param namespace string 37 | ---@param refid string 38 | ---@return integer 39 | local function storeRef(namespace, refid) 40 | local refno = (Counter[namespace] or 0) + 1 41 | 42 | debugmsg(' Adding ref to ' .. namespace .. ' -> ' .. refid .. '(#' .. refno .. ')') 43 | Counter[namespace] = refno 44 | if refid ~= nil then Targets[refid] = refno end 45 | return refno 46 | end 47 | 48 | local function getRefLink(namespace, refid) 49 | local t = Targets[refid] 50 | if refid:sub(1,4) == 'sec:' then 51 | refid = refid:sub(5) 52 | end 53 | if t == nil then 54 | warnmsg('Missing reference: (' .. namespace .. '): ' .. refid) 55 | return pandoc.Strong(refid .. '?') 56 | else 57 | return Link(tostring(t), EscapeId(refid)) 58 | end 59 | end 60 | 61 | --- prepends "Figure XY: " to an image caption 62 | local function InsertNumInImgCaption(caption, namespace, refno, identifier) 63 | if caption.long ~= nil then 64 | caption = caption.long[1].content 65 | end 66 | caption:insert(1, pandoc.Str(Cap[namespace].ref .. ' ' .. refno .. ': ')) 67 | end 68 | 69 | --- prepends "Table XY: " to a table caption 70 | function InsertNumInTabCaption(caption, namespace, refno, identifier) 71 | caption.long[1].content:insert(1, pandoc.Str(Cap[namespace].ref .. ' ' .. refno .. ': ')) 72 | end 73 | 74 | --- appends "(XY)" to a span containing an equation 75 | function InsertNumInEqCaption(caption, namespace, refno, identifier) 76 | caption.content:insert(pandoc.Str(' (' .. refno .. ')')) 77 | end 78 | 79 | --- formats and returns a reference, optionally with prefix (Table XY) 80 | function InsertRef(id, namespace, withprefix) 81 | local reflink = getRefLink(namespace, id) 82 | if withprefix then 83 | return {pandoc.Str(Cap[namespace].ref .. ' '), reflink} 84 | end 85 | return {reflink} 86 | end 87 | 88 | --- format specific overrides, e.g. for native reference handling 89 | if FORMAT == 'latex' then 90 | local function label(id) 91 | return pandoc.RawInline('latex', '\\label{' .. EscapeId(id) .. '}') 92 | end 93 | 94 | function InsertNumInTabCaption(caption, namespace, refno, identifier) 95 | -- no longer needed since pandoc 3 96 | -- caption.long[1].content:insert(label(identifier)) 97 | end 98 | 99 | --- replaces the markdown equation with a native latex equation + prepended \\label 100 | function InsertNumInEqCaption(span, namespace, refno, identifier) 101 | local eq = span.content[1] 102 | local el = label(identifier) 103 | el.text = '$$' .. el.text .. eq.text .. '$$' 104 | return el 105 | end 106 | 107 | function getRefLink(namespace, refid) 108 | if refid:sub(1,4) == 'sec:' then 109 | refid = refid:sub(5) 110 | end 111 | return pandoc.RawInline('latex', '\\ref{' .. refid ..'}') 112 | end 113 | 114 | elseif FORMAT == 'docx' then 115 | function EscapeId(id) 116 | if id:sub(1,4) == 'sec:' then 117 | return id:sub(5) 118 | end 119 | return 'refnos_' .. id 120 | end 121 | local function DocxFieldFunction(instr, text, hlink) 122 | local xml = '' .. text .. '' 123 | if hlink ~= nil then 124 | -- return '' .. xml .. '' 125 | xml = ''.. xml ..'' 126 | end 127 | return pandoc.RawInline('openxml', xml) 128 | end 129 | 130 | function InsertRef(id, namespace, withprefix) 131 | local t = Targets[id] 132 | if t == nil then 133 | warnmsg(' Missing reference: ' .. namespace .. ' -> ' .. id) 134 | return {pandoc.Strong(id .. '??')} 135 | end 136 | id = EscapeId(id) 137 | 138 | -- not yet usable 139 | if namespace == 'sec' then 140 | local field = DocxFieldFunction(' REF ' .. id .. ' \\w \\h ', t) 141 | if withprefix then 142 | return {pandoc.Str(Cap.sec.ref), field} 143 | else 144 | return {field} 145 | end 146 | end 147 | 148 | -- full fldChar XML: 149 | -- local xml = '' .. 150 | -- ' REF ' .. EscapeId(id) .. ' \\h' .. 151 | -- ''.. 152 | -- '' t .. '' .. 153 | -- '' 154 | -- local xml = ''..prefix .. ' ' .. t .. '' 155 | if withprefix then 156 | return {DocxFieldFunction(' REF ' .. id .. ' \\h', Cap[namespace].ref .. ' ' .. t)} 157 | end 158 | 159 | return {DocxFieldFunction(' SEQ ' .. Cap[namespace].ref .. ' ' .. id .. ' \\c', t, nil)} 160 | end 161 | 162 | function InsertNumInCaption(caption, namespace, refno, identifier) 163 | -- generate hopefully unique bookmark ID 164 | local span = pandoc.Span(Cap[namespace].ref .. ' ') 165 | span.attr.identifier = EscapeId(identifier) 166 | span.content:insert(DocxFieldFunction(' SEQ ' .. Cap[namespace].ref .. ' \\* ARABIC ', refno)) 167 | -- span.content:insert(pandoc.Str(': ')) 168 | caption:insert(1, span) 169 | caption:insert(2, pandoc.Str(': ')) 170 | end 171 | function InsertNumInTabCaption(caption, namespace, refno, identifier) 172 | InsertNumInCaption(caption.long[1].content, namespace, refno, identifier) 173 | end 174 | function InsertNumInImgCaption(caption, namespace, refno, identifier) 175 | if caption.long ~= nil then 176 | caption = caption.long[1].content 177 | end 178 | 179 | InsertNumInCaption(caption, namespace, refno, identifier) 180 | end 181 | function InsertNumInEqCaption(eq_span, namespace, refno, identifier) 182 | local span = pandoc.Span(DocxFieldFunction(' SEQ ' .. Cap[namespace].ref .. ' \\* ARABIC ', refno)) 183 | span.attr.identifier = EscapeId(identifier) 184 | 185 | local txt = eq_span.content 186 | txt:insert(pandoc.Str(' (')) 187 | txt:insert(pandoc.Span(span)) 188 | txt:insert(pandoc.Str(')')) 189 | end 190 | 191 | elseif FORMAT == 'markdown' then 192 | function InsertNumInTabCaption(caption, namespace, refno, identifier) 193 | local span = pandoc.Span(Cap[namespace].ref .. ' ' .. refno .. ': ') 194 | span.attr.identifier = EscapeId(identifier) 195 | caption.long[1].content:insert(1, span) 196 | end 197 | end 198 | 199 | -- parses and removes an ID and attributes like 200 | -- This is a caption {#id attr1=val1 foobar=baz} 201 | -- into "This is a caption", {attr1="val1", foobar="baz"} 202 | function GetAttrsFromCaption(e) 203 | if e.caption == nil or #e.caption.long == 0 then return e, nil end 204 | 205 | local cap = e.caption.long[1] 206 | local attrstr = (' '..pandoc.utils.stringify(cap)):match(' ({.*})$') 207 | if attrstr == nil then return e, nil end 208 | 209 | -- reuse pandocs span attribute parser 210 | local attrs = pandoc.read('[text]' .. attrstr).blocks[1].content[1].attr 211 | 212 | -- remove string from caption, starting at the end 213 | local i = #cap.content 214 | while i > 1 do 215 | local el = cap.content:remove(i) 216 | if el.tag == 'Str' and el.text:sub(1, 1) == '{' then break end 217 | i = i - 1 218 | end 219 | -- also remove preceding space 220 | if #cap.content > 0 and cap.content[#cap.content].t == 'Space' then 221 | cap.content:remove(#cap.content) 222 | end 223 | 224 | -- remove strings from caption 225 | while i <= #cap.content do cap.content:remove(#cap.content) end 226 | 227 | return e, attrs 228 | end 229 | 230 | --- Parse ID, class and attributes in the caption 231 | -- Markdown has no native syntax for table attributes (unlike images), so 232 | -- this function parses an attribute string at the end of the caption 233 | local function MoveCaptionAttrsToTable(tab) 234 | local tbl, attr = GetAttrsFromCaption(tab) 235 | if attr ~= nil then 236 | tbl.attr.identifier = attr.identifier 237 | tbl.attr.classes:extend(attr.classes) 238 | for k, v in pairs(attr.attributes) do tbl.attr.attributes[k] = v end 239 | debugmsg('Extracted table id ' .. attr.identifier) 240 | return tbl 241 | end 242 | end 243 | 244 | local function HandleTable(tbl) 245 | local id = tbl.attr.identifier 246 | if id ~= '' then 247 | local ns = getnamespace(id) 248 | local refno = storeRef(ns, tbl.attr.identifier) 249 | InsertNumInTabCaption(tbl.caption, ns, refno, tbl.attr.identifier) 250 | return tbl 251 | end 252 | end 253 | 254 | local function HandleFigure(fig) 255 | local id = fig.identifier 256 | if id:sub(1, 4) == 'fig:' then 257 | local figno = storeRef('fig', id) 258 | if fig.caption ~= nil and fig.caption.long ~= nil then 259 | InsertNumInImgCaption(fig.caption, 'fig', figno, id) 260 | return fig 261 | end 262 | end 263 | end 264 | 265 | local function HandleImage(img) 266 | local id = img.identifier 267 | if id:sub(1, 4) == 'fig:' then 268 | local figno = storeRef('fig', id) 269 | 270 | if img.caption ~= nil then 271 | InsertNumInImgCaption(img.caption, 'fig', figno, id) 272 | return img 273 | end 274 | end 275 | end 276 | 277 | local function HandleEquationInSpan(sp) 278 | if sp.content[1].t ~= 'Math' or sp.content[1].mathtype ~= 'DisplayMath' then 279 | return 280 | end 281 | local id = sp.attr.identifier 282 | local ns = getnamespace(id) 283 | if ns ~= nil then 284 | local refno = storeRef(ns, id) 285 | InsertNumInEqCaption(sp, ns, refno, id) 286 | return sp 287 | end 288 | end 289 | 290 | CurChapter = {0, 0, 0, 0, 0, 0, 0, 0, 0} 291 | local function HandleHeader(h) 292 | CurChapter[h.level] = CurChapter[h.level] + 1 293 | for i = h.level + 1, 9 do 294 | CurChapter[i] = 0 295 | end 296 | local chapterNumber = CurChapter[1] 297 | for i = 2, #CurChapter do 298 | if CurChapter[i] == 0 then break end 299 | chapterNumber = chapterNumber .. '.' .. CurChapter[i] 300 | end 301 | if h.identifier then 302 | Targets['sec:' .. h.identifier] = chapterNumber 303 | end 304 | end 305 | 306 | local function HandleCitation(c) 307 | local namespace = getnamespace(c.citations[1].id) 308 | if namespace == nil then return end 309 | 310 | local res = pandoc.List() 311 | -- special case: AuthorInText, e.g. "As seen in @tab:foobar, …" 312 | if #c.citations == 1 and c.citations[1].mode == pandoc.AuthorInText then 313 | local ct = c.citations[1] 314 | res:extend(InsertRef(ct.id, namespace, true)) 315 | res:extend(ct.suffix) 316 | return res 317 | end 318 | 319 | if c.citations[1].mode ~= pandoc.SuppressAuthor then 320 | if #c.citations == 1 then 321 | res:insert(pandoc.Str(Cap[namespace].ref .. ' ')) 322 | else 323 | res:insert(pandoc.Str(Cap[namespace].plural .. ' ')) 324 | end 325 | end 326 | 327 | for i, ct in ipairs(c.citations) do 328 | if namespace ~= getnamespace(ct.id) then 329 | warnmsg('Found mixed references in citation' .. c.content) 330 | res:insert(pandoc.Strong('… unprocessed citations')) 331 | return res 332 | end 333 | 334 | if i == 1 then 335 | -- nothing to do 336 | elseif i == #c.citations then 337 | res:insert(pandoc.Str(' & ')) 338 | else 339 | res:insert(pandoc.Str(', ')) 340 | end 341 | if #ct.prefix > 0 then 342 | res:extend(ct.prefix) 343 | res:insert(pandoc.Space()) 344 | end 345 | res:extend(InsertRef(ct.id, namespace, false)) 346 | res:extend(ct.suffix) 347 | end 348 | return res 349 | end 350 | 351 | local function MetaToStr(m) 352 | if m == nil then 353 | return nil 354 | elseif type(m) == 'string' then 355 | return m 356 | elseif m.t == 'MetaInlines' or m.t == 'Str' then 357 | return pandoc.utils.stringify(m) 358 | elseif m.t == 'MetaString' then 359 | return m.str 360 | elseif type(m) == 'table' then 361 | return MetaToStr(m[1]) 362 | else 363 | warnmsg('unhandled type ' .. type(m)) 364 | warnmsg(m.t) 365 | return nil 366 | end 367 | end 368 | 369 | function SetStrings(opts, lang) 370 | local Localized = { 371 | de = { 372 | tab = {ref = 'Tabelle', plural = 'Tabellen', abbrev = 'Tab.'}, 373 | fig = {ref = 'Abbildung', plural = 'Abbildungen', abbrev = 'Abb.'}, 374 | eq = {ref = 'Gleichung', plural = 'Gleichungen', abbrev = 'Gl.'}, 375 | sec = {ref = 'Abschnitt', plural = 'Abschnitte', abbrev = 'Abs.'} 376 | }, 377 | en = { 378 | tab = {ref = 'Table', plural = 'Tables', abbrev = 'Tab.'}, 379 | fig = {ref = 'Figure', plural = 'Figures', abbrev = 'Fig.'}, 380 | eq = {ref = 'Equation', plural = 'Equations', abbrev = 'Eq.'}, 381 | sec = {ref = 'Section', plural = 'Sections', abbrev = 'Sec.'} 382 | } 383 | } 384 | 385 | if opts.strs == nil or type(opts.strs) ~= 'table' then opts.strs = Localized end 386 | if opts.strs[lang] == nil then lang = 'en' end 387 | 388 | for namespace, strs in pairs(opts.strs[lang]) do 389 | for k, v in pairs(strs) do 390 | if k ~= 'ref' and k~='plural' and k~='abbrev' then 391 | warnmsg('Unknown key luarefnos.'..lang..'.'..namespace..'.'..k) 392 | end 393 | strs[k] = MetaToStr(v) 394 | end 395 | 396 | if strs.ref == nil then 397 | warnmsg('Missing "ref" in luarefnos.'..lang..'.'..namespace) 398 | strs.ref = '???' 399 | end 400 | if strs.plural == nil then 401 | strs.plural = strs.ref .. 's' 402 | end 403 | if strs.abbrev == nil then 404 | strs.abbrev = strs.ref:sub(1,3) 405 | end 406 | end 407 | Cap = opts.strs[lang] 408 | end 409 | 410 | function Init(m) 411 | local opts = m.luarefnos 412 | -- if opts == nil then return end 413 | 414 | local lang = MetaToStr(m.lang) or 'en' 415 | 416 | SetStrings(m.luarefnos or {}, lang) 417 | end 418 | 419 | return {{Meta = Init}, 420 | {Table = MoveCaptionAttrsToTable}, 421 | { 422 | Header = HandleHeader, 423 | Table = HandleTable, 424 | Image = HandleImage, 425 | Figure = HandleFigure, 426 | Span = HandleEquationInSpan 427 | }, 428 | {Cite = HandleCitation}} 429 | --------------------------------------------------------------------------------