Completed
Push — master ( e72567...178a50 )
by Christophe
26s
created

addLaTeX()   A

Complexity

Conditions 1

Size

Total Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
1
#!/usr/bin/env python
2
3
"""
4
Pandoc filter to number all kinds of things.
5
"""
6
7
from pandocfilters import walk, stringify, Str, Space, Para, BulletList, Plain, Strong, Span, Link, Emph, RawInline, RawBlock, Header, DefinitionList
8
from functools import reduce
9
import json
10
import io
11
import sys
12
import codecs
13
import re
14
import unicodedata
15
import subprocess
16
import copy
17
18
count = {}
19
information = {}
20
collections = {}
21
headers = [0, 0, 0, 0, 0, 0]
22
headerRegex = '(?P<header>(?P<hidden>(-\.)*)(\+\.)*)'
23
24
replace = None
25
search = None
26
27
def toJSONFilters(actions):
28
    """Generate a JSON-to-JSON filter from stdin to stdout
29
30
    The filter:
31
32
    * reads a JSON-formatted pandoc document from stdin
33
    * transforms it by walking the tree and performing the actions
34
    * returns a new JSON-formatted pandoc document to stdout
35
36
    The argument `actions` is a list of functions of the form
37
    `action(key, value, format, meta)`, as described in more
38
    detail under `walk`.
39
40
    This function calls `applyJSONFilters`, with the `format`
41
    argument provided by the first command-line argument,
42
    if present.  (Pandoc sets this by default when calling
43
    filters.)
44
    """
45
    try:
46
        input_stream = io.TextIOWrapper(sys.stdin.buffer, encoding='utf-8')
47
    except AttributeError:
48
        # Python 2 does not have sys.stdin.buffer.
49
        # REF: https://stackoverflow.com/questions/2467928/python-unicodeencode
50
        input_stream = codecs.getreader("utf-8")(sys.stdin)
51
52
    source = input_stream.read()
53
    if len(sys.argv) > 1:
54
        format = sys.argv[1]
55
    else:
56
        format = ""
57
58
    sys.stdout.write(applyJSONFilters(actions, source, format))
59
60
def applyJSONFilters(actions, source, format=""):
61
    """Walk through JSON structure and apply filters
62
63
    This:
64
65
    * reads a JSON-formatted pandoc document from a source string
66
    * transforms it by walking the tree and performing the actions
67
    * returns a new JSON-formatted pandoc document as a string
68
69
    The `actions` argument is a list of functions (see `walk`
70
    for a full description).
71
72
    The argument `source` is a string encoded JSON object.
73
74
    The argument `format` is a string describing the output format.
75
76
    Returns a the new JSON-formatted pandoc document.
77
    """
78
79
    doc = json.loads(source)
80
81
    if 'pandoc-api-version' in doc:
82
        pandocVersion.value = '.'.join(map(str, doc['pandoc-api-version']))
83
84
    if 'meta' in doc:
85
        meta = doc['meta']
86
    elif doc[0]:  # old API
87
        meta = doc[0]['unMeta']
88
    else:
89
        meta = {}
90
91
    altered = doc
92
    for action in actions:
93
        altered = walk(altered, action, format, meta)
94
95
    if 'meta' in altered:
96
        meta = altered['meta']
97
    elif meta[0]:  # old API
98
        meta = altered[0]['unMeta']
99
    else:
100
        meta = {}
101
102
    addListings(altered, format, meta)
103
104
    return json.dumps(altered)
105
106
def removeAccents(string):
107
    nfkd_form = unicodedata.normalize('NFKD', string)
108
    return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])
109
110
def toIdentifier(string):
111
    # replace invalid characters by dash
112
    string = re.sub('[^0-9a-zA-Z_-]+', '-', removeAccents(string.lower()))
113
114
    # Remove leading digits
115
    string = re.sub('^[^a-zA-Z]+', '', string)
116
117
    return string
118
119
def toLatex(x):
120
    """Walks the tree x and returns concatenated string content,
121
    leaving out all formatting.
122
    """
123
    result = []
124
125
    def go(key, val, format, meta):
126
        if key in ['Str', 'MetaString']:
127
            result.append(val)
128
        elif key == 'Code':
129
            result.append(val[1])
130
        elif key == 'Math':
131
            # Modified from the stringify function in the pandocfilter package
132
            if format == 'latex':
133
                result.append('$' + val[1] + '$')
134
            else:
135
                result.append(val[1])
136
        elif key == 'LineBreak':
137
            result.append(" ")
138
        elif key == 'Space':
139
            result.append(" ")
140
        elif key == 'Note':
141
            # Do not stringify value from Note node
142
            del val[:]
143
144
    walk(x, go, 'latex', {})
145
    return ''.join(result)
146
147
def numbering(key, value, format, meta):
148
    if key == 'Header':
149
        return numberingHeader(value)
150
    elif key == 'Para':
151
        return numberingPara(value, format, meta)
152
    elif key == 'DefinitionList':
153
        return numberingDefinitionList(value, format, meta)
154
155
def numberingHeader(value):
156
    [level, [id, classes, attributes], content] = value
157
    if 'unnumbered' not in classes:
158
        headers[level - 1] = headers[level - 1] + 1
159
        for index in range(level, 6):
160
            headers[index] = 0
161
162
def numberingInlines(value, format, meta):
163
    if len(value) >= 3 and value[-2]['t'] == 'Space' and value[-1]['t'] == 'Str':
164
        last = value[-1]['c']
165
        match = re.match('^' + headerRegex + '#((?P<prefix>[a-zA-Z][\w.-]*):)?(?P<name>[a-zA-Z][\w:.-]*)?$', last)
166
        if match:
167
            # Is the last element an identifier beginning with '#'
168
            return numberingEffective(match, value, format, meta)
169
        elif re.match('^' + headerRegex + '##(?P<prefix>[a-zA-Z][\w.-]*:)?(?P<name>[a-zA-Z][\w:.-]*)?$', last):
170
            # Special case where the last element is '...##...'
171
            return numberingSharpSharp(value)
172
    return value
173
174
def numberingPara(value, format, meta):
175
    return Para(numberingInlines(value, format, meta))
176
177
def numberingDefinitionList(value, format, meta):
178
    return DefinitionList([
179
        [numberingInlines(term, format, meta), defs]
180
        for [term, defs] in value
181
    ])
182
183
def numberingEffective(match, value, format, meta):
184
    title = computeTitle(value)
185
    description = computeDescription(value)
186
    basicCategory = computeBasicCategory(match, description)
187
    [levelInf, levelSup] = computeLevels(match, basicCategory, meta)
188
    sectionNumber = computeSectionNumber(levelSup)
189
    leading = computeLeading(levelSup, sectionNumber)
190
    category = computeCategory(basicCategory, leading)
191
    number = str(count[category])
192
    tag = computeTag(match, basicCategory, category, number)
193
    localNumber = computeLocalNumber(levelInf, levelSup, number)
194
    globalNumber = computeGlobalNumber(sectionNumber, number)
195
    [text, link, toc] = computeTextLinkToc(meta, basicCategory, description, title, localNumber, globalNumber, sectionNumber)
196
197
    # Store the numbers and the label for automatic numbering (See referencing function)
198
    information[tag] = {
199
        'section': sectionNumber,
200
        'local': localNumber,
201
        'global': globalNumber,
202
        'count': number,
203
        'description': description,
204
        'title': title,
205
        'link': link,
206
        'toc': [Str(globalNumber), Space()] + toc
207
    }
208
209
    # Compute collections
210
    if basicCategory not in collections:
211
        collections[basicCategory] = []
212
213
    collections[basicCategory].append(tag)
214
215
    # Prepare the contents
216
    if format == 'latex':
217
        pageRef = [RawInline('tex', '\\label{' + tag + '}')]
218
        if getFormat(basicCategory, meta):
219
            latexCategory = re.sub('[^a-z]+', '', basicCategory)
220
            latex = '\\phantomsection\\addcontentsline{' + latexCategory + '}{' + latexCategory + '}{\\protect\\numberline {' + \
221
                leading + number + '}{\ignorespaces ' + toLatex(toc) + '}}'
222
            latexToc = [RawInline('tex', latex)]
223
        else:
224
            latexToc=[]
225
    else:
226
        pageRef = []
227
        latexToc=[]
228
    contents = latexToc + [Span([tag, ['pandoc-numbering-text'] + getClasses(basicCategory, meta), []], pageRef + text)]
229
230
    return contents
231
232
def computeTitle(value):
233
    title = []
234
    if value[-3]['t'] == 'Str' and value[-3]['c'][-1:] == ')':
235
        for (i, item) in enumerate(value):
236
            if item['t'] == 'Str' and item['c'][0] == '(':
237
                title = value[i:-2]
238
                title[0]['c'] = title[0]['c'][1:]
239
                title[-1]['c'] = title[-1]['c'][:-1]
240
                del value[i-1:-2]
241
                break
242
    return title
243
244
def computeDescription(value):
245
    return value[:-2]
246
247
def computeBasicCategory(match, description):
248
    if match.group('prefix') == None:
249
        return toIdentifier(stringify(description))
250
    else:
251
        return match.group('prefix')
252
253
def computeLevels(match, basicCategory, meta):
254
    # Compute the levelInf and levelSup values
255
    levelInf = len(match.group('hidden')) // 2
256
    levelSup = len(match.group('header')) // 2
257
258
    # Get the default inf and sup level
259
    if levelInf == 0 and levelSup == 0:
260
        [levelInf, levelSup] = getDefaultLevels(basicCategory, meta)
261
262
    return [levelInf, levelSup]
263
264
def computeSectionNumber(levelSup):
265
    return '.'.join(map(str, headers[:levelSup]))
266
267
def computeLeading(levelSup, sectionNumber):
268
    # Compute the leading (composed of the section numbering and a dot)
269
    if levelSup != 0:
270
        return sectionNumber + '.'
271
    else:
272
        return ''
273
274
def computeCategory(basicCategory, leading):
275
    category = basicCategory + ':' + leading
276
277
    # Is it a new category?
278
    if category not in count:
279
        count[category] = 0
280
281
    count[category] = count[category] + 1
282
283
    return category
284
285
def computeTag(match, basicCategory, category, number):
286
    # Determine the final tag
287
    if match.group('name') == None:
288
        return category + number
289
    else:
290
        return basicCategory + ':' + match.group('name')
291
292
def computeLocalNumber(levelInf, levelSup, number):
293
    # Replace the '-.-.+.+...#' by the category count (omitting the hidden part)
294
    return '.'.join(map(str, headers[levelInf:levelSup] + [number]))
295
296
def computeGlobalNumber(sectionNumber, number):
297
    # Compute the globalNumber
298
    if sectionNumber:
299
        return sectionNumber + '.' + number
300
    else:
301
        return number
302
303
def computeTextLinkToc(meta, basicCategory, description, title, localNumber, globalNumber, sectionNumber):
304
    global replace, search
305
306
    # Is the automatic formatting required for this category?
307
    if getFormat(basicCategory, meta):
308
309
        # Prepare the final text
310
        if title:
311
            text = copy.deepcopy(getTextTitle(basicCategory, meta))
312
        else:
313
            text = copy.deepcopy(getText(basicCategory, meta))
314
315
        replace = description
316
        search = '%D'
317
        text = walk(text, replacing, format, meta)
318
319
        replace = walk(description, lowering, format, meta)
320
        search = '%d'
321
        text = walk(text, replacing, format, meta)
322
323
        replace = title
324
        search = '%T'
325
        text = walk(text, replacing, format, meta)
326
327
        replace = walk(title, lowering, format, meta)
328
        search = '%t'
329
        text = walk(text, replacing, format, meta)
330
331
        replace = [Str(sectionNumber)]
332
        search = '%s'
333
        text = walk(text, replacing, format, meta)
334
335
        replace = [Str(globalNumber)]
336
        search = '%g'
337
        text = walk(text, replacing, format, meta)
338
339
        replace = [Str(localNumber)]
340
        search = '%n'
341
        text = walk(text, replacing, format, meta)
342
343
        replace = [Str(localNumber)]
344
        search = '#'
345
        text = walk(text, replacing, format, meta)
346
347
        # Prepare the final link
348
        if title:
349
            link = copy.deepcopy(getLinkTitle(basicCategory, meta))
350
        else:
351
            link = copy.deepcopy(getLink(basicCategory, meta))
352
353
        replace = description
354
        search = '%D'
355
        link = walk(link, replacing, format, meta)
356
357
        replace = walk(description, lowering, format, meta)
358
        search = '%d'
359
        link = walk(link, replacing, format, meta)
360
361
        replace = title
362
        search = '%T'
363
        link = walk(link, replacing, format, meta)
364
365
        replace = walk(title, lowering, format, meta)
366
        search = '%t'
367
        link = walk(link, replacing, format, meta)
368
369
        replace = [Str(sectionNumber)]
370
        search = '%s'
371
        link = walk(link, replacing, format, meta)
372
373
        replace = [Str(globalNumber)]
374
        search = '%g'
375
        link = walk(link, replacing, format, meta)
376
377
        replace = [Str(localNumber)]
378
        search = '%n'
379
        link = walk(link, replacing, format, meta)
380
381
        replace = [Str(localNumber)]
382
        search = '#'
383
        link = walk(link, replacing, format, meta)
384
385
        # Prepare the final toc
386
        if title:
387
            toc = copy.deepcopy(getTocTitle(basicCategory, meta))
388
        else:
389
            toc = copy.deepcopy(getToc(basicCategory, meta))
390
391
        replace = description
392
        search = '%D'
393
        toc = walk(toc, replacing, format, meta)
394
395
        replace = walk(description, lowering, format, meta)
396
        search = '%d'
397
        toc = walk(toc, replacing, format, meta)
398
399
        replace = title
400
        search = '%T'
401
        toc = walk(toc, replacing, format, meta)
402
403
        replace = walk(title, lowering, format, meta)
404
        search = '%t'
405
        toc = walk(toc, replacing, format, meta)
406
407
    else:
408
        # Prepare the final text
409
        text = [
410
            Span(['', ['description'], []], description),
411
            Span(['', ['title'], []], title),
412
            Span(['', ['local'], []], [Str(localNumber)]),
413
            Span(['', ['global'], []], [Str(globalNumber)]),
414
            Span(['', ['section'], []], [Str(sectionNumber)]),
415
        ]
416
417
        # Compute the link
418
        link = [Span(['', ['pandoc-numbering-link'] + getClasses(basicCategory, meta), []], text)]
419
420
        # Compute the toc
421
        toc = [Span(['', ['pandoc-numbering-toc'] + getClasses(basicCategory, meta), []], text)]
422
    return [text, link, toc]
423
424
def numberingSharpSharp(value):
425
    value[-1]['c'] = value[-1]['c'].replace('##', '#', 1)
426
    return value
427
428
def lowering(key, value, format, meta):
429
    if key == 'Str':
430
        return Str(value.lower())
431
432
def referencing(key, value, format, meta):
433
    if key == 'Link':
434
        return referencingLink(value, format, meta)
435
    elif key == 'Cite':
436
        return referencingCite(value, format, meta)
437
438
def referencingLink(value, format, meta):
439
    global replace, search
440
    if pandocVersion() < '1.16':
441
        # pandoc 1.15
442
        [text, [reference, title]] = value
443
    else:
444
        # pandoc > 1.15
445
        [attributes, text, [reference, title]] = value
446
447
    if re.match('^(#([a-zA-Z][\w:.-]*))$', reference):
448
        # Compute the name
449
        tag = reference[1:]
450
451
        if tag in information:
452
            if pandocVersion() < '1.16':
453
                # pandoc 1.15
454
                i = 0
455
            else:
456
                # pandoc > 1.15
457
                i = 1
458
459
            # Replace all '%t', '%T', '%d', '%D', '%s', '%g', '%c', '%n', '%p', '#' with the corresponding text in the title
460
            value[i + 1][1] = value[i + 1][1].replace('%t', stringify(information[tag]['title']).lower())
461
            value[i + 1][1] = value[i + 1][1].replace('%T', stringify(information[tag]['title']))
462
            value[i + 1][1] = value[i + 1][1].replace('%d', stringify(information[tag]['description']).lower())
463
            value[i + 1][1] = value[i + 1][1].replace('%D', stringify(information[tag]['description']))
464
            value[i + 1][1] = value[i + 1][1].replace('%s', information[tag]['section'])
465
            value[i + 1][1] = value[i + 1][1].replace('%g', information[tag]['global'])
466
            value[i + 1][1] = value[i + 1][1].replace('%c', information[tag]['count'])
467
            value[i + 1][1] = value[i + 1][1].replace('%n', information[tag]['local'])
468
            value[i + 1][1] = value[i + 1][1].replace('%p', '\\pageref{' + tag + '}')
469
470
            # Keep # notation for compatibility
471
            value[i + 1][1] = value[i + 1][1].replace('#t', stringify(information[tag]['title']).lower())
472
            value[i + 1][1] = value[i + 1][1].replace('#T', stringify(information[tag]['title']))
473
            value[i + 1][1] = value[i + 1][1].replace('#d', stringify(information[tag]['description']).lower())
474
            value[i + 1][1] = value[i + 1][1].replace('#D', stringify(information[tag]['description']))
475
            value[i + 1][1] = value[i + 1][1].replace('#s', information[tag]['section'])
476
            value[i + 1][1] = value[i + 1][1].replace('#g', information[tag]['global'])
477
            value[i + 1][1] = value[i + 1][1].replace('#c', information[tag]['count'])
478
            value[i + 1][1] = value[i + 1][1].replace('#n', information[tag]['local'])
479
480
            value[i + 1][1] = value[i + 1][1].replace('#', information[tag]['local'])
481
482
            if text == []:
483
                # The link text is empty, replace it with the default label
484
                value[i] = information[tag]['link']
485
            else:
486
                # The link text is not empty
487
488
                # replace all '%t' with the title in lower case
489
                replace = walk(information[tag]['title'], lowering, format, meta)
490
                search = '%t'
491
                value[i] = walk(value[i], replacing, format, meta)
492
493
                # replace all '%T' with the title
494
                replace = information[tag]['title']
495
                search = '%T'
496
                value[i] = walk(value[i], replacing, format, meta)
497
498
                # replace all '%d' with the description in lower case
499
                replace = walk(information[tag]['description'], lowering, format, meta)
500
                search = '%d'
501
                value[i] = walk(value[i], replacing, format, meta)
502
503
                # replace all '%D' with the description
504
                replace = information[tag]['description']
505
                search = '%D'
506
                value[i] = walk(value[i], replacing, format, meta)
507
508
                # replace all '%s' with the corresponding number
509
                replace = [Str(information[tag]['section'])]
510
                search = '%s'
511
                value[i] = walk(value[i], replacing, format, meta)
512
513
                # replace all '%g' with the corresponding number
514
                replace = [Str(information[tag]['global'])]
515
                search = '%g'
516
                value[i] = walk(value[i], replacing, format, meta)
517
518
                # replace all '%c' with the corresponding number
519
                replace = [Str(information[tag]['count'])]
520
                search = '%c'
521
                value[i] = walk(value[i], replacing, format, meta)
522
523
                # replace all '%n' with the corresponding number
524
                replace = [Str(information[tag]['local'])]
525
                search = '%n'
526
                value[i] = walk(value[i], replacing, format, meta)
527
528
                # replace all '%p' with the corresponding page number
529
                replace = [RawInline('tex', '\\pageref{' + tag + '}')]
530
                search = '%p'
531
                value[i] = walk(value[i], replacing, format, meta)
532
533
                # Keep # notation for compatibility
534
535
                # replace all '#t' with the title in lower case
536
                replace = walk(information[tag]['title'], lowering, format, meta)
537
                search = '#t'
538
                value[i] = walk(value[i], replacing, format, meta)
539
540
                # replace all '#T' with the title
541
                replace = information[tag]['title']
542
                search = '#T'
543
                value[i] = walk(value[i], replacing, format, meta)
544
545
                # replace all '#d' with the description in lower case
546
                replace = walk(information[tag]['description'], lowering, format, meta)
547
                search = '#d'
548
                value[i] = walk(value[i], replacing, format, meta)
549
550
                # replace all '#D' with the description
551
                replace = information[tag]['description']
552
                search = '#D'
553
                value[i] = walk(value[i], replacing, format, meta)
554
555
                # replace all '#s' with the corresponding number
556
                replace = [Str(information[tag]['section'])]
557
                search = '#s'
558
                value[i] = walk(value[i], replacing, format, meta)
559
560
                # replace all '#g' with the corresponding number
561
                replace = [Str(information[tag]['global'])]
562
                search = '#g'
563
                value[i] = walk(value[i], replacing, format, meta)
564
565
                # replace all '#c' with the corresponding number
566
                replace = [Str(information[tag]['count'])]
567
                search = '#c'
568
                value[i] = walk(value[i], replacing, format, meta)
569
570
                # replace all '#n' with the corresponding number
571
                replace = [Str(information[tag]['local'])]
572
                search = '#n'
573
                value[i] = walk(value[i], replacing, format, meta)
574
575
                # replace all '#' with the corresponding number
576
                replace = [Str(information[tag]['local'])]
577
                search = '#'
578
                value[i] = walk(value[i], replacing, format, meta)
579
580
def referencingCite(value, format, meta):
581
    match = re.match('^(@(?P<tag>(?P<category>[a-zA-Z][\w.-]*):(([a-zA-Z][\w.-]*)|(\d*(\.\d*)*))))$', value[1][0]['c'])
582
    if match != None and getCiteShortCut(match.group('category'), meta):
583
584
        # Deal with @prefix:name shortcut
585
        tag = match.group('tag')
586
        if tag in information:
587
            if pandocVersion() < '1.16':
588
                # pandoc 1.15
589
                return Link([Str(information[tag]['local'])], ['#' + tag, ''])
590
            else:
591
                # pandoc > 1.15
592
                return Link(['', [], []], [Str(information[tag]['local'])], ['#' + tag, ''])
593
594
def replacing(key, value, format, meta):
595
    if key == 'Str':
596
        prepare = value.split(search)
597
        if len(prepare) > 1:
598
599
            ret = []
600
601
            if prepare[0] != '':
602
                ret.append(Str(prepare[0]))
603
604
            for string in prepare[1:]:
605
                ret.extend(replace)
606
                if string != '':
607
                    ret.append(Str(string))
608
609
            return ret
610
611
def hasMeta(meta):
612
    return 'pandoc-numbering' in meta and meta['pandoc-numbering']['t'] == 'MetaList'
613
614
def isCorrect(definition):
615
    return definition['t'] == 'MetaMap' and\
616
        'category' in definition['c'] and\
617
        definition['c']['category']['t'] == 'MetaInlines' and\
618
        len(definition['c']['category']['c']) == 1 and\
619
        definition['c']['category']['c'][0]['t'] == 'Str'
620
621
def hasProperty(definition, name, type):
622
    return name in definition['c'] and definition['c'][name]['t'] == type
623
624
def getProperty(definition, name):
625
    return definition['c'][name]['c']
626
627
def getFirstValue(definition, name):
628
    return getProperty(definition, name)[0]['c']
629
630
def addListings(doc, format, meta):
631
    if hasMeta(meta):
632
        listings = []
633
634
        # Loop on all listings definition
635
        for definition in meta['pandoc-numbering']['c']:
636
            if isCorrect(definition) and hasProperty(definition, 'listing', 'MetaInlines'):
637
638
                # Get the category name
639
                category = getFirstValue(definition, 'category')
640
641
                # Get the title
642
                title = getProperty(definition, 'listing')
643
644
                listings.append(Header(1, ['', ['unnumbered'], []], title))
645
646
                if format == 'latex':
647
                    extendListingsLaTeX(listings, meta, definition, category)
648
                else:
649
                    extendListingsOther(listings, meta, definition, category)
650
651
        # Add listings to the document
652
        if 'blocks' in doc:
653
            doc['blocks'][0:0] = listings
654
        else:  # old API
655
            doc[1][0:0] = listings
656
657
def extendListingsLaTeX(listings, meta, definition, category):
658
    space = getSpace(definition, category)
659
    tab = getTab(definition, category)
660
    # Add a RawBlock
661
    latexCategory = re.sub('[^a-z]+', '', category)
662
    latex = [
663
        getLinkColor(meta),
664
        '\\makeatletter',
665
        '\\newcommand*\\l@' + latexCategory + '{\\@dottedtocline{1}{' + str(tab) + 'em}{'+ str(space) +'em}}',
666
        '\\@starttoc{' + latexCategory + '}',
667
        '\\makeatother'
668
    ]
669
    listings.append(RawBlock('tex', ''.join(latex)))
670
671
def getLinkColor(meta):
672
    # Get the link color
673
    if 'toccolor' in meta:
674
        return '\\hypersetup{linkcolor=' + stringify(meta['toccolor']['c']) + '}'
675
    else:
676
        return '\\hypersetup{linkcolor=black}'
677
678
def getTab(definition, category):
679
    # Get the tab
680
    if hasProperty(definition, 'tab', 'MetaString'):
681
        try:
682
            tab = float(getProperty(definition, 'tab'))
683
        except ValueError:
684
            tab = None
685
    else:
686
        tab = None
687
688
    # Deal with default tab length
689
    if tab == None:
690
        return 1.5
691
    else:
692
        return tab
693
694
def getSpace(definition, category):
695
    # Get the space
696
    if hasProperty(definition, 'space', 'MetaString'):
697
        try:
698
            space = float(getProperty(definition, 'space'))
699
        except ValueError:
700
            space = None
701
    else:
702
        space = None
703
704
    # Deal with default space length
705
    if space == None:
706
        level = 0
707
        if category in collections:
708
            # Loop on the collection
709
            for tag in collections[category]:
710
                level = max(level, information[tag]['section'].count('.'))
711
        return level + 2.3
712
    else:
713
        return space
714
715
def extendListingsOther(listings, meta, definition, category):
716
    if category in collections:
717
        # Prepare the list
718
        elements = []
719
720
        # Loop on the collection
721
        for tag in collections[category]:
722
723
            # Add an item to the list
724
            text = information[tag]['toc']
725
726
            if pandocVersion() < '1.16':
727
                # pandoc 1.15
728
                link = Link(text, ['#' + tag, ''])
729
            else:
730
                # pandoc 1.16
731
                link = Link(['', [], []], text, ['#' + tag, ''])
732
733
            elements.append([Plain([link])])
734
735
        # Add a bullet list
736
        listings.append(BulletList(elements))
737
738
def getValue(category, meta, fct, default, analyzeDefinition):
739
    if not hasattr(fct, 'value'):
740
        fct.value = {}
741
        if hasMeta(meta):
742
            # Loop on all listings definition
743
            for definition in meta['pandoc-numbering']['c']:
744
                if isCorrect(definition):
745
                    analyzeDefinition(definition)
746
747
    if not category in fct.value:
748
        fct.value[category] = default
749
750
    return fct.value[category]
751
752
def getFormat(category, meta):
753
    def analyzeDefinition(definition):
754 View Code Duplication
        if hasProperty(definition, 'format', 'MetaBool'):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
755
            getFormat.value[getFirstValue(definition, 'category')] = getProperty(definition, 'format')
756
757
    return getValue(category, meta, getFormat, True, analyzeDefinition)
758
759
def getText(category, meta):
760
    def analyzeDefinition(definition):
761
        if hasProperty(definition, 'text', 'MetaInlines'):
762
            getText.value[getFirstValue(definition, 'category')] = getProperty(definition, 'text')
763
        elif hasProperty(definition, 'text', 'MetaBlocks') and getProperty(definition, 'text') == []:
764
            getText.value[getFirstValue(definition, 'category')] = []
765
766
    return getValue(category, meta, getText, [Strong([Str('%D'), Space(), Str('%n')])], analyzeDefinition)
767
768
def getTextTitle(category, meta):
769
    def analyzeDefinition(definition):
770
        if hasProperty(definition, 'text-title', 'MetaInlines'):
771
            getTextTitle.value[getFirstValue(definition, 'category')] = getProperty(definition, 'text-title')
772
        elif hasProperty(definition, 'text-title', 'MetaBlocks') and getProperty(definition, 'text-title') == []:
773
            getTextTitle.value[getFirstValue(definition, 'category')] = []
774
775
    return getValue(category, meta, getTextTitle, [Strong([Str('%D'), Space(), Str('%n')]), Space(), Emph([Str('('), Str('%T'), Str(')')])], analyzeDefinition)
776
777
def getLink(category, meta):
778
    def analyzeDefinition(definition):
779
        if hasProperty(definition, 'link', 'MetaInlines'):
780
            getLink.value[getFirstValue(definition, 'category')] = getProperty(definition, 'link')
781 View Code Duplication
        elif hasProperty(definition, 'link', 'MetaBlocks') and getProperty(definition, 'link') == []:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
782
            getLink.value[getFirstValue(definition, 'category')] = []
783
784
    return getValue(category, meta, getLink, [Str('%D'), Space(), Str('%n')], analyzeDefinition)
785
786
def getLinkTitle(category, meta):
787
    def analyzeDefinition(definition):
788
        if hasProperty(definition, 'link-title', 'MetaInlines'):
789
            getLinkTitle.value[getFirstValue(definition, 'category')] = getProperty(definition, 'link-title')
790
        elif hasProperty(definition, 'link-title', 'MetaBlocks') and getProperty(definition, 'link-title') == []:
791
            getLinkTitle.value[getFirstValue(definition, 'category')] = []
792
793
    return getValue(category, meta, getLinkTitle, [Str('%D'), Space(), Str('%n')], analyzeDefinition)
794
795
def getToc(category, meta):
796
    def analyzeDefinition(definition):
797
        if hasProperty(definition, 'toc', 'MetaInlines'):
798
            getToc.value[getFirstValue(definition, 'category')] = getProperty(definition, 'toc')
799
        elif hasProperty(definition, 'toc', 'MetaBlocks') and getProperty(definition, 'toc') == []:
800
            getToc.value[getFirstValue(definition, 'category')] = []
801
802
    return getValue(category, meta, getToc, [Str('%D')], analyzeDefinition)
803
804
def getTocTitle(category, meta):
805
    def analyzeDefinition(definition):
806
        if hasProperty(definition, 'toc-title', 'MetaInlines'):
807
            getTocTitle.value[getFirstValue(definition, 'category')] = getProperty(definition, 'toc-title')
808
        elif hasProperty(definition, 'toc-title', 'MetaBlocks') and getProperty(definition, 'toc-title') == []:
809
            getTocTitle.value[getFirstValue(definition, 'category')] = []
810
811
    return getValue(category, meta, getTocTitle, [Str('%T')], analyzeDefinition)
812
813
def getCiteShortCut(category, meta):
814
    def analyzeDefinition(definition):
815
        if hasProperty(definition, 'cite-shortcut', 'MetaBool'):
816
            getCiteShortCut.value[getFirstValue(definition, 'category')] = getProperty(definition, 'cite-shortcut')
817
818
    return getValue(category, meta, getCiteShortCut, False, analyzeDefinition)
819
820
def getLevelsFromYaml(definition):
821
    levelInf = 0
822
    levelSup = 0
823
    if hasProperty(definition, 'first', 'MetaString'):
824
        try:
825
            levelInf = max(min(int(getProperty(definition, 'first')) - 1, 6), 0)
826
        except ValueError:
827
            pass
828
    if hasProperty(definition, 'last', 'MetaString'):
829
        try:
830
            levelSup = max(min(int(getProperty(definition, 'last')), 6), levelInf)
831
        except ValueError:
832
            pass
833
    return [levelInf, levelSup]
834
835
def getLevelsFromRegex(definition):
836
    match = re.match('^' + headerRegex + '$', getFirstValue(definition, 'sectioning'))
837
    if match:
838
        # Compute the levelInf and levelSup values
839
        return [len(match.group('hidden')) // 2, len(match.group('header')) // 2]
840
    else:
841
        return [0, 0]
842
843
def getDefaultLevels(category, meta):
844
    def analyzeDefinition(definition):
845
        if hasProperty(definition, 'sectioning', 'MetaInlines') and\
846
           len(getProperty(definition, 'sectioning')) == 1 and\
847
           getProperty(definition, 'sectioning')[0]['t'] == 'Str':
848
849
            getDefaultLevels.value[getFirstValue(definition, 'category')] = getLevelsFromRegex(definition)
850
        else:
851
            getDefaultLevels.value[getFirstValue(definition, 'category')] = getLevelsFromYaml(definition)
852
853
    return getValue(category, meta, getDefaultLevels, [0, 0], analyzeDefinition)
854
855
def getClasses(category, meta):
856
    def analyzeDefinition(definition):
857
        if hasProperty(definition, 'classes', 'MetaList'):
858
            classes = []
859
            for elt in getProperty(definition, 'classes'):
860
                classes.append(stringify(elt))
861
            getClasses.value[getFirstValue(definition, 'category')] = classes
862
863
    return getValue(category, meta, getClasses, [category], analyzeDefinition)
864
865
def pandocVersion():
866
    if not hasattr(pandocVersion, 'value'):
867
        p = subprocess.Popen(['pandoc', '-v'], stdout=subprocess.PIPE,stderr=subprocess.PIPE)
868
        out, err = p.communicate()
869
        pandocVersion.value = re.search(b'pandoc (?P<version>.*)', out).group('version').decode('utf-8')
870
    return pandocVersion.value
871
872
def main():
873
    toJSONFilters([numbering, referencing])
874
875
if __name__ == '__main__':
876
    main()
877