Completed
Push — master ( 3949a6...8b9eb6 )
by Christophe
03:03
created

Numbered   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 258
rs 6.8539
wmc 54

27 Methods

Rating   Name   Duplication   Size   Complexity  
A _get_content() 0 5 3
A _compute_tag() 0 12 3
A entry() 0 3 1
A global_number() 0 3 1
B __init__() 0 12 5
A _compute_section_number() 0 2 1
A _compute_levels() 0 9 3
A _remove_accents() 0 4 3
A _compute_global_number() 0 6 2
A _compute_number() 0 2 1
A link() 0 3 1
A _replace_double_sharp() 0 2 1
A title() 0 3 1
A description() 0 3 1
A _compute_category() 0 8 2
A _set_content() 0 5 3
A section_number() 0 3 1
A tag() 0 3 1
A local_number() 0 3 1
A _replace_marker() 0 13 1
A _compute_leading() 0 6 2
A _identifier() 0 9 1
A _compute_description() 0 5 1
A _compute_basic_category() 0 7 3
A _compute_data() 0 58 4
B _compute_title() 0 13 6
A _compute_local_number() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Numbered 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
"""
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')
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
        'entry-tab':            1.5,
310
        'entry-space':          2.3,
311
    }
312
    if doc.format == 'latex':
313
        doc.defined[category]['format-entry-classic'] = [Str('%D')]
314
        doc.defined[category]['entry-tab'] = 1.5
315
        doc.defined[category]['entry-space'] = 2.3
316
    else:
317
        doc.defined[category]['format-entry-classic'] = [Str('%D'), Space(), Str('%g')]
318
319
def lowering(elem, doc):
320
    if isinstance(elem, Str):
321
        elem.text = elem.text.lower()
322
323
def replacing(elem, doc, search=None, replace=None):
324
    if isinstance(elem, Str):
325
        prepare = elem.text.split(search)
326
        if len(prepare) > 1:
327
328
            text = []
329
330
            if prepare[0] != '':
331
                text.append(Str(prepare[0]))
332
333
            for string in prepare[1:]:
334
                text.extend(replace)
335
                if string != '':
336
                    text.append(Str(string))
337
338
            return text
339
340
    return [elem]
341
342
def numbering(elem, doc):
343
    if isinstance(elem, Header):
344
        update_header_numbers(elem, doc)
345
    elif isinstance(elem, (Para, DefinitionItem)):
346
        numbered = Numbered(elem, doc)
347
        if numbered.tag is not None:
348
            doc.information[numbered.tag] = numbered
349
350
def referencing(elem, doc):
351
    if isinstance(elem, Link):
352
        return referencing_link(elem, doc)
353
    elif isinstance(elem, Cite):
354
        return referencing_cite(elem, doc)
355
356
def referencing_link(elem, doc):
357
    match = re.match('^#(?P<tag>([a-zA-Z][\w:.-]*))$', elem.url)
358
    if match:
359
        tag = match.group('tag')
360
        if tag in doc.information:
361
            if bool(elem.content):
362
                replace_title(elem, doc.information[tag].title)
363
                replace_description(elem, doc.information[tag].description)
364
                replace_global_number(elem, doc.information[tag].global_number)
365
                replace_section_number(elem, doc.information[tag].section_number)
366
                replace_local_number(elem, doc.information[tag].local_number)
367
            else:
368
                elem.content = [doc.information[tag].link]
369
370
def referencing_cite(elem, doc):
371
    if len(elem.content) == 1 and isinstance(elem.content[0], Str):
372
        match = re.match('^(@(?P<tag>(?P<category>[a-zA-Z][\w.-]*):(([a-zA-Z][\w.-]*)|(\d*(\.\d*)*))))$', elem.content[0].text)
373
        if match:
374
            category = match.group('category')
375
            if category in doc.defined and doc.defined[category]['cite-shortcut']:
376
                # Deal with @prefix:name shortcut
377
                tag = match.group('tag')
378
                if tag in doc.information:
379
                    return Link(doc.information[tag].link, url = '#' + tag)
380
381
def update_header_numbers(elem, doc):
382
    if 'unnumbered' not in elem.classes:
383
        doc.headers[elem.level - 1] = doc.headers[elem.level - 1] + 1
384
        for index in range(elem.level, 6):
385
            doc.headers[index] = 0
386
387
def prepare(doc):
388
    doc.headers = [0, 0, 0, 0, 0, 0]
389
    doc.information = {}
390
    doc.defined = {}
391
392
    if 'pandoc-numbering' in doc.metadata.content and isinstance(doc.metadata.content['pandoc-numbering'], MetaMap):
393
        for category, definition in doc.metadata.content['pandoc-numbering'].content.items():
394
            if isinstance(definition, MetaMap):
395
                add_definition(category, definition, doc)
396
397
    doc.count = {}
398
    doc.collections = {}
399
400
def add_definition(category, definition, doc):
401
    # Create the category with options by default
402
    define(category, doc)
403
404
    # Detect general options
405
    if 'general' in definition:
406
        meta_cite(category, definition['general'], doc.defined)
407
        meta_listing_title(category, definition['general'], doc.defined)
408
        meta_levels(category, definition['general'], doc.defined)
409
        meta_classes(category, definition['general'], doc.defined)
410
411
    # Detect LaTeX options
412
    if doc.format == 'latex':
413
        if 'latex' in definition:
414
            meta_format_text(category, definition['latex'], doc.defined)
415
            meta_format_link(category, definition['latex'], doc.defined)
416
            meta_format_entry(category, definition['latex'], doc.defined)
417
            meta_entry_tab(category, definition['latex'], doc.defined)
418
            meta_entry_space(category, definition['latex'], doc.defined)            
419
    # Detect standard options
420
    else:
421
        if 'standard' in definition:
422
            meta_format_text(category, definition['standard'], doc.defined)
423
            meta_format_link(category, definition['standard'], doc.defined)
424
            meta_format_entry(category, definition['standard'], doc.defined)
425
426
def meta_cite(category, definition, defined):
427
    if 'cite-shortcut' in definition:
428
        if isinstance(definition['cite-shortcut'], MetaBool):
429
            defined[category]['cite-shortcut'] = definition['cite-shortcut'].boolean
430
        else:
431
            debug('[WARNING] pandoc-numbering: cite-shortcut is not correct for category ' + category)
432
433
def meta_listing_title(category, definition, defined):
434
    if 'listing-title' in definition:
435
        if isinstance(definition['listing-title'], MetaInlines):
436
            defined[category]['listing-title'] = definition['listing-title'].content
437
            # Detach from original parent
438
            defined[category]['listing-title'].parent = None
439
        else:
440
            debug('[WARNING] pandoc-numbering: listing-title is not correct for category ' + category)
441
442 View Code Duplication
def meta_format_text(category, definition, defined):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
443
    if 'format-text-classic' in definition:
444
        if isinstance(definition['format-text-classic'], MetaInlines):
445
            defined[category]['format-text-classic'] = definition['format-text-classic'].content
446
            # Detach from original parent
447
            defined[category]['format-text-classic'].parent = None
448
        else:
449
            debug('[WARNING] pandoc-numbering: format-text-classic is not correct for category ' + category)
450
451
    if 'format-text-title' in definition:
452
        if isinstance(definition['format-text-title'], MetaInlines):
453
            defined[category]['format-text-title'] = definition['format-text-title'].content
454
            # Detach from original parent
455
            defined[category]['format-text-title'].parent = None
456
        else:
457
            debug('[WARNING] pandoc-numbering: format-text-title is not correct for category ' + category)
458
459 View Code Duplication
def meta_format_link(category, definition, defined):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
460
    if 'format-link-classic' in definition:
461
        if isinstance(definition['format-link-classic'], MetaInlines):
462
            defined[category]['format-link-classic'] = definition['format-link-classic'].content
463
            # Detach from original parent
464
            defined[category]['format-link-classic'].parent = None
465
        else:
466
            debug('[WARNING] pandoc-numbering: format-link-classic is not correct for category ' + category)
467
468
    if 'format-link-title' in definition:
469
        if isinstance(definition['format-link-title'], MetaInlines):
470
            defined[category]['format-link-title'] = definition['format-link-title'].content
471
            # Detach from original parent
472
            defined[category]['format-link-title'].parent = None
473
        else:
474
            debug('[WARNING] pandoc-numbering: format-link-title is not correct for category ' + category)
475
476 View Code Duplication
def meta_format_entry(category, definition, defined):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
477
    if 'format-entry-classic' in definition:
478
        if isinstance(definition['format-entry-classic'], MetaInlines):
479
            defined[category]['format-entry-classic'] = definition['format-entry-classic'].content
480
            # Detach from original parent
481
            defined[category]['format-entry-classic'].parent = None
482
        else:
483
            debug('[WARNING] pandoc-numbering: format-entry-classic is not correct for category ' + category)
484
485
    if 'format-entry-title' in definition:
486
        if isinstance(definition['format-entry-title'], MetaInlines):
487
            defined[category]['format-entry-title'] = definition['format-entry-title'].content
488
            # Detach from original parent
489
            defined[category]['format-entry-title'].parent = None
490
        else:
491
            debug('[WARNING] pandoc-numbering: format-entry-title is not correct for category ' + category)
492
493
def meta_entry_tab(category, definition, defined):
494
    if 'entry-tab' in definition and isinstance(definition['entry-tab'], MetaString):
495
        # Get the tab
496
        try:
497
            tab = float(definition['entry-tab'].text)
498
            if tab > 0:
499
                defined[category]['entry-tab'] = tab
500
            else:
501
                debug('[WARNING] pandoc-numbering: entry-tab must be positive for category ' + category)
502
        except ValueError:
503
            debug('[WARNING] pandoc-numbering: entry-tab is not correct for category ' + category)
504
505
def meta_entry_space(category, definition, defined):
506
    if 'entry-space' in definition and isinstance(definition['entry-space'], MetaString):
507
        # Get the space
508
        try:
509
            space = float(definition['entry-space'].text)
510
            if space > 0:
511
                defined[category]['entry-space'] = space
512
            else:
513
                debug('[WARNING] pandoc-numbering: entry-space must be positive for category ' + category)
514
        except ValueError:
515
            debug('[WARNING] pandoc-numbering: entry-space is not correct for category ' + category)
516
517
def meta_levels(category, definition, defined):
518
    if 'sectioning-levels' in definition and isinstance(definition['sectioning-levels'], MetaInlines) and len(definition['sectioning-levels'].content) == 1:
519
        match = re.match(Numbered.header_regex, definition['sectioning-levels'].content[0].text)
520
        if match:
521
            # Compute the first and last levels section
522
            defined[category]['first-section-level'] =  len(match.group('hidden')) // 2
523
            defined[category]['last-section-level'] = len(match.group('header')) // 2
524
    if 'first-section-level' in definition and isinstance(definition['first-section-level'], MetaString):
525
        # Get the level
526
        try:
527
            level = int(definition['first-section-level'].text) - 1
528
            if level >= 0 and level <= 6 :
529
                defined[category]['first-section-level'] = level
530
            else:
531
                debug('[WARNING] pandoc-numbering: first-section-level must be positive or zero for category ' + category)
532
        except ValueError:
533
            debug('[WARNING] pandoc-numbering: first-section-level is not correct for category ' + category)
534
    if 'last-section-level' in definition and isinstance(definition['last-section-level'], MetaString):
535
        # Get the level
536
        try:
537
            level = int(definition['last-section-level'].text)
538
            if level >= 0 and level <= 6 :
539
                defined[category]['last-section-level'] = level
540
            else:
541
                debug('[WARNING] pandoc-numbering: last-section-level must be positive or zero for category ' + category)
542
        except ValueError:
543
            debug('[WARNING] pandoc-numbering: last-section-level is not correct for category ' + category)
544
545
def meta_classes(category, definition, defined):
546
    if 'classes' in definition and isinstance(definition['classes'], MetaList):
547
        classes = []
548
        for elt in definition['classes'].content:
549
            classes.append(stringify(elt))
550
        defined[category]['classes'] = classes
551
552
def finalize(doc):
553
    listings = []
554
    # Loop on all listings definition
555
    i = 0
556
    for category, definition in doc.defined.items():
557
        if definition['listing-title'] is not None:
558
            doc.content.insert(i, Header(*definition['listing-title'], level=1, classes=['pandoc-numbering-listing'] + definition['classes']))
559
            i = i + 1
560
561
            if doc.format == 'latex':
562
                table = table_latex(doc, category, definition)
563
            else:
564
                table = table_other(doc, category, definition)
565
566
            if table:
567
                doc.content.insert(i, table)
568
                i = i + 1
569
570
def table_other(doc, category, definition):
571
    if category in doc.collections:
572
        # Prepare the list
573
        elements = []
574
        # Loop on the collection
575
        for tag in doc.collections[category]:
576
            # Add an item to the list
577
            elements.append(ListItem(Plain(Link(doc.information[tag].entry, url='#' + tag))))
578
        # Return a bullet list
579
        return BulletList(*elements)
580
    else:
581
        return None
582
583
def table_latex(doc, category, definition):
584
    latex_category = re.sub('[^a-z]+', '', category)
585
    latex = [
586
        link_color(doc),
587
        '\\makeatletter',
588
        '\\newcommand*\\l@' + latex_category + '{\\@dottedtocline{1}{' + str(definition['entry-tab']) + 'em}{'+ str(definition['entry-space']) +'em}}',
589
        '\\@starttoc{' + latex_category + '}',
590
        '\\makeatother'
591
    ]
592
    # Return a RawBlock
593
    return RawBlock(''.join(latex), 'tex')
594
595
def link_color(doc):
596
    # Get the link color
597
    metadata = doc.get_metadata()
598
    if 'toccolor' in metadata:
599
        return '\\hypersetup{linkcolor=' + str(metadata['toccolor']) + '}'
600
    else:
601
        return '\\hypersetup{linkcolor=black}'
602
603
def main(doc = None):
604
    run_filters([numbering, referencing], prepare = prepare, doc = doc, finalize = finalize)
605
606
if __name__ == '__main__':
607
    main()
608