Completed
Push — master ( 0b86a5...142324 )
by Christophe
25s
created

Numbered.description()   A

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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