├── .gitignore ├── .travis.install_pandoc.sh ├── .travis.yml ├── README.md ├── autoref.md ├── image.png ├── internalreferences.py ├── ref.md ├── setup.py ├── spec.md └── tests ├── spec.html ├── spec.html5 ├── spec.json ├── spec.latex ├── spec.markdown └── tests.py /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | *.egg-info 4 | -------------------------------------------------------------------------------- /.travis.install_pandoc.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | mkdir ~/pandoc 4 | wget https://github.com/jgm/pandoc/releases/download/1.17.0.2/pandoc-1.17.0.2-1-amd64.deb 5 | dpkg --extract pandoc-1.17.0.2-1-amd64.deb ~/pandoc/1.17 6 | export PATH=~/pandoc/$PANDOC/usr/bin/:$PATH 7 | 8 | echo $PANDOC 9 | type -p pandoc 10 | pandoc --version 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | sudo: no 4 | 5 | python: 6 | - 2.7 7 | - 3.5 8 | 9 | env: 10 | global: 11 | - PEP=false 12 | matrix: 13 | - PANDOC=1.17 14 | 15 | matrix: 16 | include: 17 | - python: 2.7 18 | env: PEP=true 19 | 20 | before_install: 21 | - if ! $PEP; then source .travis.install_pandoc.sh; fi 22 | 23 | install: 24 | - pip install pep8 25 | - pip install . 26 | 27 | script: 28 | - if $PEP; then pep8 internalreferences.py; fi 29 | - if ! $PEP; then nosetests; fi 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This pandoc filter implements an internal reference manager for 2 | pandoc, making it possible to reference images and sections that 3 | have attribute tags. 4 | 5 | **This filter uses the same `@` syntax as citations**, with priority 6 | given to internal references if there is a key clash. 7 | 8 | This filter allows both `\ref` and `\autoref` referencing styles to 9 | be used, toggled with a metadata switch. 10 | 11 | Supported output formats are latex, html, html5 and markdown. The 12 | markdown output format can be used to convert 13 | markdown-with-figure-attributes into currently valid pandoc 14 | markdown. 15 | 16 | This allows you to write something like this: 17 | 18 | ```markdown 19 | Look at @fig:thing. 20 | 21 | ![some caption](image.png){#fig:thing} 22 | ``` 23 | 24 | and get this markdown: 25 | 26 | ```markdown 27 | Look at [Figure 1](#fig:thing). 28 | 29 |
30 | ![Figure 1: some caption](image.png) 31 | 32 |
33 | ``` 34 | 35 | this latex: 36 | 37 | ```latex 38 | Look at \autoref{fig:thing}. 39 | 40 | \begin{figure}[htbp] 41 | \centering 42 | \includegraphics{image.png} 43 | \caption{some caption} 44 | \label{fig:thing} 45 | \end{figure} 46 | ``` 47 | 48 | or this html: 49 | 50 | ```html 51 |

Look at Figure 1.

52 | 53 |
54 | some caption 55 |

Figure 1: some caption

56 |
57 | ``` 58 | 59 | For example input see the [spec] and for the output see [markdown], 60 | [html], [html5] or [latex]. 61 | 62 | [spec]: spec.md 63 | [markdown]: tests/spec.markdown 64 | [html]: tests/spec.html 65 | [html5]: tests/spec.html5 66 | [latex]: tests/spec.latex 67 | 68 | 69 | ### Usage: 70 | 71 | ```bash 72 | pandoc spec.md --filter internal-references.py --to latex 73 | ``` 74 | 75 | alternately you can install it and use the command line link: 76 | 77 | ```bash 78 | python setup.py install 79 | pandoc spec.md --filter internal-references --to latex 80 | ``` 81 | 82 | 83 | Requires [pandocfilters] and [pandoc]. 84 | 85 | [pandocfilters]: https://pypi.python.org/pypi/pandocfilters 86 | [pandoc]: http://johnmacfarlane.net/pandoc/ 87 | 88 | 89 | ### How it works: 90 | 91 | In order to manage references we need to maintain some internal 92 | state that tracks the objects that can be referenced in the 93 | document. This is implemented with the `ReferenceManager`. 94 | 95 | `pandocfilters` contains a function `toJSONFilter` that passes a 96 | given function over the entire document syntax tree and interfaces 97 | with pandoc via stdin/stdout. 98 | 99 | However, we need to walk the document tree twice, once to capture 100 | all of the objects (figures, sections, whatever) and again to change 101 | all of the internal links to the appropriate output. This requires a 102 | modified `toJSONFilter` that accepts a list of functions to pass the 103 | tree through sequentially. 104 | 105 | It is easy to determine the type of a reference object as we go 106 | along (whether figure, section or whatever) on the first pass and we 107 | can use this information to let us choose the right text for 108 | internal links on the second pass. This allows us to avoid being 109 | constrained to ids like '#fig:somefigure' to indicate a figure. 110 | 111 | 112 | ### TODO: 113 | 114 | - [ ] allow switching off figure / section text replacement (perhaps 115 | using document metadata as the switch) 116 | 117 | - [x] implement implicit internal reference link syntax for links to 118 | figures, i.e. `[](#ref) == [#ref]` 119 | 120 | 121 | ### Contributing: 122 | 123 | Very welcome. Make sure you add appropriate tests and use verbose 124 | commit messages and pull requests. Explain what you are trying to 125 | do in English so that I don't have to work it out through the code. 126 | -------------------------------------------------------------------------------- /autoref.md: -------------------------------------------------------------------------------- 1 | `@ref` | [Figure 1](#ref) 2 | 3 | `@ref(a)` | [Figure 1](#ref)(a) 4 | 5 | `[@ref]` | [Figure 1](#ref) 6 | 7 | `[@ref(a)]` | [Figure 1(a)](#ref) 8 | 9 | `@ref, @ref2` | [Figure 1](#ref), [Figure 2](#ref2) 10 | 11 | `[@ref; @ref2]` | Figures [1](#ref) and [2](#ref2) 12 | 13 | `[see @ref]` | [see Figure 1](#ref) 14 | 15 | `[see @ref(a)]`| [see Figure 1(a)](#ref) 16 | 17 | 18 | ![caption](){#ref} 19 | 20 | ![caption](){#ref2} 21 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aaren/pandoc-reference-filter/9c6ac72f54e1d314c06ed4849c74dd6325db827d/image.png -------------------------------------------------------------------------------- /internalreferences.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import re 3 | from collections import OrderedDict 4 | 5 | import pandocfilters as pf 6 | 7 | from pandocattributes import PandocAttributes 8 | 9 | 10 | def RawInline(format, string): 11 | """Overwrite pandocfilters RawInline so that html5 12 | and html raw output both use the html writer. 13 | """ 14 | if format == 'html5': 15 | format = 'html' 16 | return pf.RawInline(format, string) 17 | 18 | 19 | def RawBlock(format, string): 20 | """Overwrite pandocfilters RawBlock so that html5 21 | and html raw output both use the html writer. 22 | """ 23 | if format == 'html5': 24 | format = 'html' 25 | return pf.RawBlock(format, string) 26 | 27 | 28 | def isheader(key, value): 29 | return (key == 'Header') 30 | 31 | 32 | math_label = r'\\label{(.*?)}' 33 | 34 | def islabeledmath(key, value): 35 | return (key == 'Math' and re.search(math_label, value[1])) 36 | 37 | 38 | def isattr(string): 39 | return string.startswith('{') and string.endswith('}') 40 | 41 | 42 | # define a new Figure type - an image with attributes 43 | Figure = pf.elt('Figure', 3) # caption, target, attrs 44 | 45 | 46 | def isfigure(key, value): 47 | return (key == 'Para' and len(value) == 2 and value[0]['t'] == 'Image') 48 | 49 | 50 | def isattrfigure(key, value): 51 | return (key == 'Para' 52 | and value[0]['t'] == 'Image' 53 | and isattr(pf.stringify(value[1:]))) 54 | 55 | 56 | def isdivfigure(key, value): 57 | """Matches images contained in a Div with 'figure' as a class.""" 58 | return (key == 'Div' and 'figure' in value[0][1]) 59 | 60 | 61 | def isFigure(key, value): 62 | return key == 'Figure' 63 | 64 | 65 | def create_pandoc_multilink(strings, refs): 66 | inlines = [[pf.Str(str(s))] for s in strings] 67 | targets = [(r, "") for r in refs] 68 | links = [pf.Link(inline, target) 69 | for inline, target in zip(inlines, targets)] 70 | 71 | return join_items(links) 72 | 73 | 74 | def create_latex_multilink(labels): 75 | links = ['\\ref{{{label}}}'.format(label=label) for label in labels] 76 | return join_items(links, call=str) 77 | 78 | 79 | def join_items(items, method='append', call=pf.Str): 80 | """Join the list of items together in the format 81 | 82 | 'item[0]' if len(items) == 1 83 | 'item[0] and item[1]' if len(items) == 2 84 | 'item[0], item[1] and item[2]' if len(items) == 3 85 | 86 | and so on. 87 | """ 88 | out = [] 89 | join_to_out = getattr(out, method) 90 | 91 | join_to_out(items[0]) 92 | 93 | if len(items) == 1: 94 | return out 95 | 96 | for item in items[1: -1]: 97 | out.append(call(', ')) 98 | join_to_out(item) 99 | 100 | out.append(call(' and ')) 101 | join_to_out(items[-1]) 102 | 103 | return out 104 | 105 | 106 | def create_figures(key, value, format, metadata): 107 | """Convert Images with attributes to Figures. 108 | 109 | Images are [caption, (filename, title)]. 110 | 111 | Figures are [caption, (filename, title), attrs]. 112 | 113 | This isn't a supported pandoc type, we just use it internally. 114 | """ 115 | if isattrfigure(key, value): 116 | image = value[0] 117 | attr = PandocAttributes(pf.stringify(value[1:]), 'markdown') 118 | caption, target = image['c'] 119 | return Figure(caption, target, attr.to_pandoc()) 120 | 121 | elif isdivfigure(key, value): 122 | # use the first image inside 123 | attr, blocks = value 124 | images = [b['c'][0] for b in blocks if b['c'][0]['t'] == 'Image'] 125 | image = images[0] 126 | caption, target = image['c'] 127 | return Figure(caption, target, attr) 128 | 129 | else: 130 | return None 131 | 132 | 133 | class ReferenceManager(object): 134 | """Internal reference manager. 135 | 136 | Stores all referencable objects in the document, with a label 137 | and a type, then allows us to look up the object and type using 138 | a label. 139 | 140 | This means that we can determine the appropriate replacement 141 | text of any given internal reference (no need for e.g. 'fig:' at 142 | the start of labels). 143 | """ 144 | figure_styles = { 145 | 'latex': (u'\n' 146 | '\\begin{{figure}}[htbp]\n' 147 | '\\centering\n' 148 | '\\includegraphics{{{filename}}}\n' 149 | '\\caption{star}{{{caption}}}\n' 150 | '\\label{{{attr.id}}}\n' 151 | '\\end{{figure}}\n'), 152 | 153 | 'html': (u'\n' 154 | '
\n' 155 | '{alt}' 156 | '

{fcaption}

\n' 157 | '
\n'), 158 | 159 | 'html5': (u'\n' 160 | '
\n' 161 | '{alt}\n' 162 | '
{fcaption}
\n' 163 | '
\n'), 164 | 165 | 'markdown': (u'\n' 166 | '
\n' 167 | '![{fcaption}]({filename})\n' 168 | '\n' 169 | '
\n')} 170 | 171 | latex_multi_autolink = u'\\cref{{{labels}}}{post}' 172 | 173 | section_count = [0, 0, 0, 0, 0, 0] 174 | figure_count = 0 175 | fig_replacement_count = 0 176 | auto_fig_id = '___fig___[{}]'.format 177 | equation_count = 0 178 | references = {} 179 | 180 | formats = ('html', 'html5', 'markdown', 'latex') 181 | 182 | def __init__(self, autoref=True): 183 | if autoref: 184 | self.replacements = {'figure': 'Figure {}', 185 | 'section': 'Section {}', 186 | 'math': 'Equation {}'} 187 | 188 | self.multi_replacements = {'figure': 'Figures ', 189 | 'section': 'Sections ', 190 | 'math': 'Equations '} 191 | elif not autoref: 192 | self.replacements = {'figure': '{}', 193 | 'section': '{}', 194 | 'math': '{}'} 195 | 196 | self.multi_replacements = {'figure': '', 197 | 'section': '', 198 | 'math': ''} 199 | 200 | self.autoref = autoref 201 | 202 | def increment_section_count(self, header_level): 203 | """Changing the section count is dependent on the header level. 204 | 205 | When we add to the section count, we want to reset the 206 | count for all headers at a higher header level than that 207 | given, increment the count at the header level, and leave 208 | the same all lower levels. 209 | """ 210 | self.section_count[header_level - 1] += 1 211 | for i, _ in enumerate(self.section_count[header_level:]): 212 | self.section_count[header_level + i] = 0 213 | 214 | def format_section_count(self, header_level): 215 | """Format the section count for a given header level, 216 | leaving off info from higher header levels, 217 | 218 | e.g. section_count = [1, 2, 4, 3] 219 | format_section_count(3) == '1.2.4' 220 | """ 221 | return '.'.join(str(i) for i in self.section_count[:header_level]) 222 | 223 | def consume_references(self, key, value, format, metadata): 224 | """Find all figures, sections and math in the document 225 | and append reference information to the reference state. 226 | """ 227 | if isFigure(key, value): 228 | self.consume_figure(key, value, format, metadata) 229 | elif isheader(key, value): 230 | self.consume_section(key, value, format, metadata) 231 | elif islabeledmath(key, value): 232 | self.consume_math(key, value, format, metadata) 233 | 234 | def replace_references(self, key, value, format, metadata): 235 | """Find all figures, sections and equations that can be 236 | referenced in the document and replace them with format 237 | appropriate substitutions. 238 | """ 239 | if isFigure(key, value): 240 | return self.figure_replacement(key, value, format, metadata) 241 | elif isheader(key, value): 242 | return self.section_replacement(key, value, format, metadata) 243 | elif islabeledmath(key, value): 244 | return self.math_replacement(key, value, format, metadata) 245 | 246 | def consume_figure(self, key, value, format, metadata): 247 | """If the key, value represents a figure, append reference 248 | data to internal state. 249 | """ 250 | _caption, (filename, target), (id, classes, kvs) = value 251 | if 'unnumbered' in classes: 252 | return 253 | else: 254 | self.figure_count += 1 255 | id = id or self.auto_fig_id(self.figure_count) 256 | self.references[id] = {'type': 'figure', 257 | 'id': self.figure_count, 258 | 'label': id} 259 | 260 | def consume_section(self, key, value, format, metadata): 261 | """If the key, value represents a section, append reference 262 | data to internal state. 263 | """ 264 | level, attr, text = value 265 | label, classes, kvs = attr 266 | 267 | if 'unnumbered' in classes: 268 | return 269 | else: 270 | self.increment_section_count(level) 271 | secn = self.format_section_count(level) 272 | self.references[label] = {'type': 'section', 273 | 'id': secn, 274 | 'label': label} 275 | 276 | def consume_math(self, key, value, format, metadata): 277 | """If the key, value represents math, append reference 278 | data to internal state. 279 | """ 280 | self.equation_count += 1 281 | mathtype, math = value 282 | label, = re.search(math_label, math).groups() 283 | self.references[label] = {'type': 'math', 284 | 'id': self.equation_count, 285 | 'label': label} 286 | 287 | def figure_replacement(self, key, value, format, metadata): 288 | """Replace figures with appropriate representation. 289 | 290 | This works with Figure, which is our special type for images 291 | with attributes. This allows us to set an id in the attributes. 292 | 293 | The other way of doing it would be to pull out a '\label{(.*)}' 294 | from the caption of an Image and use that to update the references. 295 | """ 296 | _caption, (filename, target), attrs = value 297 | caption = pf.stringify(_caption) 298 | 299 | attr = PandocAttributes(attrs) 300 | 301 | if 'unnumbered' in attr.classes: 302 | star = '*' 303 | fcaption = caption 304 | else: 305 | self.fig_replacement_count += 1 306 | if not attr.id: 307 | attr.id = self.auto_fig_id(self.fig_replacement_count) 308 | 309 | ref = self.references[attr.id] 310 | star = '' 311 | if caption: 312 | fcaption = u'Figure {n}: {caption}'.format(n=ref['id'], 313 | caption=caption) 314 | else: 315 | fcaption = u'Figure {n}'.format(n=ref['id']) 316 | 317 | if 'figure' not in attr.classes: 318 | attr.classes.insert(0, 'figure') 319 | 320 | if format in self.formats: 321 | figure = self.figure_styles[format].format(attr=attr, 322 | filename=filename, 323 | alt=fcaption, 324 | fcaption=fcaption, 325 | caption=caption, 326 | star=star).encode('utf-8') 327 | 328 | return RawBlock(format, figure) 329 | 330 | else: 331 | alt = [pf.Str(fcaption)] 332 | target = (filename, '') 333 | image = pf.Image(alt, target) 334 | figure = pf.Para([image]) 335 | return pf.Div(attr.to_pandoc(), [figure]) 336 | 337 | def section_replacement(self, key, value, format, metadata): 338 | """Replace sections with appropriate representation. 339 | """ 340 | level, attr, text = value 341 | label, classes, kvs = attr 342 | 343 | if 'unnumbered' in classes: 344 | pretext = '' 345 | else: 346 | ref = self.references[label] 347 | pretext = '{}: '.format(ref['id']) 348 | 349 | pretext = [pf.Str(pretext)] 350 | 351 | if format in ('html', 'html5', 'markdown'): 352 | return pf.Header(level, attr, pretext + text) 353 | 354 | elif format == 'latex': 355 | # have to do this to get rid of hyperref 356 | return pf.Header(level, attr, text) 357 | 358 | else: 359 | return pf.Header(level, attr, pretext + text) 360 | 361 | def math_replacement(self, key, value, format, metadata): 362 | """Create our own links to equations instead of relying on 363 | mathjax. 364 | 365 | http://meta.math.stackexchange.com/questions/3764/equation-and-equation-is-the-same-for-me 366 | """ 367 | mathtype, math = value 368 | label = re.findall(math_label, math)[-1] 369 | 370 | attr = PandocAttributes() 371 | attr.id = '#' + label 372 | 373 | if format == 'latex': 374 | return pf.Math(mathtype, math) 375 | 376 | else: 377 | return pf.Span(attr.to_pandoc(), [pf.Math(mathtype, math)]) 378 | 379 | def convert_internal_refs(self, key, value, format, metadata): 380 | """Convert all internal links from '#blah' into format 381 | specified in self.replacements. 382 | """ 383 | if key != 'Cite': 384 | return None 385 | 386 | citations, inlines = value 387 | 388 | if len(citations) > 1: 389 | ''' 390 | Note: Need to check that *all* of the citations in a 391 | multicitation are in the reference list. If not, the citation 392 | is bibliographic, and we want LaTeX to handle it, so just 393 | return unmodified. 394 | ''' 395 | for citation in citations: 396 | if citation['citationId'] not in self.references: return 397 | return self.convert_multiref(key, value, format, metadata) 398 | 399 | else: 400 | citation = citations[0] 401 | 402 | prefix = pf.stringify(citation['citationPrefix']) 403 | suffix = pf.stringify(citation['citationSuffix']) 404 | 405 | if prefix: 406 | prefix += ' ' 407 | 408 | label = citation['citationId'] 409 | 410 | if label not in self.references: 411 | return 412 | 413 | rtype = self.references[label]['type'] 414 | n = self.references[label]['id'] 415 | text = self.replacements[rtype].format(n) 416 | 417 | if format == 'latex' and self.autoref: 418 | link = u'{pre}\\autoref{{{label}}}{post}'.format(pre=prefix, 419 | label=label, 420 | post=suffix) 421 | return pf.RawInline('latex', link) 422 | 423 | elif format == 'latex' and not self.autoref: 424 | link = u'{pre}\\ref{{{label}}}{post}'.format(pre=prefix, 425 | label=label, 426 | post=suffix) 427 | return pf.RawInline('latex', link) 428 | 429 | else: 430 | link_text = '{}{}{}'.format(prefix, text, suffix) 431 | link = pf.Link([pf.Str(link_text)], ('#' + label, '')) 432 | return link 433 | 434 | def convert_multiref(self, key, value, format, metadata): 435 | """Convert all internal links from '#blah' into format 436 | specified in self.replacements. 437 | """ 438 | citations, inlines = value 439 | 440 | labels = [citation['citationId'] for citation in citations] 441 | 442 | if format == 'latex' and self.autoref: 443 | link = self.latex_multi_autolink.format(pre='', 444 | post='', 445 | labels=','.join(labels)) 446 | return RawInline('latex', link) 447 | 448 | elif format == 'latex' and not self.autoref: 449 | link = ''.join(create_latex_multilink(labels)) 450 | return RawInline('latex', link) 451 | 452 | else: 453 | D = [self.references[label] for label in labels] 454 | # uniquely ordered types 455 | types = list(OrderedDict.fromkeys(d['type'] for d in D)) 456 | 457 | links = [] 458 | 459 | for t in set(types): 460 | n = [d['id'] for d in D if d['type'] == t] 461 | labels = ['#' + d['label'] for d in D if d['type'] == t] 462 | multi_link = create_pandoc_multilink(n, labels) 463 | 464 | if len(labels) == 1: 465 | multi_link.insert(0, 466 | pf.Str(self.replacements[t].format(''))) 467 | else: 468 | multi_link.insert(0, pf.Str(self.multi_replacements[t])) 469 | 470 | links.append(multi_link) 471 | 472 | return join_items(links, method='extend') 473 | 474 | @property 475 | def reference_filter(self): 476 | return [create_figures, 477 | self.consume_references, 478 | self.replace_references, 479 | self.convert_internal_refs] 480 | 481 | 482 | def toJSONFilter(actions): 483 | """Modified from pandocfilters to accept a list of actions (to 484 | apply in series) as well as a single action. 485 | 486 | Converts an action into a filter that reads a JSON-formatted 487 | pandoc document from stdin, transforms it by walking the tree 488 | with the action, and returns a new JSON-formatted pandoc document 489 | to stdout. 490 | 491 | The argument is a function action(key, value, format, meta), 492 | where key is the type of the pandoc object (e.g. 'Str', 'Para'), 493 | value is the contents of the object (e.g. a string for 'Str', 494 | a list of inline elements for 'Para'), format is the target 495 | output format (which will be taken for the first command line 496 | argument if present), and meta is the document's metadata. 497 | 498 | If the function returns None, the object to which it applies 499 | will remain unchanged. If it returns an object, the object will 500 | be replaced. If it returns a list, the list will be spliced in to 501 | the list to which the target object belongs. (So, returning an 502 | empty list deletes the object.) 503 | """ 504 | doc = pf.json.loads(pf.sys.stdin.read()) 505 | if len(pf.sys.argv) > 1: 506 | format = pf.sys.argv[1] 507 | else: 508 | format = "" 509 | 510 | if type(actions) is type(toJSONFilter): 511 | altered = pf.walk(doc, actions, format, doc[0]['unMeta']) 512 | elif type(actions) is list: 513 | altered = doc 514 | for action in actions: 515 | altered = pf.walk(altered, action, format, doc[0]['unMeta']) 516 | 517 | pf.json.dump(altered, pf.sys.stdout) 518 | 519 | 520 | def main(): 521 | doc = pf.json.loads(pf.sys.stdin.read()) 522 | if len(pf.sys.argv) > 1: 523 | format = pf.sys.argv[1] 524 | else: 525 | format = "" 526 | 527 | metadata = doc[0]['unMeta'] 528 | args = {k: v['c'] for k, v in metadata.items()} 529 | autoref = args.get('autoref', True) 530 | 531 | refmanager = ReferenceManager(autoref=autoref) 532 | 533 | altered = doc 534 | for action in refmanager.reference_filter: 535 | altered = pf.walk(altered, action, format, metadata) 536 | 537 | pf.json.dump(altered, pf.sys.stdout) 538 | 539 | 540 | if __name__ == '__main__': 541 | main() 542 | -------------------------------------------------------------------------------- /ref.md: -------------------------------------------------------------------------------- 1 | `@ref` | @ref 2 | 3 | `@ref(a)` | @ref(a) 4 | 5 | `[@ref]` | [@ref] 6 | 7 | `[@ref(a)]` | [@ref(a)] 8 | 9 | `@ref; @ref2` | @ref; @ref2 10 | 11 | `[@ref; @ref2]` | [@ref; @ref2] 12 | 13 | `[figure @ref]` | [figure @ref] 14 | 15 | `[figure @ref(a)]`| [figure @ref(a)] 16 | 17 | 18 | ![caption](){#ref} 19 | 20 | ![caption](){#ref2} 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | 4 | setup( 5 | name="pandoc-internal-references", 6 | version='0.5.1', 7 | description="Image attributes and internal referencing in markdown", 8 | py_modules=['internalreferences'], 9 | author="Aaron O'Leary", 10 | author_email='dev@aaren.me', 11 | license='BSD 2-Clause', 12 | url='https://github.com/aaren/pandoc-reference-filter', 13 | install_requires=['pandocfilters', 'pandoc-attributes'], 14 | entry_points={ 15 | 'console_scripts': [ 16 | 'internal-references = internalreferences:main', 17 | ], 18 | } 19 | ) 20 | -------------------------------------------------------------------------------- /spec.md: -------------------------------------------------------------------------------- 1 | ## Experiments with pandoc figures (ˈjuːnɪˌkəʊd!) {#sec:expt .class1 .class2 key=value} 2 | 3 | ![a figure that can be referred to (ˈjuːnɪˌkəʊd!)](image.png){#fig:attr .class1 .class2 key=value} 4 | 5 | Here is a reference to @fig:attr and here is one to @fig:attr2. 6 | 7 | Here is reference to the section called @sec:expt. 8 | 9 |
10 | ![another figure that can be referred to (ˈjuːnɪˌkəʊd!)](image.png) 11 |
12 | ![figure with no attr (ˈjuːnɪˌkəʊd!)](image.png) 13 | 14 | 15 | Here is @eq:silly: 16 | 17 | $$ 18 | 2 + 2 = 5 19 | \label{eq:silly} 20 | $$ 21 | 22 | ## Unnumbered Section {-} 23 | 24 | ![no numbering here (ˈjuːnɪˌkəʊd!)](image.png){#fig:nonum -} 25 | 26 | 27 | ## Multiple references {-} 28 | 29 | We can refer to multiple things of the same type: [@fig:attr; @fig:attr2] 30 | 31 | Or to multiple things of mixed type: [@fig:attr; @fig:attr2; 32 | @sec:expt; @eq:silly] 33 | 34 | But if there are any missing keys, nothing will happen: [@fig:attr; 35 | @fig:idontexist] 36 | -------------------------------------------------------------------------------- /tests/spec.html: -------------------------------------------------------------------------------- 1 |

0.1: Experiments with pandoc figures (ˈjuːnɪˌkəʊd!)

2 | 3 |
4 | Figure 1: a figure that can be referred to (ˈjuːnɪˌkəʊd!)

Figure 1: a figure that can be referred to (ˈjuːnɪˌkəʊd!)

5 |
6 | 7 |

Here is a reference to Figure 1 and here is one to Figure 2.

8 |

Here is reference to the section called Section 0.1.

9 | 10 |
11 | Figure 2: another figure that can be referred to (ˈjuːnɪˌkəʊd!)

Figure 2: another figure that can be referred to (ˈjuːnɪˌkəʊd!)

12 |
13 | 14 |
15 | figure with no attr (ˈjuːnɪˌkəʊd!) 16 |

figure with no attr (ˈjuːnɪˌkəʊd!)

17 |
18 |

Here is Equation 1:

19 |

\[ 20 | 2 + 2 = 5 21 | \label{eq:silly} 22 | \]

23 |

Unnumbered Section

24 | 25 |
26 | no numbering here (ˈjuːnɪˌkəʊd!)

no numbering here (ˈjuːnɪˌkəʊd!)

27 |
28 | 29 |

Multiple references

30 |

We can refer to multiple things of the same type: Figures 1 and 2

31 |

Or to multiple things of mixed type: Section 0.1, Equation 1 and Figures 1 and 2

32 |

But if there are any missing keys, nothing will happen: [@fig:attr; @fig:idontexist]

33 | -------------------------------------------------------------------------------- /tests/spec.html5: -------------------------------------------------------------------------------- 1 |

0.1: Experiments with pandoc figures (ˈjuːnɪˌkəʊd!)

2 | 3 |
4 | Figure 1: a figure that can be referred to (ˈjuːnɪˌkəʊd!) 5 |
Figure 1: a figure that can be referred to (ˈjuːnɪˌkəʊd!)
6 |
7 | 8 |

Here is a reference to Figure 1 and here is one to Figure 2.

9 |

Here is reference to the section called Section 0.1.

10 | 11 |
12 | Figure 2: another figure that can be referred to (ˈjuːnɪˌkəʊd!) 13 |
Figure 2: another figure that can be referred to (ˈjuːnɪˌkəʊd!)
14 |
15 | 16 |
17 | figure with no attr (ˈjuːnɪˌkəʊd!)
figure with no attr (ˈjuːnɪˌkəʊd!)
18 |
19 |

Here is Equation 1:

20 |

\[ 21 | 2 + 2 = 5 22 | \label{eq:silly} 23 | \]

24 |

Unnumbered Section

25 | 26 |
27 | no numbering here (ˈjuːnɪˌkəʊd!) 28 |
no numbering here (ˈjuːnɪˌkəʊd!)
29 |
30 | 31 |

Multiple references

32 |

We can refer to multiple things of the same type: Figures 1 and 2

33 |

Or to multiple things of mixed type: Section 0.1, Equation 1 and Figures 1 and 2

34 |

But if there are any missing keys, nothing will happen: [@fig:attr; @fig:idontexist]

35 | -------------------------------------------------------------------------------- /tests/spec.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "unMeta": {} 4 | }, 5 | [ 6 | { 7 | "t": "Header", 8 | "c": [ 9 | 2, 10 | [ 11 | "sec:expt", 12 | [ 13 | "class1", 14 | "class2" 15 | ], 16 | [ 17 | [ 18 | "key", 19 | "value" 20 | ] 21 | ] 22 | ], 23 | [ 24 | { 25 | "t": "Str", 26 | "c": "0.1: " 27 | }, 28 | { 29 | "t": "Str", 30 | "c": "Experiments" 31 | }, 32 | { 33 | "t": "Space", 34 | "c": [] 35 | }, 36 | { 37 | "t": "Str", 38 | "c": "with" 39 | }, 40 | { 41 | "t": "Space", 42 | "c": [] 43 | }, 44 | { 45 | "t": "Str", 46 | "c": "pandoc" 47 | }, 48 | { 49 | "t": "Space", 50 | "c": [] 51 | }, 52 | { 53 | "t": "Str", 54 | "c": "figures" 55 | }, 56 | { 57 | "t": "Space", 58 | "c": [] 59 | }, 60 | { 61 | "t": "Str", 62 | "c": "(ˈjuːnɪˌkəʊd!)" 63 | } 64 | ] 65 | ] 66 | }, 67 | { 68 | "t": "Div", 69 | "c": [ 70 | [ 71 | "fig:attr", 72 | [ 73 | "figure", 74 | "class1", 75 | "class2" 76 | ], 77 | [ 78 | [ 79 | "key", 80 | "value" 81 | ] 82 | ] 83 | ], 84 | [ 85 | { 86 | "t": "Para", 87 | "c": [ 88 | { 89 | "t": "Image", 90 | "c": [ 91 | [ 92 | { 93 | "t": "Str", 94 | "c": "Figure 1: a figure that can be referred to (ˈjuːnɪˌkəʊd!)" 95 | } 96 | ], 97 | [ 98 | "image.png", 99 | "" 100 | ] 101 | ] 102 | } 103 | ] 104 | } 105 | ] 106 | ] 107 | }, 108 | { 109 | "t": "Para", 110 | "c": [ 111 | { 112 | "t": "Str", 113 | "c": "Here" 114 | }, 115 | { 116 | "t": "Space", 117 | "c": [] 118 | }, 119 | { 120 | "t": "Str", 121 | "c": "is" 122 | }, 123 | { 124 | "t": "Space", 125 | "c": [] 126 | }, 127 | { 128 | "t": "Str", 129 | "c": "a" 130 | }, 131 | { 132 | "t": "Space", 133 | "c": [] 134 | }, 135 | { 136 | "t": "Str", 137 | "c": "reference" 138 | }, 139 | { 140 | "t": "Space", 141 | "c": [] 142 | }, 143 | { 144 | "t": "Str", 145 | "c": "to" 146 | }, 147 | { 148 | "t": "Space", 149 | "c": [] 150 | }, 151 | { 152 | "t": "Link", 153 | "c": [ 154 | [ 155 | { 156 | "t": "Str", 157 | "c": "Figure 1" 158 | } 159 | ], 160 | [ 161 | "#fig:attr", 162 | "" 163 | ] 164 | ] 165 | }, 166 | { 167 | "t": "Space", 168 | "c": [] 169 | }, 170 | { 171 | "t": "Str", 172 | "c": "and" 173 | }, 174 | { 175 | "t": "Space", 176 | "c": [] 177 | }, 178 | { 179 | "t": "Str", 180 | "c": "here" 181 | }, 182 | { 183 | "t": "Space", 184 | "c": [] 185 | }, 186 | { 187 | "t": "Str", 188 | "c": "is" 189 | }, 190 | { 191 | "t": "Space", 192 | "c": [] 193 | }, 194 | { 195 | "t": "Str", 196 | "c": "one" 197 | }, 198 | { 199 | "t": "Space", 200 | "c": [] 201 | }, 202 | { 203 | "t": "Str", 204 | "c": "to" 205 | }, 206 | { 207 | "t": "Space", 208 | "c": [] 209 | }, 210 | { 211 | "t": "Link", 212 | "c": [ 213 | [ 214 | { 215 | "t": "Str", 216 | "c": "Figure 2" 217 | } 218 | ], 219 | [ 220 | "#fig:attr2", 221 | "" 222 | ] 223 | ] 224 | }, 225 | { 226 | "t": "Str", 227 | "c": "." 228 | } 229 | ] 230 | }, 231 | { 232 | "t": "Para", 233 | "c": [ 234 | { 235 | "t": "Str", 236 | "c": "Here" 237 | }, 238 | { 239 | "t": "Space", 240 | "c": [] 241 | }, 242 | { 243 | "t": "Str", 244 | "c": "is" 245 | }, 246 | { 247 | "t": "Space", 248 | "c": [] 249 | }, 250 | { 251 | "t": "Str", 252 | "c": "reference" 253 | }, 254 | { 255 | "t": "Space", 256 | "c": [] 257 | }, 258 | { 259 | "t": "Str", 260 | "c": "to" 261 | }, 262 | { 263 | "t": "Space", 264 | "c": [] 265 | }, 266 | { 267 | "t": "Str", 268 | "c": "the" 269 | }, 270 | { 271 | "t": "Space", 272 | "c": [] 273 | }, 274 | { 275 | "t": "Str", 276 | "c": "section" 277 | }, 278 | { 279 | "t": "Space", 280 | "c": [] 281 | }, 282 | { 283 | "t": "Str", 284 | "c": "called" 285 | }, 286 | { 287 | "t": "Space", 288 | "c": [] 289 | }, 290 | { 291 | "t": "Link", 292 | "c": [ 293 | [ 294 | { 295 | "t": "Str", 296 | "c": "Section 0.1" 297 | } 298 | ], 299 | [ 300 | "#sec:expt", 301 | "" 302 | ] 303 | ] 304 | }, 305 | { 306 | "t": "Str", 307 | "c": "." 308 | } 309 | ] 310 | }, 311 | { 312 | "t": "Div", 313 | "c": [ 314 | [ 315 | "fig:attr2", 316 | [ 317 | "figure" 318 | ], 319 | [] 320 | ], 321 | [ 322 | { 323 | "t": "Para", 324 | "c": [ 325 | { 326 | "t": "Image", 327 | "c": [ 328 | [ 329 | { 330 | "t": "Str", 331 | "c": "Figure 2: another figure that can be referred to (ˈjuːnɪˌkəʊd!)" 332 | } 333 | ], 334 | [ 335 | "image.png", 336 | "" 337 | ] 338 | ] 339 | } 340 | ] 341 | } 342 | ] 343 | ] 344 | }, 345 | { 346 | "t": "Para", 347 | "c": [ 348 | { 349 | "t": "Image", 350 | "c": [ 351 | [ 352 | { 353 | "t": "Str", 354 | "c": "figure" 355 | }, 356 | { 357 | "t": "Space", 358 | "c": [] 359 | }, 360 | { 361 | "t": "Str", 362 | "c": "with" 363 | }, 364 | { 365 | "t": "Space", 366 | "c": [] 367 | }, 368 | { 369 | "t": "Str", 370 | "c": "no" 371 | }, 372 | { 373 | "t": "Space", 374 | "c": [] 375 | }, 376 | { 377 | "t": "Str", 378 | "c": "attr" 379 | }, 380 | { 381 | "t": "Space", 382 | "c": [] 383 | }, 384 | { 385 | "t": "Str", 386 | "c": "(ˈjuːnɪˌkəʊd!)" 387 | } 388 | ], 389 | [ 390 | "image.png", 391 | "fig:" 392 | ] 393 | ] 394 | } 395 | ] 396 | }, 397 | { 398 | "t": "Para", 399 | "c": [ 400 | { 401 | "t": "Str", 402 | "c": "Here" 403 | }, 404 | { 405 | "t": "Space", 406 | "c": [] 407 | }, 408 | { 409 | "t": "Str", 410 | "c": "is" 411 | }, 412 | { 413 | "t": "Space", 414 | "c": [] 415 | }, 416 | { 417 | "t": "Link", 418 | "c": [ 419 | [ 420 | { 421 | "t": "Str", 422 | "c": "Equation 1" 423 | } 424 | ], 425 | [ 426 | "#eq:silly", 427 | "" 428 | ] 429 | ] 430 | }, 431 | { 432 | "t": "Str", 433 | "c": ":" 434 | } 435 | ] 436 | }, 437 | { 438 | "t": "Para", 439 | "c": [ 440 | { 441 | "t": "Span", 442 | "c": [ 443 | [ 444 | "#eq:silly", 445 | [], 446 | [] 447 | ], 448 | [ 449 | { 450 | "t": "Math", 451 | "c": [ 452 | { 453 | "t": "DisplayMath", 454 | "c": [] 455 | }, 456 | "\n2 + 2 = 5\n\\label{eq:silly}\n" 457 | ] 458 | } 459 | ] 460 | ] 461 | } 462 | ] 463 | }, 464 | { 465 | "t": "Header", 466 | "c": [ 467 | 2, 468 | [ 469 | "unnumbered-section", 470 | [ 471 | "unnumbered" 472 | ], 473 | [] 474 | ], 475 | [ 476 | { 477 | "t": "Str", 478 | "c": "" 479 | }, 480 | { 481 | "t": "Str", 482 | "c": "Unnumbered" 483 | }, 484 | { 485 | "t": "Space", 486 | "c": [] 487 | }, 488 | { 489 | "t": "Str", 490 | "c": "Section" 491 | } 492 | ] 493 | ] 494 | }, 495 | { 496 | "t": "Div", 497 | "c": [ 498 | [ 499 | "fig:nonum", 500 | [ 501 | "figure", 502 | "unnumbered" 503 | ], 504 | [] 505 | ], 506 | [ 507 | { 508 | "t": "Para", 509 | "c": [ 510 | { 511 | "t": "Image", 512 | "c": [ 513 | [ 514 | { 515 | "t": "Str", 516 | "c": "no numbering here (ˈjuːnɪˌkəʊd!)" 517 | } 518 | ], 519 | [ 520 | "image.png", 521 | "" 522 | ] 523 | ] 524 | } 525 | ] 526 | } 527 | ] 528 | ] 529 | }, 530 | { 531 | "t": "Header", 532 | "c": [ 533 | 2, 534 | [ 535 | "multiple-references", 536 | [ 537 | "unnumbered" 538 | ], 539 | [] 540 | ], 541 | [ 542 | { 543 | "t": "Str", 544 | "c": "" 545 | }, 546 | { 547 | "t": "Str", 548 | "c": "Multiple" 549 | }, 550 | { 551 | "t": "Space", 552 | "c": [] 553 | }, 554 | { 555 | "t": "Str", 556 | "c": "references" 557 | } 558 | ] 559 | ] 560 | }, 561 | { 562 | "t": "Para", 563 | "c": [ 564 | { 565 | "t": "Str", 566 | "c": "We" 567 | }, 568 | { 569 | "t": "Space", 570 | "c": [] 571 | }, 572 | { 573 | "t": "Str", 574 | "c": "can" 575 | }, 576 | { 577 | "t": "Space", 578 | "c": [] 579 | }, 580 | { 581 | "t": "Str", 582 | "c": "refer" 583 | }, 584 | { 585 | "t": "Space", 586 | "c": [] 587 | }, 588 | { 589 | "t": "Str", 590 | "c": "to" 591 | }, 592 | { 593 | "t": "Space", 594 | "c": [] 595 | }, 596 | { 597 | "t": "Str", 598 | "c": "multiple" 599 | }, 600 | { 601 | "t": "Space", 602 | "c": [] 603 | }, 604 | { 605 | "t": "Str", 606 | "c": "things" 607 | }, 608 | { 609 | "t": "Space", 610 | "c": [] 611 | }, 612 | { 613 | "t": "Str", 614 | "c": "of" 615 | }, 616 | { 617 | "t": "Space", 618 | "c": [] 619 | }, 620 | { 621 | "t": "Str", 622 | "c": "the" 623 | }, 624 | { 625 | "t": "Space", 626 | "c": [] 627 | }, 628 | { 629 | "t": "Str", 630 | "c": "same" 631 | }, 632 | { 633 | "t": "Space", 634 | "c": [] 635 | }, 636 | { 637 | "t": "Str", 638 | "c": "type:" 639 | }, 640 | { 641 | "t": "Space", 642 | "c": [] 643 | }, 644 | { 645 | "t": "Str", 646 | "c": "Figures " 647 | }, 648 | { 649 | "t": "Link", 650 | "c": [ 651 | [ 652 | { 653 | "t": "Str", 654 | "c": "1" 655 | } 656 | ], 657 | [ 658 | "#fig:attr", 659 | "" 660 | ] 661 | ] 662 | }, 663 | { 664 | "t": "Str", 665 | "c": " and " 666 | }, 667 | { 668 | "t": "Link", 669 | "c": [ 670 | [ 671 | { 672 | "t": "Str", 673 | "c": "2" 674 | } 675 | ], 676 | [ 677 | "#fig:attr2", 678 | "" 679 | ] 680 | ] 681 | } 682 | ] 683 | }, 684 | { 685 | "t": "Para", 686 | "c": [ 687 | { 688 | "t": "Str", 689 | "c": "Or" 690 | }, 691 | { 692 | "t": "Space", 693 | "c": [] 694 | }, 695 | { 696 | "t": "Str", 697 | "c": "to" 698 | }, 699 | { 700 | "t": "Space", 701 | "c": [] 702 | }, 703 | { 704 | "t": "Str", 705 | "c": "multiple" 706 | }, 707 | { 708 | "t": "Space", 709 | "c": [] 710 | }, 711 | { 712 | "t": "Str", 713 | "c": "things" 714 | }, 715 | { 716 | "t": "Space", 717 | "c": [] 718 | }, 719 | { 720 | "t": "Str", 721 | "c": "of" 722 | }, 723 | { 724 | "t": "Space", 725 | "c": [] 726 | }, 727 | { 728 | "t": "Str", 729 | "c": "mixed" 730 | }, 731 | { 732 | "t": "Space", 733 | "c": [] 734 | }, 735 | { 736 | "t": "Str", 737 | "c": "type:" 738 | }, 739 | { 740 | "t": "Space", 741 | "c": [] 742 | }, 743 | { 744 | "t": "Str", 745 | "c": "Section " 746 | }, 747 | { 748 | "t": "Link", 749 | "c": [ 750 | [ 751 | { 752 | "t": "Str", 753 | "c": "0.1" 754 | } 755 | ], 756 | [ 757 | "#sec:expt", 758 | "" 759 | ] 760 | ] 761 | }, 762 | { 763 | "t": "Str", 764 | "c": ", " 765 | }, 766 | { 767 | "t": "Str", 768 | "c": "Equation " 769 | }, 770 | { 771 | "t": "Link", 772 | "c": [ 773 | [ 774 | { 775 | "t": "Str", 776 | "c": "1" 777 | } 778 | ], 779 | [ 780 | "#eq:silly", 781 | "" 782 | ] 783 | ] 784 | }, 785 | { 786 | "t": "Str", 787 | "c": " and " 788 | }, 789 | { 790 | "t": "Str", 791 | "c": "Figures " 792 | }, 793 | { 794 | "t": "Link", 795 | "c": [ 796 | [ 797 | { 798 | "t": "Str", 799 | "c": "1" 800 | } 801 | ], 802 | [ 803 | "#fig:attr", 804 | "" 805 | ] 806 | ] 807 | }, 808 | { 809 | "t": "Str", 810 | "c": " and " 811 | }, 812 | { 813 | "t": "Link", 814 | "c": [ 815 | [ 816 | { 817 | "t": "Str", 818 | "c": "2" 819 | } 820 | ], 821 | [ 822 | "#fig:attr2", 823 | "" 824 | ] 825 | ] 826 | } 827 | ] 828 | }, 829 | { 830 | "t": "Para", 831 | "c": [ 832 | { 833 | "t": "Str", 834 | "c": "But" 835 | }, 836 | { 837 | "t": "Space", 838 | "c": [] 839 | }, 840 | { 841 | "t": "Str", 842 | "c": "if" 843 | }, 844 | { 845 | "t": "Space", 846 | "c": [] 847 | }, 848 | { 849 | "t": "Str", 850 | "c": "there" 851 | }, 852 | { 853 | "t": "Space", 854 | "c": [] 855 | }, 856 | { 857 | "t": "Str", 858 | "c": "are" 859 | }, 860 | { 861 | "t": "Space", 862 | "c": [] 863 | }, 864 | { 865 | "t": "Str", 866 | "c": "any" 867 | }, 868 | { 869 | "t": "Space", 870 | "c": [] 871 | }, 872 | { 873 | "t": "Str", 874 | "c": "missing" 875 | }, 876 | { 877 | "t": "Space", 878 | "c": [] 879 | }, 880 | { 881 | "t": "Str", 882 | "c": "keys," 883 | }, 884 | { 885 | "t": "Space", 886 | "c": [] 887 | }, 888 | { 889 | "t": "Str", 890 | "c": "nothing" 891 | }, 892 | { 893 | "t": "Space", 894 | "c": [] 895 | }, 896 | { 897 | "t": "Str", 898 | "c": "will" 899 | }, 900 | { 901 | "t": "Space", 902 | "c": [] 903 | }, 904 | { 905 | "t": "Str", 906 | "c": "happen:" 907 | }, 908 | { 909 | "t": "Space", 910 | "c": [] 911 | }, 912 | { 913 | "t": "Cite", 914 | "c": [ 915 | [ 916 | { 917 | "citationSuffix": [], 918 | "citationNoteNum": 0, 919 | "citationMode": { 920 | "t": "NormalCitation", 921 | "c": [] 922 | }, 923 | "citationPrefix": [], 924 | "citationId": "fig:attr", 925 | "citationHash": 0 926 | }, 927 | { 928 | "citationSuffix": [], 929 | "citationNoteNum": 0, 930 | "citationMode": { 931 | "t": "NormalCitation", 932 | "c": [] 933 | }, 934 | "citationPrefix": [], 935 | "citationId": "fig:idontexist", 936 | "citationHash": 0 937 | } 938 | ], 939 | [ 940 | { 941 | "t": "Str", 942 | "c": "[@fig:attr;" 943 | }, 944 | { 945 | "t": "Space", 946 | "c": [] 947 | }, 948 | { 949 | "t": "Str", 950 | "c": "@fig:idontexist]" 951 | } 952 | ] 953 | ] 954 | } 955 | ] 956 | } 957 | ] 958 | ] 959 | -------------------------------------------------------------------------------- /tests/spec.latex: -------------------------------------------------------------------------------- 1 | \subsection{Experiments with pandoc figures 2 | (ˈjuːnɪˌkəʊd!)}\label{sec:expt} 3 | 4 | 5 | \begin{figure}[htbp] 6 | \centering 7 | \includegraphics{image.png} 8 | \caption{a figure that can be referred to (ˈjuːnɪˌkəʊd!)} 9 | \label{fig:attr} 10 | \end{figure} 11 | 12 | Here is a reference to \autoref{fig:attr} and here is one to 13 | \autoref{fig:attr2}. 14 | 15 | Here is reference to the section called \autoref{sec:expt}. 16 | 17 | 18 | \begin{figure}[htbp] 19 | \centering 20 | \includegraphics{image.png} 21 | \caption{another figure that can be referred to (ˈjuːnɪˌkəʊd!)} 22 | \label{fig:attr2} 23 | \end{figure} 24 | 25 | \begin{figure}[htbp] 26 | \centering 27 | \includegraphics{image.png} 28 | \caption{figure with no attr (ˈjuːnɪˌkəʊd!)} 29 | \end{figure} 30 | 31 | Here is \autoref{eq:silly}: 32 | 33 | \[ 34 | 2 + 2 = 5 35 | \label{eq:silly} 36 | \] 37 | 38 | \subsection*{Unnumbered Section}\label{unnumbered-section} 39 | \addcontentsline{toc}{subsection}{Unnumbered Section} 40 | 41 | 42 | \begin{figure}[htbp] 43 | \centering 44 | \includegraphics{image.png} 45 | \caption*{no numbering here (ˈjuːnɪˌkəʊd!)} 46 | \label{fig:nonum} 47 | \end{figure} 48 | 49 | \subsection*{Multiple references}\label{multiple-references} 50 | \addcontentsline{toc}{subsection}{Multiple references} 51 | 52 | We can refer to multiple things of the same type: 53 | \cref{fig:attr,fig:attr2} 54 | 55 | Or to multiple things of mixed type: 56 | \cref{fig:attr,fig:attr2,sec:expt,eq:silly} 57 | 58 | But if there are any missing keys, nothing will happen: {[}@fig:attr; 59 | @fig:idontexist{]} 60 | -------------------------------------------------------------------------------- /tests/spec.markdown: -------------------------------------------------------------------------------- 1 | 0.1: Experiments with pandoc figures (ˈjuːnɪˌkəʊd!) {#sec:expt .class1 .class2 key="value"} 2 | --------------------------------------------------- 3 | 4 | 5 |
6 | ![Figure 1: a figure that can be referred to (ˈjuːnɪˌkəʊd!)](image.png) 7 | 8 |
9 | 10 | Here is a reference to [Figure 1](#fig:attr) and here is one to 11 | [Figure 2](#fig:attr2). 12 | 13 | Here is reference to the section called [Section 0.1](#sec:expt). 14 | 15 | 16 |
17 | ![Figure 2: another figure that can be referred to (ˈjuːnɪˌkəʊd!)](image.png) 18 | 19 |
20 | 21 | ![figure with no attr (ˈjuːnɪˌkəʊd!)](image.png) 22 | 23 | Here is [Equation 1](#eq:silly): 24 | 25 | $$ 26 | 2 + 2 = 5 27 | \label{eq:silly} 28 | $$ 29 | 30 | Unnumbered Section {#unnumbered-section .unnumbered} 31 | ------------------ 32 | 33 | 34 |
35 | ![no numbering here (ˈjuːnɪˌkəʊd!)](image.png) 36 | 37 |
38 | 39 | Multiple references {#multiple-references .unnumbered} 40 | ------------------- 41 | 42 | We can refer to multiple things of the same type: 43 | Figures [1](#fig:attr) and [2](#fig:attr2) 44 | 45 | Or to multiple things of mixed type: 46 | Section [0.1](#sec:expt), Equation [1](#eq:silly) and Figures [1](#fig:attr) and [2](#fig:attr2) 47 | 48 | But if there are any missing keys, nothing will happen: 49 | [@fig:attr; @fig:idontexist] 50 | -------------------------------------------------------------------------------- /tests/tests.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import json 3 | 4 | import nose.tools as nt 5 | 6 | import internalreferences 7 | 8 | 9 | def test_attributes(): 10 | attr_markdown = r"""{#identify .class1 .class2 11 | key1=blah key2="o'brien = 1" -}""" 12 | attr_dict = {'id': 'identify', 13 | 'classes': ['class1', 'class2', 'unnumbered'], 14 | 'key1': 'blah', 15 | 'key2': '"o\'brien = 1"' 16 | } 17 | attr_html = '''id="identify" class="class1 class2 unnumbered" key1=blah key2="o'brien = 1"''' 18 | 19 | attr = internalreferences.PandocAttributes(attr_markdown, 'markdown') 20 | 21 | print(attr_dict) 22 | print(attr.to_dict()) 23 | nt.assert_dict_equal(attr_dict, attr.to_dict()) 24 | nt.assert_equal(attr_html, attr.to_html()) 25 | 26 | 27 | def call_pandoc(format): 28 | pandoc_cmd = ('pandoc', 'spec.md', 29 | '--filter', './internalreferences.py', 30 | '--mathjax', 31 | '--to', format) 32 | p = subprocess.Popen(pandoc_cmd, stdout=subprocess.PIPE) 33 | stdout, stderr = p.communicate() 34 | return stdout.decode() 35 | 36 | 37 | def _test(format): 38 | pandoc_output = call_pandoc(format) 39 | 40 | ref_file = 'tests/spec.{ext}'.format(ext=format) 41 | 42 | with open(ref_file) as f: 43 | nt.assert_multi_line_equal(pandoc_output, f.read()) 44 | 45 | 46 | def test_markdown(): 47 | _test('markdown') 48 | 49 | 50 | def test_html(): 51 | _test('html') 52 | 53 | 54 | def test_html5(): 55 | _test('html5') 56 | 57 | 58 | def test_latex(): 59 | _test('latex') 60 | 61 | 62 | def test_generic(): 63 | test = json.loads(call_pandoc('json')) 64 | 65 | with open('tests/spec.json') as f: 66 | ref = json.load(f) 67 | 68 | assert test == ref 69 | 70 | 71 | def test_all_formats(): 72 | for format in ('markdown', 'latex', 'html', 'html5'): 73 | _test(format) 74 | 75 | 76 | if __name__ == '__main__': 77 | print("Comparing pandoc output with reference output in tests/spec.format") 78 | test_markdown() 79 | test_html() 80 | test_html5() 81 | test_latex() 82 | print("All comparison tests passed ok!") 83 | --------------------------------------------------------------------------------