Completed
Push — master ( 60bff7...89c7f7 )
by Christophe
24s
created

finalize()   C

Complexity

Conditions 7

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
c 0
b 0
f 0
dl 0
loc 22
rs 5.7894
1
#!/usr/bin/env python
2
3
"""
4
Pandoc filter to number all kinds of things.
5
"""
6
7
from panflute import *
8
from functools import partial
9
import re
10
import unicodedata
11
import copy
12
import itertools
13
14
class Numbered(object):
15
    __slots__ = [
16
        '_elem',
17
        '_doc',
18
        '_match',
19
        '_tag',
20
        '_entry',
21
        '_link',
22
        '_title',
23
        '_description',
24
        '_category',
25
        '_basic_category',
26
        '_first_section_level',
27
        '_last_section_level',
28
        '_leading',
29
        '_number',
30
        '_global_number',
31
        '_section_number',
32
        '_local_number',
33
    ]
34
35
    @property
36
    def tag(self):
37
        return self._tag
38
39
    @property
40
    def entry(self):
41
        return self._entry
42
43
    @property
44
    def link(self):
45
        return self._link
46
47
    @property
48
    def title(self):
49
        return self._title
50
51
    @property
52
    def description(self):
53
        return self._description
54
55
    @property
56
    def global_number(self):
57
        return self._global_number
58
59
    @property
60
    def section_number(self):
61
        return self._section_number
62
63
    @property
64
    def local_number(self):
65
        return self._local_number
66
67
    number_regex = '#((?P<prefix>[a-zA-Z][\w.-]*):)?(?P<name>[a-zA-Z][\w:.-]*)?'
68
    _regex = '(?P<header>(?P<hidden>(-\.)*)(\+\.)*)'
69
    header_regex = '^' + _regex + '$'
70
    marker_regex = '^' + _regex + number_regex + '$'
71
    double_sharp_regex = '^' + _regex + '#' + number_regex + '$'
72
73
    @staticmethod
74
    def _remove_accents(string):
75
        nfkd_form = unicodedata.normalize('NFKD', string)
76
        return u''.join([c for c in nfkd_form if not unicodedata.combining(c)])
77
78
    @staticmethod
79
    def _identifier(string):
80
        # replace invalid characters by dash
81
        string = re.sub('[^0-9a-zA-Z_-]+', '-', Numbered._remove_accents(string.lower()))
82
83
        # Remove leading digits
84
        string = re.sub('^[^a-zA-Z]+', '', string)
85
86
        return string
87
88
    def __init__(self, elem, doc):
89
        self._elem = elem
90
        self._doc = doc
91
        self._entry = Span(classes=['pandoc-numbering-entry'])
92
        self._link = Span(classes=['pandoc-numbering-link'])
93
        self._tag = None
94
        if len(self._get_content()) > 0 and isinstance(self._get_content()[-1], Str):
95
            self._match = re.match(Numbered.marker_regex, self._get_content()[-1].text)
96
            if self._match:
97
                self._replace_marker()
98
            elif re.match(Numbered.double_sharp_regex, self._get_content()[-1].text):
99
                self._replace_double_sharp()
100
101
    def _set_content(self, content):
102
        if isinstance(self._elem, Para):
103
            self._elem.content = content
104
        elif isinstance(self._elem, DefinitionItem):
105
            self._elem.term = content
106
107
    def _get_content(self):
108
        if isinstance(self._elem, Para):
109
            return self._elem.content
110
        elif isinstance(self._elem, DefinitionItem):
111
            return self._elem.term
112
113
    def _replace_double_sharp(self):
114
        self._get_content()[-1].text = self._get_content()[-1].text.replace('##', '#', 1)
115
116
    def _replace_marker(self):
117
        self._compute_title()
118
        self._compute_description()
119
        self._compute_basic_category()
120
        self._compute_levels()
121
        self._compute_section_number()
122
        self._compute_leading()
123
        self._compute_category()
124
        self._compute_number()
125
        self._compute_tag()
126
        self._compute_local_number()
127
        self._compute_global_number()
128
        self._compute_data()
129
130
    def _compute_title(self):
131
        self._title = []
132
        if isinstance(self._get_content()[-3], Str) and self._get_content()[-3].text[-1:] == ')':
133
            for (i, item) in enumerate(self._get_content()):
134
                if isinstance(item, Str) and item.text[0] == '(':
135
                    self._title = self._get_content()[i:-2]
136
                    # Detach from original parent
137
                    self._title.parent = None
138
                    self._title[0].text = self._title[0].text[1:]
139
                    self._title[-1].text = self._title[-1].text[:-1]
140
                    del self._get_content()[i-1:-2]
141
                    break
142
        self._title = list(self._title)
143
        
144
    def _compute_description(self):
145
        self._description = self._get_content()[:-2]
146
        # Detach from original parent
147
        self._description.parent = None
148
        self._description = list(self._description)
149
150
    def _compute_basic_category(self):
151
        if self._match.group('prefix') == None:
152
            self._basic_category = Numbered._identifier(''.join(map(stringify, self._description)))
153
        else:
154
            self._basic_category = self._match.group('prefix')
155
        if self._basic_category not in self._doc.defined:
156
            define(self._basic_category, self._doc)
157
158
    def _compute_levels(self):
159
        # Compute the first and last section level values
160
        self._first_section_level = len(self._match.group('hidden')) // 2
161
        self._last_section_level = len(self._match.group('header')) // 2
162
163
        # Get the default first and last section level
164
        if self._first_section_level == 0 and self._last_section_level == 0:
165
            self._first_section_level = self._doc.defined[self._basic_category]['first-section-level']
166
            self._last_section_level = self._doc.defined[self._basic_category]['last-section-level']
167
168
    def _compute_section_number(self):
169
        self._section_number = '.'.join(map(str, self._doc.headers[:self._last_section_level]))
170
171
    def _compute_leading(self):
172
        # Compute the leading (composed of the section numbering and a dot)
173
        if self._last_section_level != 0:
174
            self._leading = self._section_number + '.'
175
        else:
176
            self._leading =  ''
177
178
    def _compute_category(self):
179
        self._category = self._basic_category + ':' + self._leading
180
181
        # Is it a new category?
182
        if self._category not in self._doc.count:
183
            self._doc.count[self._category] = 0
184
185
        self._doc.count[self._category] = self._doc.count[self._category] + 1
186
187
    def _compute_number(self):
188
        self._number = str(self._doc.count[self._category])
189
190
    def _compute_tag(self):
191
        # Determine the final tag
192
        if self._match.group('name') == None:
193
            self._tag = self._category + self._number
194
        else:
195
            self._tag = self._basic_category + ':' + self._match.group('name')
196
197
        # Compute collections
198
        if self._basic_category not in self._doc.collections:
199
            self._doc.collections[self._basic_category] = []
200
201
        self._doc.collections[self._basic_category].append(self._tag)
202
203
    def _compute_local_number(self):
204
        # Replace the '-.-.+.+...#' by the category count (omitting the hidden part)
205
        self._local_number = '.'.join(map(str, self._doc.headers[self._first_section_level:self._last_section_level] + [self._number]))
206
207
    def _compute_global_number(self):
208
        # Compute the global number
209
        if self._section_number:
210
            self._global_number = self._section_number + '.' + self._number
211
        else:
212
            self._global_number = self._number
213
214
    def _compute_data(self):
215
        classes = self._doc.defined[self._basic_category]['classes']
216
        self._set_content([Span(
217
            classes=['pandoc-numbering-text'] + classes,
218
            identifier=self._tag
219
        )])
220
        self._link.classes = self._link.classes + classes
221
        self._entry.classes = self._entry.classes + classes
222
223
        # Prepare the final data
224
        if self._title: 
225
            self._get_content()[0].content = copy.deepcopy(self._doc.defined[self._basic_category]['format-text-title'])
226
            self._link.content = copy.deepcopy(self._doc.defined[self._basic_category]['format-link-title'])
227
            self._entry.content = copy.deepcopy(self._doc.defined[self._basic_category]['format-entry-title'])
228
        else:
229
            self._get_content()[0].content = copy.deepcopy(self._doc.defined[self._basic_category]['format-text-classic'])
230
            self._link.content = copy.deepcopy(self._doc.defined[self._basic_category]['format-link-classic'])
231
            self._entry.content = copy.deepcopy(self._doc.defined[self._basic_category]['format-entry-classic'])
232
233
        # Compute content
234
        replace_description(self._elem, self._description)
235
        replace_title(self._elem, self._title)
236
        replace_global_number(self._elem, self._global_number)
237
        replace_section_number(self._elem, self._section_number)
238
        replace_local_number(self._elem, self._local_number)
239
240
        ## Compute link
241
        replace_description(self._link, self._description)
242
        replace_title(self._link, self._title)
243
        replace_global_number(self._link, self._global_number)
244
        replace_section_number(self._link, self._section_number)
245
        replace_local_number(self._link, self._local_number)
246
        if self._doc.format == 'latex':
247
            replace_page_number(self._link, self._tag)
248
249
        # Compute entry
250
        replace_description(self._entry, self._description)
251
        replace_title(self._entry, self._title)
252
        replace_global_number(self._entry, self._global_number)
253
        replace_section_number(self._entry, self._section_number)
254
        replace_local_number(self._entry, self._local_number)
255
        
256
        # Finalize the content
257
        if self._doc.format == 'latex':
258
            self._get_content()[0].content.insert(0, RawInline('\\label{' + self._tag + '}', 'tex'))
259
260
            latex_category = re.sub('[^a-z]+', '', self._basic_category)
261
            latex = '\\phantomsection\\addcontentsline{' + \
262
                latex_category + \
263
                '}{' + \
264
                latex_category + \
265
                '}{\\protect\\numberline {' + \
266
                self._leading + \
267
                self._number + \
268
                '}{\ignorespaces ' + \
269
                to_latex(self._entry) + \
270
                '}}'
271
            self._get_content().insert(0, RawInline(latex, 'tex'))
272
273
def replace_description(where, description):
274
    where.walk(partial(replacing, search='%D', replace=copy.deepcopy(description)))
275
    where.walk(partial(replacing, search='%d', replace=list(item.walk(lowering) for item in copy.deepcopy(description))))
276
277
def replace_title(where, title):
278
    where.walk(partial(replacing, search='%T', replace=copy.deepcopy(title)))
279
    where.walk(partial(replacing, search='%t', replace=list(item.walk(lowering) for item in copy.deepcopy(title))))
280
281
def replace_section_number(where, section_number):
282
    where.walk(partial(replacing, search='%s', replace=[Str(section_number)]))
283
284
def replace_global_number(where, global_number):
285
    where.walk(partial(replacing, search='%g', replace=[Str(global_number)]))
286
287
def replace_local_number(where, local_number):
288
    where.walk(partial(replacing, search='%n', replace=[Str(local_number)]))
289
    where.walk(partial(replacing, search='#', replace=[Str(local_number)]))
290
291
def replace_page_number(where, tag):
292
    where.walk(partial(replacing, search='%p', replace=[RawInline('\\pageref{' + tag + '}', 'tex')]))
293
294
def to_latex(elem):
295
    return convert_text(Plain(elem), input_format='panflute', output_format='latex', extra_args=['--no-highlight'])
296
297
def define(category, doc):
298
    doc.defined[category] = {
299
        'first-section-level':  0,
300
        'last-section-level':   0,
301
        'format-text-classic':  [Strong(Str('%D'), Space(), Str('%n'))],
302
        'format-text-title':    [Strong(Str('%D'), Space(), Str('%n')), Space(), Emph(Str('(%T)'))],
303
        'format-link-classic':  [Str('%D'), Space(), Str('%n')],
304
        'format-link-title':    [Str('%D'), Space(), Str('%n'), Space(), Str('(%T)')],
305
        'format-entry-title':   [Str('%T')],
306
        'classes':              [category],
307
        'cite-shortcut':        False,
308
        'listing-title':        None,
309
        'listing-unnumbered':   True,
310
        'listing-unlisted':     True,
311
        'entry-tab':            1.5,
312
        'entry-space':          2.3,
313
    }
314
    if doc.format == 'latex':
315
        doc.defined[category]['format-entry-classic'] = [Str('%D')]
316
        doc.defined[category]['entry-tab'] = 1.5
317
        doc.defined[category]['entry-space'] = 2.3
318
    else:
319
        doc.defined[category]['format-entry-classic'] = [Str('%D'), Space(), Str('%g')]
320
321
def lowering(elem, doc):
322
    if isinstance(elem, Str):
323
        elem.text = elem.text.lower()
324
325
def replacing(elem, doc, search=None, replace=None):
326
    if isinstance(elem, Str):
327
        prepare = elem.text.split(search)
328
        if len(prepare) > 1:
329
330
            text = []
331
332
            if prepare[0] != '':
333
                text.append(Str(prepare[0]))
334
335
            for string in prepare[1:]:
336
                text.extend(replace)
337
                if string != '':
338
                    text.append(Str(string))
339
340
            return text
341
342
    return [elem]
343
344
def numbering(elem, doc):
345
    if isinstance(elem, Header):
346
        update_header_numbers(elem, doc)
347
    elif isinstance(elem, (Para, DefinitionItem)):
348
        numbered = Numbered(elem, doc)
349
        if numbered.tag is not None:
350
            doc.information[numbered.tag] = numbered
351
352
def referencing(elem, doc):
353
    if isinstance(elem, Link):
354
        return referencing_link(elem, doc)
355
    elif isinstance(elem, Cite):
356
        return referencing_cite(elem, doc)
357
358
def referencing_link(elem, doc):
359
    match = re.match('^#(?P<tag>([a-zA-Z][\w:.-]*))$', elem.url)
360
    if match:
361
        tag = match.group('tag')
362
        if tag in doc.information:
363
            if bool(elem.content):
364
                replace_title(elem, doc.information[tag].title)
365
                replace_description(elem, doc.information[tag].description)
366
                replace_global_number(elem, doc.information[tag].global_number)
367
                replace_section_number(elem, doc.information[tag].section_number)
368
                replace_local_number(elem, doc.information[tag].local_number)
369
            else:
370
                elem.content = [doc.information[tag].link]
371
372
def referencing_cite(elem, doc):
373
    if len(elem.content) == 1 and isinstance(elem.content[0], Str):
374
        match = re.match('^(@(?P<tag>(?P<category>[a-zA-Z][\w.-]*):(([a-zA-Z][\w.-]*)|(\d*(\.\d*)*))))$', elem.content[0].text)
375
        if match:
376
            category = match.group('category')
377
            if category in doc.defined and doc.defined[category]['cite-shortcut']:
378
                # Deal with @prefix:name shortcut
379
                tag = match.group('tag')
380
                if tag in doc.information:
381
                    return Link(doc.information[tag].link, url = '#' + tag)
382
383
def update_header_numbers(elem, doc):
384
    if 'unnumbered' not in elem.classes:
385
        doc.headers[elem.level - 1] = doc.headers[elem.level - 1] + 1
386
        for index in range(elem.level, 6):
387
            doc.headers[index] = 0
388
389
def prepare(doc):
390
    doc.headers = [0, 0, 0, 0, 0, 0]
391
    doc.information = {}
392
    doc.defined = {}
393
394
    if 'pandoc-numbering' in doc.metadata.content and isinstance(doc.metadata.content['pandoc-numbering'], MetaMap):
395
        for category, definition in doc.metadata.content['pandoc-numbering'].content.items():
396
            if isinstance(definition, MetaMap):
397
                add_definition(category, definition, doc)
398
399
    doc.count = {}
400
    doc.collections = {}
401
402
def add_definition(category, definition, doc):
403
    # Create the category with options by default
404
    define(category, doc)
405
406
    # Detect general options
407
    if 'general' in definition:
408
        meta_cite(category, definition['general'], doc.defined)
409
        meta_listing(category, definition['general'], doc.defined)
410
        meta_levels(category, definition['general'], doc.defined)
411
        meta_classes(category, definition['general'], doc.defined)
412
413
    # Detect LaTeX options
414
    if doc.format == 'latex':
415
        if 'latex' in definition:
416
            meta_format_text(category, definition['latex'], doc.defined)
417
            meta_format_link(category, definition['latex'], doc.defined)
418
            meta_format_entry(category, definition['latex'], doc.defined)
419
            meta_entry_tab(category, definition['latex'], doc.defined)
420
            meta_entry_space(category, definition['latex'], doc.defined)            
421
    # Detect standard options
422
    else:
423
        if 'standard' in definition:
424
            meta_format_text(category, definition['standard'], doc.defined)
425
            meta_format_link(category, definition['standard'], doc.defined)
426
            meta_format_entry(category, definition['standard'], doc.defined)
427
428
def meta_cite(category, definition, defined):
429
    if 'cite-shortcut' in definition:
430
        if isinstance(definition['cite-shortcut'], MetaBool):
431
            defined[category]['cite-shortcut'] = definition['cite-shortcut'].boolean
432
        else:
433
            debug('[WARNING] pandoc-numbering: cite-shortcut is not correct for category ' + category)
434
435
def meta_listing(category, definition, defined):
436
    if 'listing-title' in definition:
437
        if isinstance(definition['listing-title'], MetaInlines):
438
            defined[category]['listing-title'] = definition['listing-title'].content
439
            # Detach from original parent
440
            defined[category]['listing-title'].parent = None
441
        else:
442 View Code Duplication
            debug('[WARNING] pandoc-numbering: listing-title is not correct for category ' + category)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
443
    if 'listing-unnumbered' in definition:
444
        if isinstance(definition['listing-unnumbered'], MetaBool):
445
            defined[category]['listing-unnumbered'] = definition['listing-unnumbered'].boolean
446
        else:
447
            debug('[WARNING] pandoc-numbering: listing-unnumbered is not correct for category ' + category)
448
    if 'listing-unlisted' in definition:
449
        if isinstance(definition['listing-unlisted'], MetaBool):
450
            defined[category]['listing-unlisted'] = definition['listing-unlisted'].boolean
451
        else:
452
            debug('[WARNING] pandoc-numbering: listing-unlisted is not correct for category ' + category)
453
454
def meta_format_text(category, definition, defined):
455
    if 'format-text-classic' in definition:
456
        if isinstance(definition['format-text-classic'], MetaInlines):
457
            defined[category]['format-text-classic'] = definition['format-text-classic'].content
458
            # Detach from original parent
459 View Code Duplication
            defined[category]['format-text-classic'].parent = None
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
460
        else:
461
            debug('[WARNING] pandoc-numbering: format-text-classic is not correct for category ' + category)
462
463
    if 'format-text-title' in definition:
464
        if isinstance(definition['format-text-title'], MetaInlines):
465
            defined[category]['format-text-title'] = definition['format-text-title'].content
466
            # Detach from original parent
467
            defined[category]['format-text-title'].parent = None
468
        else:
469
            debug('[WARNING] pandoc-numbering: format-text-title is not correct for category ' + category)
470
471
def meta_format_link(category, definition, defined):
472
    if 'format-link-classic' in definition:
473
        if isinstance(definition['format-link-classic'], MetaInlines):
474
            defined[category]['format-link-classic'] = definition['format-link-classic'].content
475
            # Detach from original parent
476 View Code Duplication
            defined[category]['format-link-classic'].parent = None
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
477
        else:
478
            debug('[WARNING] pandoc-numbering: format-link-classic is not correct for category ' + category)
479
480
    if 'format-link-title' in definition:
481
        if isinstance(definition['format-link-title'], MetaInlines):
482
            defined[category]['format-link-title'] = definition['format-link-title'].content
483
            # Detach from original parent
484
            defined[category]['format-link-title'].parent = None
485
        else:
486
            debug('[WARNING] pandoc-numbering: format-link-title is not correct for category ' + category)
487
488
def meta_format_entry(category, definition, defined):
489
    if 'format-entry-classic' in definition:
490
        if isinstance(definition['format-entry-classic'], MetaInlines):
491
            defined[category]['format-entry-classic'] = definition['format-entry-classic'].content
492
            # Detach from original parent
493
            defined[category]['format-entry-classic'].parent = None
494
        else:
495
            debug('[WARNING] pandoc-numbering: format-entry-classic is not correct for category ' + category)
496
497
    if 'format-entry-title' in definition:
498
        if isinstance(definition['format-entry-title'], MetaInlines):
499
            defined[category]['format-entry-title'] = definition['format-entry-title'].content
500
            # Detach from original parent
501
            defined[category]['format-entry-title'].parent = None
502
        else:
503
            debug('[WARNING] pandoc-numbering: format-entry-title is not correct for category ' + category)
504
505
def meta_entry_tab(category, definition, defined):
506
    if 'entry-tab' in definition and isinstance(definition['entry-tab'], MetaString):
507
        # Get the tab
508
        try:
509
            tab = float(definition['entry-tab'].text)
510
            if tab > 0:
511
                defined[category]['entry-tab'] = tab
512
            else:
513
                debug('[WARNING] pandoc-numbering: entry-tab must be positive for category ' + category)
514
        except ValueError:
515
            debug('[WARNING] pandoc-numbering: entry-tab is not correct for category ' + category)
516
517
def meta_entry_space(category, definition, defined):
518
    if 'entry-space' in definition and isinstance(definition['entry-space'], MetaString):
519
        # Get the space
520
        try:
521
            space = float(definition['entry-space'].text)
522
            if space > 0:
523
                defined[category]['entry-space'] = space
524
            else:
525
                debug('[WARNING] pandoc-numbering: entry-space must be positive for category ' + category)
526
        except ValueError:
527
            debug('[WARNING] pandoc-numbering: entry-space is not correct for category ' + category)
528
529
def meta_levels(category, definition, defined):
530
    if 'sectioning-levels' in definition and isinstance(definition['sectioning-levels'], MetaInlines) and len(definition['sectioning-levels'].content) == 1:
531
        match = re.match(Numbered.header_regex, definition['sectioning-levels'].content[0].text)
532
        if match:
533
            # Compute the first and last levels section
534
            defined[category]['first-section-level'] =  len(match.group('hidden')) // 2
535
            defined[category]['last-section-level'] = len(match.group('header')) // 2
536
    if 'first-section-level' in definition and isinstance(definition['first-section-level'], MetaString):
537
        # Get the level
538
        try:
539
            level = int(definition['first-section-level'].text) - 1
540
            if level >= 0 and level <= 6 :
541
                defined[category]['first-section-level'] = level
542
            else:
543
                debug('[WARNING] pandoc-numbering: first-section-level must be positive or zero for category ' + category)
544
        except ValueError:
545
            debug('[WARNING] pandoc-numbering: first-section-level is not correct for category ' + category)
546
    if 'last-section-level' in definition and isinstance(definition['last-section-level'], MetaString):
547
        # Get the level
548
        try:
549
            level = int(definition['last-section-level'].text)
550
            if level >= 0 and level <= 6 :
551
                defined[category]['last-section-level'] = level
552
            else:
553
                debug('[WARNING] pandoc-numbering: last-section-level must be positive or zero for category ' + category)
554
        except ValueError:
555
            debug('[WARNING] pandoc-numbering: last-section-level is not correct for category ' + category)
556
557
def meta_classes(category, definition, defined):
558
    if 'classes' in definition and isinstance(definition['classes'], MetaList):
559
        classes = []
560
        for elt in definition['classes'].content:
561
            classes.append(stringify(elt))
562
        defined[category]['classes'] = classes
563
564
def finalize(doc):
565
    listings = []
566
    # Loop on all listings definition
567
    i = 0
568
    for category, definition in doc.defined.items():
569
        if definition['listing-title'] is not None:
570
            classes = ['pandoc-numbering-listing'] + definition['classes']
571
            if definition['listing-unnumbered']:
572
                classes.append('unnumbered')
573
            if definition['listing-unlisted']:
574
                classes.append('unlisted')
575
            doc.content.insert(i, Header(*definition['listing-title'], level=1, classes=classes))
576
            i = i + 1
577
578
            if doc.format == 'latex':
579
                table = table_latex(doc, category, definition)
580
            else:
581
                table = table_other(doc, category, definition)
582
583
            if table:
584
                doc.content.insert(i, table)
585
                i = i + 1
586
587
def table_other(doc, category, definition):
588
    if category in doc.collections:
589
        # Prepare the list
590
        elements = []
591
        # Loop on the collection
592
        for tag in doc.collections[category]:
593
            # Add an item to the list
594
            elements.append(ListItem(Plain(Link(doc.information[tag].entry, url='#' + tag))))
595
        # Return a bullet list
596
        return BulletList(*elements)
597
    else:
598
        return None
599
600
def table_latex(doc, category, definition):
601
    latex_category = re.sub('[^a-z]+', '', category)
602
    latex = [
603
        link_color(doc),
604
        '\\makeatletter',
605
        '\\newcommand*\\l@' + latex_category + '{\\@dottedtocline{1}{' + str(definition['entry-tab']) + 'em}{'+ str(definition['entry-space']) +'em}}',
606
        '\\@starttoc{' + latex_category + '}',
607
        '\\makeatother'
608
    ]
609
    # Return a RawBlock
610
    return RawBlock(''.join(latex), 'tex')
611
612
def link_color(doc):
613
    # Get the link color
614
    metadata = doc.get_metadata()
615
    if 'toccolor' in metadata:
616
        return '\\hypersetup{linkcolor=' + str(metadata['toccolor']) + '}'
617
    else:
618
        return '\\hypersetup{linkcolor=black}'
619
620
def main(doc = None):
621
    run_filters([numbering, referencing], prepare = prepare, doc = doc, finalize = finalize)
622
623
if __name__ == '__main__':
624
    main()
625