Passed
Push — develop ( 9104d5...9ddb8e )
by Christophe
01:08
created

pandoc_numbering._main.Numbered.identifier()   A

Complexity

Conditions 1

Size

Total Lines 22
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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