Passed
Push — develop ( 17b5b5...f694b1 )
by Christophe
57s
created

pandoc_numbering._main   F

Complexity

Total Complexity 190

Size/Duplication

Total Lines 1601
Duplicated Lines 3.87 %

Importance

Changes 0
Metric Value
eloc 767
dl 62
loc 1601
rs 1.833
c 0
b 0
f 0
wmc 190

36 Functions

Rating   Name   Duplication   Size   Complexity  
C meta_listing() 0 40 9
A replace_description() 0 17 1
F finalize() 0 123 13
A referencing() 0 22 5
A lowering() 0 11 2
B referencing_cite() 0 37 7
F meta_levels() 62 89 17
A meta_format() 0 23 3
A replace_page_number() 0 14 1
A to_latex() 0 18 1
A meta_cite() 0 20 3
A replace_local_number() 0 13 1
B replacing() 0 33 6
A replace_global_number() 0 12 1
A update_header_aliases() 0 14 2
A remove_useless_latex() 0 45 2
A main() 0 10 1
A prepare() 0 25 5
A numbering() 0 18 4
A meta_format_entry() 0 15 1
A meta_format_caption() 0 20 4
A meta_format_text() 0 15 1
A update_header_numbers() 0 15 3
A replace_section_number() 0 12 1
A meta_entry_tab() 0 14 1
A meta_entry_space() 0 14 1
B referencing_link() 0 39 5
A replace_count() 0 12 1
A link_color() 0 18 2
A meta_format_link() 0 15 1
A meta_classes() 0 16 3
B add_definition() 0 39 5
A replace_title() 0 17 1
A define() 0 41 2
B meta_entry() 0 42 7
A table_other() 0 24 2

33 Methods

Rating   Name   Duplication   Size   Complexity  
A Numbered.link() 0 10 1
A Numbered.alias() 0 10 1
A Numbered._get_content() 0 6 3
C Numbered._compute_data() 0 109 7
A Numbered.section_alias() 0 10 1
A Numbered._remove_accents() 0 5 1
A Numbered._set_content() 0 5 3
A Numbered.tag() 0 10 1
B Numbered.__init__() 0 37 5
A Numbered.global_number() 0 10 1
A Numbered._compute_category() 0 8 2
A Numbered.entry() 0 10 1
A Numbered._compute_tag() 0 12 3
A Numbered._compute_description() 0 5 1
A Numbered._identifier() 0 9 1
A Numbered._compute_number() 0 2 1
A Numbered._compute_alias() 0 27 4
A Numbered._compute_global_number() 0 6 2
A Numbered.caption() 0 10 1
A Numbered._compute_basic_category() 0 9 3
A Numbered._replace_double_sharp() 0 3 1
A Numbered.category() 0 10 1
A Numbered._compute_leading() 0 6 2
A Numbered.description() 0 10 1
A Numbered.title() 0 10 1
A Numbered.section_number() 0 10 1
A Numbered.local_number() 0 10 1
A Numbered._compute_section_alias() 0 6 3
A Numbered._replace_marker() 0 15 1
A Numbered._compute_local_number() 0 7 1
A Numbered._compute_section_number() 0 3 1
B Numbered._compute_title() 0 16 6
A Numbered._compute_levels() 0 12 2

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like pandoc_numbering._main often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#!/usr/bin/env python
2
3
# pylint: disable=too-many-lines
4
5
"""Pandoc filter to number all kinds of things."""
6
7
import copy
8
import re
9
import unicodedata
10
from functools import partial
11
from textwrap import dedent
12
13
from panflute import (
14
    BlockQuote,
15
    BulletList,
16
    Citation,
17
    Cite,
18
    CodeBlock,
19
    Definition,
20
    DefinitionItem,
21
    DefinitionList,
22
    Div,
23
    Emph,
24
    Header,
25
    HorizontalRule,
26
    Image,
27
    LineBlock,
28
    LineBreak,
29
    LineItem,
30
    Link,
31
    ListItem,
32
    MetaBool,
33
    MetaInlines,
34
    MetaList,
35
    MetaMap,
36
    MetaString,
37
    Note,
38
    Para,
39
    Plain,
40
    RawBlock,
41
    RawInline,
42
    SoftBreak,
43
    Space,
44
    Span,
45
    Str,
46
    Strong,
47
    Table,
48
    TableCell,
49
    TableRow,
50
    convert_text,
51
    debug,
52
    run_filters,
53
    stringify,
54
)
55
56
57
# pylint: disable=bad-option-value,useless-object-inheritance
58
class Numbered:
59
    """
60
    Numbered elements.
61
    """
62
63
    # pylint: disable=too-many-instance-attributes
64
    __slots__ = [
65
        "_elem",
66
        "_doc",
67
        "_match",
68
        "_tag",
69
        "_entry",
70
        "_link",
71
        "_caption",
72
        "_title",
73
        "_description",
74
        "_category",
75
        "_basic_category",
76
        "_first_section_level",
77
        "_last_section_level",
78
        "_leading",
79
        "_number",
80
        "_global_number",
81
        "_section_number",
82
        "_local_number",
83
        "_section_alias",
84
        "_alias",
85
    ]
86
87
    @property
88
    def tag(self):
89
        """
90
        Get the tag property.
91
92
        Returns
93
        -------
94
            The tag property.
95
        """
96
        return self._tag
97
98
    @property
99
    def entry(self):
100
        """
101
        Get the entry property.
102
103
        Returns
104
        -------
105
            The entry property.
106
        """
107
        return self._entry
108
109
    @property
110
    def link(self):
111
        """
112
        Get the link property.
113
114
        Returns
115
        -------
116
            The link property.
117
        """
118
        return self._link
119
120
    @property
121
    def title(self):
122
        """
123
        Get the title property.
124
125
        Returns
126
        -------
127
            The title property.
128
        """
129
        return self._title
130
131
    @property
132
    def description(self):
133
        """
134
        Get the description property.
135
136
        Returns
137
        -------
138
            The description property.
139
        """
140
        return self._description
141
142
    @property
143
    def global_number(self):
144
        """
145
        Get the global_number property.
146
147
        Returns
148
        -------
149
            The global_number property.
150
        """
151
        return self._global_number
152
153
    @property
154
    def section_number(self):
155
        """
156
        Get the section_number property.
157
158
        Returns
159
        -------
160
            The section_number property.
161
        """
162
        return self._section_number
163
164
    @property
165
    def section_alias(self):
166
        """
167
        Get the section_alias property.
168
169
        Returns
170
        -------
171
            The section_alias property.
172
        """
173
        return self._section_alias
174
175
    @property
176
    def alias(self):
177
        """
178
        Get the alias property.
179
180
        Returns
181
        -------
182
            The alias property.
183
        """
184
        return self._alias
185
186
    @property
187
    def local_number(self):
188
        """
189
        Get the local_number property.
190
191
        Returns
192
        -------
193
            The local_number property.
194
        """
195
        return self._local_number
196
197
    @property
198
    def category(self):
199
        """
200
        Get the category property.
201
202
        Returns
203
        -------
204
            The category property.
205
        """
206
        return self._category
207
208
    @property
209
    def caption(self):
210
        """
211
        Get the caption property.
212
213
        Returns
214
        -------
215
            The caption property.
216
        """
217
        return self._caption
218
219
    number_regex = "#((?P<prefix>[a-zA-Z][\\w.-]*):)?(?P<name>[a-zA-Z][\\w:.-]*)?"
220
    _regex = "(?P<header>(?P<hidden>(-\\.)*)(\\+\\.)*)"
221
    header_regex = "^" + _regex + "$"
222
    marker_regex = "^" + _regex + number_regex + "$"
223
    double_sharp_regex = "^" + _regex + "#" + number_regex + "$"
224
225
    @staticmethod
226
    def _remove_accents(string):
227
        nfkd_form = unicodedata.normalize("NFKD", string)
228
        # pylint: disable=redundant-u-string-prefix
229
        return "".join([c for c in nfkd_form if not unicodedata.combining(c)])
230
231
    @staticmethod
232
    def _identifier(string):
233
        # replace invalid characters by dash
234
        string = re.sub(
235
            "[^0-9a-zA-Z_-]+", "-", Numbered._remove_accents(string.lower())
236
        )
237
238
        # Remove leading digits
239
        return re.sub("^[^a-zA-Z]+", "", string)
240
241
    def __init__(self, elem, doc):
242
        """
243
        Initialise an instance.
244
245
        Arguments
246
        ---------
247
        elem
248
            An element.
249
        doc
250
            The document.
251
        """
252
        self._elem = elem
253
        self._doc = doc
254
        self._tag = None
255
        self._entry = Span(classes=["pandoc-numbering-entry"])
256
        self._link = Span(classes=["pandoc-numbering-link"])
257
        self._caption = None
258
        self._title = None
259
        self._description = None
260
        self._category = None
261
        self._basic_category = None
262
        self._first_section_level = None
263
        self._last_section_level = None
264
        self._leading = None
265
        self._number = None
266
        self._global_number = None
267
        self._section_number = None
268
        self._local_number = None
269
        self._section_alias = None
270
        self._alias = None
271
272
        if self._get_content() and isinstance(self._get_content()[-1], Str):
273
            self._match = re.match(Numbered.marker_regex, self._get_content()[-1].text)
274
            if self._match:
275
                self._replace_marker()
276
            elif re.match(Numbered.double_sharp_regex, self._get_content()[-1].text):
277
                self._replace_double_sharp()
278
279
    def _set_content(self, content):
280
        if isinstance(self._elem, Para):
281
            self._elem.content = content
282
        elif isinstance(self._elem, DefinitionItem):
283
            self._elem.term = content
284
285
    def _get_content(self):
286
        if isinstance(self._elem, Para):
287
            return self._elem.content
288
        if isinstance(self._elem, DefinitionItem):
289
            return self._elem.term
290
        return None
291
292
    def _replace_double_sharp(self):
293
        self._get_content()[-1].text = self._get_content()[-1].text.replace(
294
            "##", "#", 1
295
        )
296
297
    def _replace_marker(self):
298
        self._compute_title()
299
        self._compute_description()
300
        self._compute_basic_category()
301
        self._compute_levels()
302
        self._compute_section_number()
303
        self._compute_section_alias()
304
        self._compute_leading()
305
        self._compute_category()
306
        self._compute_number()
307
        self._compute_tag()
308
        self._compute_alias()
309
        self._compute_local_number()
310
        self._compute_global_number()
311
        self._compute_data()
312
313
    def _compute_title(self):
314
        self._title = []
315
        if (
316
            isinstance(self._get_content()[-3], Str)
317
            and self._get_content()[-3].text[-1:] == ")"
318
        ):
319
            for i, item in enumerate(self._get_content()):
320
                if isinstance(item, Str) and item.text[0] == "(":
321
                    self._title = self._get_content()[i:-2]
322
                    # Detach from original parent
323
                    self._title.parent = None
324
                    self._title[0].text = self._title[0].text[1:]
325
                    self._title[-1].text = self._title[-1].text[:-1]
326
                    del self._get_content()[i - 1 : -2]
327
                    break
328
        self._title = list(self._title)
329
330
    def _compute_description(self):
331
        self._description = self._get_content()[:-2]
332
        # Detach from original parent
333
        self._description.parent = None
334
        self._description = list(self._description)
335
336
    def _compute_basic_category(self):
337
        if self._match.group("prefix") is None:
338
            self._basic_category = Numbered._identifier(
339
                "".join(map(stringify, self._description))
340
            )
341
        else:
342
            self._basic_category = self._match.group("prefix")
343
        if self._basic_category not in self._doc.defined:
344
            define(self._basic_category, self._doc)
345
346
    def _compute_levels(self):
347
        # Compute the first and last section level values
348
        self._first_section_level = len(self._match.group("hidden")) // 2
349
        self._last_section_level = len(self._match.group("header")) // 2
350
351
        # Get the default first and last section level
352
        if self._first_section_level == self._last_section_level == 0:
353
            self._first_section_level = self._doc.defined[self._basic_category][
354
                "first-section-level"
355
            ]
356
            self._last_section_level = self._doc.defined[self._basic_category][
357
                "last-section-level"
358
            ]
359
360
    def _compute_section_number(self):
361
        self._section_number = ".".join(
362
            map(str, self._doc.headers[: self._last_section_level])
363
        )
364
365
    def _compute_section_alias(self):
366
        strings = list(map(str, self._doc.aliases[: self._last_section_level]))
367
        for index, string in enumerate(strings):
368
            if string == "":
369
                strings[index] = "0"
370
        self._section_alias = ".".join(strings)
371
372
    def _compute_leading(self):
373
        # Compute the leading (composed of the section numbering and a dot)
374
        if self._last_section_level != 0:
375
            self._leading = self._section_number + "."
376
        else:
377
            self._leading = ""
378
379
    def _compute_category(self):
380
        self._category = self._basic_category + ":" + self._leading
381
382
        # Is it a new category?
383
        if self._category not in self._doc.count:
384
            self._doc.count[self._category] = 0
385
386
        self._doc.count[self._category] = self._doc.count[self._category] + 1
387
388
    def _compute_number(self):
389
        self._number = str(self._doc.count[self._category])
390
391
    def _compute_tag(self):
392
        # Determine the final tag
393
        if self._match.group("name") is None:
394
            self._tag = self._category + self._number
395
        else:
396
            self._tag = self._basic_category + ":" + self._match.group("name")
397
398
        # Compute collections
399
        if self._basic_category not in self._doc.collections:
400
            self._doc.collections[self._basic_category] = []
401
402
        self._doc.collections[self._basic_category].append(self._tag)
403
404
    def _compute_alias(self):
405
        # Determine the final alias
406
        if not self._title:
407
            if self._section_alias:
408
                self._alias = (
409
                    self._basic_category
410
                    + ":"
411
                    + self._section_alias
412
                    + "."
413
                    + self._number
414
                )
415
            else:
416
                self._alias = self._basic_category + ":" + self._number
417
        else:
418
            if self._section_alias:
419
                self._alias = (
420
                    self._basic_category
421
                    + ":"
422
                    + self._section_alias
423
                    + "."
424
                    + Numbered._identifier(stringify(Span(*self._title)))
425
                )
426
            else:
427
                self._alias = (
428
                    self._basic_category
429
                    + ":"
430
                    + Numbered._identifier(stringify(Span(*self._title)))
431
                )
432
433
    def _compute_local_number(self):
434
        # Replace the '-.-.+.+...#' by the category count (omitting the hidden part)
435
        self._local_number = ".".join(
436
            map(
437
                str,
438
                self._doc.headers[self._first_section_level : self._last_section_level]
439
                + [self._number],
440
            )
441
        )
442
443
    def _compute_global_number(self):
444
        # Compute the global number
445
        if self._section_number:
446
            self._global_number = self._section_number + "." + self._number
447
        else:
448
            self._global_number = self._number
449
450
    def _compute_data(self):
451
        # pylint: disable=too-many-statements,no-member
452
        classes = self._doc.defined[self._basic_category]["classes"]
453
        if self._alias == self._tag:
454
            self._set_content(
455
                [
456
                    Span(),
457
                    Span(
458
                        identifier=self._tag,
459
                        classes=["pandoc-numbering-text"] + classes,
460
                    ),
461
                ]
462
            )
463
        else:
464
            self._set_content(
465
                [
466
                    Span(identifier=self._alias),
467
                    Span(
468
                        identifier=self._tag,
469
                        classes=["pandoc-numbering-text"] + classes,
470
                    ),
471
                ]
472
            )
473
        self._link.classes = self._link.classes + classes
474
        self._entry.classes = self._entry.classes + classes
475
476
        # Prepare the final data
477
        if self._title:
478
            self._get_content()[1].content = copy.deepcopy(
479
                self._doc.defined[self._basic_category]["format-text-title"]
480
            )
481
            self._link.content = copy.deepcopy(
482
                self._doc.defined[self._basic_category]["format-link-title"]
483
            )
484
            self._entry.content = copy.deepcopy(
485
                self._doc.defined[self._basic_category]["format-entry-title"]
486
            )
487
            self._caption = self._doc.defined[self._basic_category][
488
                "format-caption-title"
489
            ]
490
        else:
491
            self._get_content()[1].content = copy.deepcopy(
492
                self._doc.defined[self._basic_category]["format-text-classic"]
493
            )
494
            self._link.content = copy.deepcopy(
495
                self._doc.defined[self._basic_category]["format-link-classic"]
496
            )
497
            self._entry.content = copy.deepcopy(
498
                self._doc.defined[self._basic_category]["format-entry-classic"]
499
            )
500
            self._caption = self._doc.defined[self._basic_category][
501
                "format-caption-classic"
502
            ]
503
504
        # Compute caption (delay replacing %c at the end)
505
        title = stringify(Span(*self._title))
506
        description = stringify(Span(*self._description))
507
        self._caption = self._caption.replace("%t", title.lower())
508
        self._caption = self._caption.replace("%T", title)
509
        self._caption = self._caption.replace("%d", description.lower())
510
        self._caption = self._caption.replace("%D", description)
511
        self._caption = self._caption.replace("%s", self._section_number)
512
        self._caption = self._caption.replace("%g", self._global_number)
513
        self._caption = self._caption.replace("%n", self._local_number)
514
        self._caption = self._caption.replace("#", self._local_number)
515
        if self._doc.format in {"tex", "latex"}:
516
            self._caption = self._caption.replace("%p", "\\pageref{" + self._tag + "}")
517
518
        # Compute content
519
        if isinstance(self._elem, DefinitionItem):
520
            replace_description(Plain(*self._elem.term), self._description)
521
            replace_title(Plain(*self._elem.term), self._title)
522
            replace_global_number(Plain(*self._elem.term), self._global_number)
523
            replace_section_number(Plain(*self._elem.term), self._section_number)
524
            replace_local_number(Plain(*self._elem.term), self._local_number)
525
        else:
526
            replace_description(self._elem, self._description)
527
            replace_title(self._elem, self._title)
528
            replace_global_number(self._elem, self._global_number)
529
            replace_section_number(self._elem, self._section_number)
530
            replace_local_number(self._elem, self._local_number)
531
532
        # Compute link
533
        replace_description(self._link, self._description)
534
        replace_title(self._link, self._title)
535
        replace_global_number(self._link, self._global_number)
536
        replace_section_number(self._link, self._section_number)
537
        replace_local_number(self._link, self._local_number)
538
        if self._doc.format in {"tex", "latex"}:
539
            replace_page_number(self._link, self._tag)
540
541
        # Compute entry
542
        replace_description(self._entry, self._description)
543
        replace_title(self._entry, self._title)
544
        replace_global_number(self._entry, self._global_number)
545
        replace_section_number(self._entry, self._section_number)
546
        replace_local_number(self._entry, self._local_number)
547
548
        # Finalize the content
549
        if self._doc.format in {"tex", "latex"}:
550
            latex_category = re.sub("[^a-z]+", "", self._basic_category)
551
            latex = (
552
                "\\phantomsection"
553
                f"\\addcontentsline{{{latex_category}}}{{{latex_category}}}"
554
                f"{{\\protect\\numberline {{{self._leading + self._number}}}"
555
                f"{{\\ignorespaces {to_latex(self._entry)}"
556
                "}}"
557
            )
558
            self._get_content().insert(0, RawInline(latex, "tex"))
559
560
561
def replace_description(where, description):
562
    """
563
    Replace description in where.
564
565
    Arguments
566
    ---------
567
    where
568
        where to replace
569
    description
570
        replace %D and %d by description
571
    """
572
    where.walk(partial(replacing, search="%D", replace=copy.deepcopy(description)))
573
    where.walk(
574
        partial(
575
            replacing,
576
            search="%d",
577
            replace=[item.walk(lowering) for item in copy.deepcopy(description)],
578
        )
579
    )
580
581
582
def replace_title(where, title):
583
    """
584
    Replace title in where.
585
586
    Arguments
587
    ---------
588
    where
589
        where to replace
590
    title
591
        replace %T and %t by title
592
    """
593
    where.walk(partial(replacing, search="%T", replace=copy.deepcopy(title)))
594
    where.walk(
595
        partial(
596
            replacing,
597
            search="%t",
598
            replace=[item.walk(lowering) for item in copy.deepcopy(title)],
599
        )
600
    )
601
602
603
def replace_section_number(where, section_number):
604
    """
605
    Replace section number in where.
606
607
    Arguments
608
    ---------
609
    where
610
        where to replace
611
    section_number
612
        replace %s by section_number
613
    """
614
    where.walk(partial(replacing, search="%s", replace=[Str(section_number)]))
615
616
617
def replace_global_number(where, global_number):
618
    """
619
    Replace global number in where.
620
621
    Arguments
622
    ---------
623
    where
624
        where to replace
625
    global_number
626
        replace %g by global_number
627
    """
628
    where.walk(partial(replacing, search="%g", replace=[Str(global_number)]))
629
630
631
def replace_local_number(where, local_number):
632
    """
633
    Replace local number in where.
634
635
    Arguments
636
    ---------
637
    where
638
        where to replace
639
    local_number
640
        replace %n and # by local_number
641
    """
642
    where.walk(partial(replacing, search="%n", replace=[Str(local_number)]))
643
    where.walk(partial(replacing, search="#", replace=[Str(local_number)]))
644
645
646
def replace_page_number(where, tag):
647
    """
648
    Replace page number in where.
649
650
    Arguments
651
    ---------
652
    where
653
        where to replace
654
    tag
655
        replace %p by tag
656
    """
657
    where.walk(
658
        partial(
659
            replacing, search="%p", replace=[RawInline("\\pageref{" + tag + "}", "tex")]
660
        )
661
    )
662
663
664
def replace_count(where, count):
665
    """
666
    Replace count in where.
667
668
    Arguments
669
    ---------
670
    where
671
        where to replace
672
    count
673
        replace %c by count
674
    """
675
    where.walk(partial(replacing, search="%c", replace=[Str(count)]))
676
677
678
def remove_useless_latex(elem, _):
679
    """
680
    Clean up LaTeX element for entries.
681
682
    Arguments
683
    ---------
684
    elem
685
        elem to scan
686
687
    Returns
688
    -------
689
        []: if elem is an instance to remove
690
        None: otherwise
691
    """
692
    if isinstance(
693
        elem,
694
        (
695
            BlockQuote,
696
            BulletList,
697
            Citation,
698
            Cite,
699
            CodeBlock,
700
            Definition,
701
            DefinitionItem,
702
            DefinitionList,
703
            Div,
704
            Header,
705
            HorizontalRule,
706
            Image,
707
            LineBlock,
708
            LineBreak,
709
            LineItem,
710
            ListItem,
711
            Note,
712
            Para,
713
            RawBlock,
714
            RawInline,
715
            SoftBreak,
716
            Table,
717
            TableCell,
718
            TableRow,
719
        ),
720
    ):
721
        return []
722
    return None
723
724
725
def to_latex(elem):
726
    """
727
    Convert element to LaTeX.
728
729
    Arguments
730
    ---------
731
    elem
732
        elem to convert
733
734
    Returns
735
    -------
736
        LaTex string
737
    """
738
    return convert_text(
739
        run_filters([remove_useless_latex], doc=Plain(elem)),
740
        input_format="panflute",
741
        output_format="latex",
742
        extra_args=["--no-highlight"],
743
    )
744
745
746
def define(category, doc):
747
    """
748
    Define a category in document.
749
750
    Arguments
751
    ---------
752
    category
753
        category to define
754
    doc
755
        pandoc document
756
    """
757
    # pylint: disable=line-too-long
758
    doc.defined[category] = {
759
        "first-section-level": 0,
760
        "last-section-level": 0,
761
        "format-text-classic": [Strong(Str("%D"), Space(), Str("%n"))],
762
        "format-text-title": [
763
            Strong(Str("%D"), Space(), Str("%n")),
764
            Space(),
765
            Emph(Str("(%T)")),
766
        ],
767
        "format-link-classic": [Str("%D"), Space(), Str("%n")],
768
        "format-link-title": [Str("%D"), Space(), Str("%n"), Space(), Str("(%T)")],
769
        "format-caption-classic": "%D %n",
770
        "format-caption-title": "%D %n (%T)",
771
        "format-entry-title": [Str("%T")],
772
        "classes": [category],
773
        "cite-shortcut": True,
774
        "listing-title": None,
775
        "listing-unnumbered": True,
776
        "listing-unlisted": True,
777
        "listing-identifier": True,
778
        "entry-tab": 1.5,
779
        "entry-space": 2.3,
780
    }
781
    if doc.format == "latex":
782
        doc.defined[category]["format-entry-classic"] = [Str("%D")]
783
        doc.defined[category]["entry-tab"] = 1.5
784
        doc.defined[category]["entry-space"] = 2.3
785
    else:
786
        doc.defined[category]["format-entry-classic"] = [Str("%D"), Space(), Str("%g")]
787
788
789
def lowering(elem, _):
790
    """
791
    Lower element.
792
793
    Arguments
794
    ---------
795
    elem
796
        element to lower
797
    """
798
    if isinstance(elem, Str):
799
        elem.text = elem.text.lower()
800
801
802
def replacing(elem, _, search=None, replace=None):
803
    """
804
    Replace an element.
805
806
    Arguments
807
    ---------
808
    elem
809
        element to scan
810
    search
811
        string to search
812
    replace
813
        string to replace
814
815
    Returns
816
    -------
817
        The modified elements.
818
    """
819
    if isinstance(elem, Str):
820
        search_splitted = elem.text.split(search)
821
        if len(search_splitted) > 1:
822
            text = []
823
824
            if search_splitted[0] != "":
825
                text.append(Str(search_splitted[0]))
826
827
            for string in search_splitted[1:]:
828
                text.extend(replace)
829
                if string != "":
830
                    text.append(Str(string))
831
832
            return text
833
834
    return [elem]
835
836
837
def numbering(elem, doc):
838
    """
839
    Add the numbering of an element.
840
841
    Arguments
842
    ---------
843
    elem
844
        element to number
845
    doc
846
        pandoc document
847
    """
848
    if isinstance(elem, Header):
849
        update_header_numbers(elem, doc)
850
        update_header_aliases(elem, doc)
851
    elif isinstance(elem, (Para, DefinitionItem)):
852
        numbered = Numbered(elem, doc)
853
        if numbered.tag is not None:
854
            doc.information[numbered.tag] = numbered
855
856
857
def referencing(elem, doc):
858
    """
859
    Add a reference for an element.
860
861
    Arguments
862
    ---------
863
    elem
864
        element to reference
865
    doc
866
        pandoc document
867
868
    Returns
869
    -------
870
        A Link or None
871
    """
872
    if isinstance(elem, Link):
873
        return referencing_link(elem, doc)
874
    if isinstance(elem, Cite):
875
        return referencing_cite(elem, doc)
876
    if isinstance(elem, Span) and elem.identifier in doc.information:
877
        replace_count(elem, str(doc.count[doc.information[elem.identifier].category]))
878
    return None
879
880
881
def referencing_link(elem, doc):
882
    """
883
    Add a eference link.
884
885
    Arguments
886
    ---------
887
    elem
888
        element to reference
889
    doc
890
        pandoc document
891
    """
892
    match = re.match("^#(?P<tag>([a-zA-Z][\\w:.-]*))$", elem.url)
893
    if match:
894
        tag = match.group("tag")
895
        if tag in doc.information:
896
            replace_title(elem, doc.information[tag].title)
897
            replace_description(elem, doc.information[tag].description)
898
            replace_global_number(elem, doc.information[tag].global_number)
899
            replace_section_number(elem, doc.information[tag].section_number)
900
            replace_local_number(elem, doc.information[tag].local_number)
901
            replace_count(elem, str(doc.count[doc.information[tag].category]))
902
            if doc.format in {"tex", "latex"}:
903
                replace_page_number(elem, tag)
904
905
            title = stringify(Span(*doc.information[tag].title))
906
            description = stringify(Span(*doc.information[tag].description))
907
            elem.title = elem.title.replace("%t", title.lower())
908
            elem.title = elem.title.replace("%T", title)
909
            elem.title = elem.title.replace("%d", description.lower())
910
            elem.title = elem.title.replace("%D", description)
911
            elem.title = elem.title.replace("%s", doc.information[tag].section_number)
912
            elem.title = elem.title.replace("%g", doc.information[tag].global_number)
913
            elem.title = elem.title.replace("%n", doc.information[tag].local_number)
914
            elem.title = elem.title.replace("#", doc.information[tag].local_number)
915
            elem.title = elem.title.replace(
916
                "%c", str(doc.count[doc.information[tag].category])
917
            )
918
            if doc.format in {"tex", "latex"}:
919
                elem.title = elem.title.replace("%p", "\\pageref{" + tag + "}")
920
921
922
def referencing_cite(elem, doc):
923
    """
924
    Cite reference.
925
926
    Arguments
927
    ---------
928
    elem
929
        element to reference
930
    doc
931
        pandoc document
932
933
    Returns
934
    -------
935
        A Link or None
936
    """
937
    if len(elem.content) == 1 and isinstance(elem.content[0], Str):
938
        match = re.match(
939
            "^(@(?P<tag>(?P<category>[a-zA-Z][\\w.-]*):"
940
            "(([a-zA-Z][\\w.-]*)|(\\d*(\\.\\d*)*))))$",
941
            elem.content[0].text,
942
        )
943
        if match:
944
            category = match.group("category")
945
            if category in doc.defined and doc.defined[category]["cite-shortcut"]:
946
                # Deal with @prefix:name shortcut
947
                tag = match.group("tag")
948
                if tag in doc.information:
949
                    ret = Link(
950
                        doc.information[tag].link,
951
                        url="#" + tag,
952
                        title=doc.information[tag].caption.replace(
953
                            "%c", str(doc.count[doc.information[tag].category])
954
                        ),
955
                    )
956
                    replace_count(ret, str(doc.count[doc.information[tag].category]))
957
                    return ret
958
    return None
959
960
961
def update_header_numbers(elem, doc):
962
    """
963
    Update header numbers.
964
965
    Arguments
966
    ---------
967
    elem
968
        element to update
969
    doc
970
        pandoc document
971
    """
972
    if "unnumbered" not in elem.classes:
973
        doc.headers[elem.level - 1] = doc.headers[elem.level - 1] + 1
974
        for index in range(elem.level, 6):
975
            doc.headers[index] = 0
976
977
978
def update_header_aliases(elem, doc):
979
    """
980
    Update header aliases.
981
982
    Arguments
983
    ---------
984
    elem
985
        element to update
986
    doc
987
        pandoc document
988
    """
989
    doc.aliases[elem.level - 1] = elem.identifier
990
    for index in range(elem.level, 6):
991
        doc.aliases[index] = ""
992
993
994
def prepare(doc):
995
    """
996
    Prepare document.
997
998
    Arguments
999
    ---------
1000
    doc
1001
        pandoc document
1002
    """
1003
    doc.headers = [0, 0, 0, 0, 0, 0]
1004
    doc.aliases = ["", "", "", "", "", ""]
1005
    doc.information = {}
1006
    doc.defined = {}
1007
1008
    if "pandoc-numbering" in doc.metadata.content and isinstance(
1009
        doc.metadata.content["pandoc-numbering"], MetaMap
1010
    ):
1011
        for category, definition in doc.metadata.content[
1012
            "pandoc-numbering"
1013
        ].content.items():
1014
            if isinstance(definition, MetaMap):
1015
                add_definition(category, definition, doc)
1016
1017
    doc.count = {}
1018
    doc.collections = {}
1019
1020
1021
def add_definition(category, definition, doc):
1022
    """
1023
    Add definition for a category.
1024
1025
    Arguments
1026
    ---------
1027
    category
1028
        The category
1029
    definition
1030
        The definition
1031
    doc
1032
        The pandoc document
1033
    """
1034
    # Create the category with options by default
1035
    define(category, doc)
1036
1037
    # Detect general options
1038
    if "general" in definition:
1039
        meta_cite(category, definition["general"], doc.defined)
1040
        meta_listing(category, definition["general"], doc.defined)
1041
        meta_levels(category, definition["general"], doc.defined)
1042
        meta_classes(category, definition["general"], doc.defined)
1043
1044
    # Detect LaTeX options
1045
    if doc.format in {"tex", "latex"}:
1046
        if "latex" in definition:
1047
            meta_format_text(category, definition["latex"], doc.defined)
1048
            meta_format_link(category, definition["latex"], doc.defined)
1049
            meta_format_caption(category, definition["latex"], doc.defined)
1050
            meta_format_entry(category, definition["latex"], doc.defined)
1051
            meta_entry_tab(category, definition["latex"], doc.defined)
1052
            meta_entry_space(category, definition["latex"], doc.defined)
1053
    # Detect standard options
1054
    else:
1055
        if "standard" in definition:
1056
            meta_format_text(category, definition["standard"], doc.defined)
1057
            meta_format_link(category, definition["standard"], doc.defined)
1058
            meta_format_caption(category, definition["standard"], doc.defined)
1059
            meta_format_entry(category, definition["standard"], doc.defined)
1060
1061
1062
def meta_cite(category, definition, defined):
1063
    """
1064
    Compute cite for a category.
1065
1066
    Arguments
1067
    ---------
1068
    category
1069
        The category
1070
    definition
1071
        The definition
1072
    defined
1073
        The defined parameter
1074
    """
1075
    if "cite-shortcut" in definition:
1076
        if isinstance(definition["cite-shortcut"], MetaBool):
1077
            defined[category]["cite-shortcut"] = definition["cite-shortcut"].boolean
1078
        else:
1079
            debug(
1080
                "[WARNING] pandoc-numbering: cite-shortcut is not correct for category "
1081
                + category
1082
            )
1083
1084
1085
def meta_format(category, definition, defined, tag):
1086
    """
1087
    Compute format text for a category and a tag.
1088
1089
    Arguments
1090
    ---------
1091
    category
1092
        The category
1093
    definition
1094
        The definition
1095
    defined
1096
        The defined parameter
1097
    tag
1098
        The tag parameter
1099
    """
1100
    if tag in definition:
1101
        if isinstance(definition[tag], MetaInlines):
1102
            # Detach from original parent
1103
            defined[category][tag] = definition[tag].content
1104
            defined[category][tag].parent = None
1105
        else:
1106
            debug(
1107
                f"[WARNING] pandoc-numbering: "
1108
                f"{tag} is not correct for category {category}"
1109
            )
1110
1111
1112
# pylint:disable=too-many-branches
1113
def meta_listing(category, definition, defined):
1114
    """
1115
    Compute listing for a category.
1116
1117
    Arguments
1118
    ---------
1119
    category
1120
        The category
1121
    definition
1122
        The definition
1123
    defined
1124
        The defined parameter
1125
    """
1126
    meta_format(category, definition, defined, "listing-title")
1127
    for key in ("listing-unnumbered", "listing-unlisted"):
1128
        if key in definition:
1129
            if isinstance(definition[key], MetaBool):
1130
                defined[category][key] = definition[key].boolean
1131
            else:
1132
                debug(
1133
                    f"[WARNING] pandoc-numbering: "
1134
                    f"{key} is not correct for category {category}"
1135
                )
1136
    if "listing-identifier" in definition:
1137
        if isinstance(definition["listing-identifier"], MetaBool):
1138
            defined[category]["listing-identifier"] = definition[
1139
                "listing-identifier"
1140
            ].boolean
1141
        elif (
1142
            isinstance(definition["listing-identifier"], MetaInlines)
1143
            and len(definition["listing-identifier"].content) == 1
1144
            and isinstance(definition["listing-identifier"].content[0], Str)
1145
        ):
1146
            defined[category]["listing-identifier"] = (
1147
                definition["listing-identifier"].content[0].text
1148
            )
1149
        else:
1150
            debug(
1151
                "[WARNING] pandoc-numbering: "
1152
                "listing-identifier is not correct for category " + category
1153
            )
1154
1155
1156
def meta_format_text(category, definition, defined):
1157
    """
1158
    Compute format text for a category.
1159
1160
    Arguments
1161
    ---------
1162
    category
1163
        The category
1164
    definition
1165
        The definition
1166
    defined
1167
        The defined parameter
1168
    """
1169
    meta_format(category, definition, defined, "format-text-classic")
1170
    meta_format(category, definition, defined, "format-text-title")
1171
1172
1173
def meta_format_link(category, definition, defined):
1174
    """
1175
    Compute format link for a category.
1176
1177
    Arguments
1178
    ---------
1179
    category
1180
        The category
1181
    definition
1182
        The definition
1183
    defined
1184
        The defined parameter
1185
    """
1186
    meta_format(category, definition, defined, "format-link-classic")
1187
    meta_format(category, definition, defined, "format-link-title")
1188
1189
1190
def meta_format_entry(category, definition, defined):
1191
    """
1192
    Compute format entry for a category.
1193
1194
    Arguments
1195
    ---------
1196
    category
1197
        The category
1198
    definition
1199
        The definition
1200
    defined
1201
        The defined parameter
1202
    """
1203
    meta_format(category, definition, defined, "format-entry-classic")
1204
    meta_format(category, definition, defined, "format-entry-title")
1205
1206
1207
def meta_format_caption(category, definition, defined):
1208
    """
1209
    Compute format caption for a category.
1210
1211
    Arguments
1212
    ---------
1213
    category
1214
        The category
1215
    definition
1216
        The definition
1217
    defined
1218
        The defined parameter
1219
    """
1220
    for tag in ("format-caption-classic", "format-caption-title"):
1221
        if tag in definition:
1222
            if isinstance(definition[tag], MetaInlines):
1223
                defined[category][tag] = stringify(definition[tag])
1224
            else:
1225
                debug(
1226
                    f"[WARNING] pandoc-numbering: "
1227
                    f"{tag} is not correct for category {category}"
1228
                )
1229
1230
1231
def meta_entry(category, definition, defined, tag):
1232
    """
1233
    Compute entry tab for a category.
1234
1235
    Arguments
1236
    ---------
1237
    category
1238
        The category
1239
    definition
1240
        The definition
1241
    defined
1242
        The defined parameter
1243
    tag
1244
        The tag parameter
1245
    """
1246
    if tag in definition:
1247
        if isinstance(definition[tag], MetaString):
1248
            value = definition[tag].text
1249
        elif (
1250
            isinstance(definition[tag], MetaInlines)
1251
            and len(definition[tag].content) == 1
1252
        ):
1253
            value = definition[tag].content[0].text
1254
        else:
1255
            debug(
1256
                f"[WARNING] pandoc-numbering: "
1257
                f"{tag} is not correct for category {category}"
1258
            )
1259
            return
1260
        # Get the element
1261
        try:
1262
            element = float(value)
1263
            if element > 0:
1264
                defined[category][tag] = element
1265
            else:
1266
                debug(
1267
                    f"[WARNING] pandoc-numbering: "
1268
                    f"{tag} must be positive for category {category}"
1269
                )
1270
        except ValueError:
1271
            debug(
1272
                f"[WARNING] pandoc-numbering: "
1273
                f"{tag} is not correct for category {category}"
1274
            )
1275
1276
1277
def meta_entry_tab(category, definition, defined):
1278
    """
1279
    Compute entry tab for a category.
1280
1281
    Arguments
1282
    ---------
1283
    category
1284
        The category
1285
    definition
1286
        The definition
1287
    defined
1288
        The defined parameter
1289
    """
1290
    meta_entry(category, definition, defined, "entry-tab")
1291
1292
1293
def meta_entry_space(category, definition, defined):
1294
    """
1295
    Compute entry space for a category.
1296
1297
    Arguments
1298
    ---------
1299
    category
1300
        The category
1301
    definition
1302
        The definition
1303
    defined
1304
        The defined parameter
1305
    """
1306
    meta_entry(category, definition, defined, "entry-space")
1307
1308
1309
def meta_levels(category, definition, defined):
1310
    """
1311
    Compute level for a category.
1312
1313
    Arguments
1314
    ---------
1315
    category
1316
        The category
1317
    definition
1318
        The definition
1319
    defined
1320
        The defined parameter
1321
    """
1322
    if (
1323
        "sectioning-levels" in definition
1324
        and isinstance(definition["sectioning-levels"], MetaInlines)
1325
        and len(definition["sectioning-levels"].content) == 1
1326
    ):
1327
        match = re.match(
1328
            Numbered.header_regex, definition["sectioning-levels"].content[0].text
1329
        )
1330
        if match:
1331
            # Compute the first and last levels section
1332
            defined[category]["first-section-level"] = len(match.group("hidden")) // 2
1333
            defined[category]["last-section-level"] = len(match.group("header")) // 2
1334 View Code Duplication
    if "first-section-level" in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1335
        if isinstance(definition["first-section-level"], MetaString):
1336
            value = definition["first-section-level"].text
1337
        elif (
1338
            isinstance(definition["first-section-level"], MetaInlines)
1339
            and len(definition["first-section-level"].content) == 1
1340
        ):
1341
            value = definition["first-section-level"].content[0].text
1342
        else:
1343
            debug(
1344
                "[WARNING] pandoc-numbering: "
1345
                "first-section-level is not correct for category " + category
1346
            )
1347
            return
1348
1349
        # Get the level
1350
        try:
1351
            level = int(value) - 1
1352
        except ValueError:
1353
            debug(
1354
                "[WARNING] pandoc-numbering: "
1355
                "first-section-level is not correct for category " + category
1356
            )
1357
1358
        if 0 <= level <= 6:
1359
            defined[category]["first-section-level"] = level
1360
        else:
1361
            # pylint: disable=line-too-long
1362
            debug(
1363
                "[WARNING] pandoc-numbering: "
1364
                "first-section-level must be positive or zero for category " + category
1365
            )
1366
1367 View Code Duplication
    if "last-section-level" in definition:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1368
        if isinstance(definition["last-section-level"], MetaString):
1369
            value = definition["last-section-level"].text
1370
        elif (
1371
            isinstance(definition["last-section-level"], MetaInlines)
1372
            and len(definition["last-section-level"].content) == 1
1373
        ):
1374
            value = definition["last-section-level"].content[0].text
1375
        else:
1376
            debug(
1377
                "[WARNING] pandoc-numbering: "
1378
                "last-section-level is not correct for category " + category
1379
            )
1380
            return
1381
1382
        # Get the level
1383
        try:
1384
            level = int(value)
1385
        except ValueError:
1386
            debug(
1387
                "[WARNING] pandoc-numbering: "
1388
                "last-section-level is not correct for category " + category
1389
            )
1390
1391
        if 0 <= level <= 6:
1392
            defined[category]["last-section-level"] = level
1393
        else:
1394
            # pylint: disable=line-too-long
1395
            debug(
1396
                "[WARNING] pandoc-numbering: "
1397
                "last-section-level must be positive or zero for category " + category
1398
            )
1399
1400
1401
def meta_classes(category, definition, defined):
1402
    """
1403
    Compute classes for a category.
1404
1405
    Arguments
1406
    ---------
1407
    category
1408
        The category
1409
    definition
1410
        The definition
1411
    defined
1412
        The defined parameter
1413
    """
1414
    if "classes" in definition and isinstance(definition["classes"], MetaList):
1415
        defined[category]["classes"] = [
1416
            stringify(elt) for elt in definition["classes"].content
1417
        ]
1418
1419
1420
def finalize(doc):
1421
    """
1422
    Finalize document.
1423
1424
    Arguments
1425
    ---------
1426
    doc
1427
        The pandoc document
1428
    """
1429
    # Loop on all listings definition
1430
1431
    if doc.format in {"tex", "latex"}:
1432
        # Add header-includes if necessary
1433
        if "header-includes" not in doc.metadata:
1434
            doc.metadata["header-includes"] = MetaList()
1435
        # Convert header-includes to MetaList if necessary
1436
        elif not isinstance(doc.metadata["header-includes"], MetaList):
1437
            doc.metadata["header-includes"] = MetaList(doc.metadata["header-includes"])
1438
1439
        doc.metadata["header-includes"].append(
1440
            MetaInlines(
1441
                RawInline(
1442
                    dedent(
1443
                        r"""
1444
                        \makeatletter
1445
                        \@ifpackageloaded{subfig}{
1446
                            \usepackage[subfigure]{tocloft}
1447
                        }{
1448
                            \usepackage{tocloft}
1449
                        }
1450
                        \makeatother
1451
                        """
1452
                    ),
1453
                    "tex",
1454
                )
1455
            )
1456
        )
1457
        doc.metadata["header-includes"].append(
1458
            MetaInlines(RawInline(r"\usepackage{etoolbox}", "tex"))
1459
        )
1460
1461
    i = 0
1462
    listof = []
1463
    for category, definition in doc.defined.items():
1464
        if definition["listing-title"] is not None:
1465
            # pylint: disable=consider-using-f-string
1466
            if doc.format in {"tex", "latex"}:
1467
                latex_category = re.sub("[^a-z]+", "", category)
1468
                latex = (
1469
                    r"\newlistof{%s}{%s}{%s}"
1470
                    r"\renewcommand{\cft%stitlefont}{\cfttoctitlefont}"
1471
                    r"\setlength{\cft%snumwidth}{\cftfignumwidth}"
1472
                    r"\setlength{\cft%sindent}{\cftfigindent}"
1473
                    % (
1474
                        latex_category,
1475
                        latex_category,
1476
                        convert_text(
1477
                            Plain(*definition["listing-title"]),
1478
                            input_format="panflute",
1479
                            output_format="latex",
1480
                        ),
1481
                        latex_category,
1482
                        latex_category,
1483
                        latex_category,
1484
                    )
1485
                )
1486
                doc.metadata["header-includes"].append(
1487
                    MetaInlines(RawInline(latex, "tex"))
1488
                )
1489
                listof.append(f"\\listof{latex_category}")
1490
            else:
1491
                classes = ["pandoc-numbering-listing"] + definition["classes"]
1492
1493
                if definition["listing-unnumbered"]:
1494
                    classes.append("unnumbered")
1495
1496
                if definition["listing-unlisted"]:
1497
                    classes.append("unlisted")
1498
1499
                if definition["listing-identifier"] is False:
1500
                    header = Header(
1501
                        *definition["listing-title"], level=1, classes=classes
1502
                    )
1503
                elif definition["listing-identifier"] is True:
1504
                    header = Header(
1505
                        *definition["listing-title"], level=1, classes=classes
1506
                    )
1507
                    header = convert_text(
1508
                        convert_text(
1509
                            header, input_format="panflute", output_format="markdown"
1510
                        ),
1511
                        output_format="panflute",
1512
                    )[0]
1513
                else:
1514
                    header = Header(
1515
                        *definition["listing-title"],
1516
                        level=1,
1517
                        classes=classes,
1518
                        identifier=definition["listing-identifier"],
1519
                    )
1520
1521
                doc.content.insert(i, header)
1522
                i = i + 1
1523
1524
                table = table_other(doc, category, definition)
1525
1526
                if table:
1527
                    doc.content.insert(i, table)
1528
                    i = i + 1
1529
1530
    if doc.format in {"tex", "latex"}:
1531
        header = (
1532
            r"\ifdef{\mainmatter}"
1533
            r"{\let\oldmainmatter\mainmatter"
1534
            r"\renewcommand{\mainmatter}[0]{%s\oldmainmatter}}"
1535
            r"{}"
1536
        )
1537
        doc.metadata["header-includes"].append(
1538
            MetaInlines(RawInline(header % "\n".join(listof), "tex"))
1539
        )
1540
1541
        latex = r"\ifdef{\mainmatter}{}{%s}"
1542
        doc.content.insert(0, RawBlock(latex % "\n".join(listof), "tex"))
1543
1544
1545
def table_other(doc, category, _):
1546
    """
1547
    Compute other code for table.
1548
1549
    Arguments
1550
    ---------
1551
    doc
1552
        pandoc document
1553
    category
1554
        category numbered
1555
1556
    Returns
1557
    -------
1558
        A BulletList or None
1559
    """
1560
    if category in doc.collections:
1561
        # Return a bullet list
1562
        return BulletList(
1563
            *(
1564
                ListItem(Plain(Link(doc.information[tag].entry, url="#" + tag)))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable tag does not seem to be defined.
Loading history...
1565
                for tag in doc.collections[category]
1566
            )
1567
        )
1568
    return None
1569
1570
1571
def link_color(doc):
1572
    """
1573
    Compute LaTeX code for toc.
1574
1575
    Arguments
1576
    ---------
1577
    doc
1578
        pandoc document
1579
1580
    Returns
1581
    -------
1582
        LaTeX code for links.
1583
    """
1584
    # Get the link color
1585
    metadata = doc.get_metadata()
1586
    if "toccolor" in metadata:
1587
        return "\\hypersetup{linkcolor=" + str(metadata["toccolor"]) + "}"
1588
    return "\\hypersetup{linkcolor=black}"
1589
1590
1591
def main(doc=None) -> None:
1592
    """
1593
    Produce the final document.
1594
1595
    Parameters
1596
    ----------
1597
    doc
1598
        pandoc document
1599
    """
1600
    run_filters([numbering, referencing], prepare=prepare, doc=doc, finalize=finalize)
1601