Completed
Push — master ( c39211...51c7c6 )
by Christophe
29s
created

getText()   B

Complexity

Conditions 5

Size

Total Lines 8

Duplication

Lines 8
Ratio 100 %

Importance

Changes 0
Metric Value
cc 5
c 0
b 0
f 0
dl 8
loc 8
rs 8.5454
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
    # Prepare the contents
210
    contents = [Span([tag, ['pandoc-numbering-text'] + getClasses(basicCategory, meta), []], text)]
211
212
    # Compute collections
213
    if basicCategory not in collections:
214
        collections[basicCategory] = []
215
216
    collections[basicCategory].append(tag)
217
218
    # Special case for LaTeX
219
    if format == 'latex' and getFormat(basicCategory, meta):
220
        addLaTeX(contents, basicCategory, toc, leading, number)
221
222
    return contents
223
224
def computeTitle(value):
225
    title = []
226
    if value[-3]['t'] == 'Str' and value[-3]['c'][-1:] == ')':
227
        for (i, item) in enumerate(value):
228
            if item['t'] == 'Str' and item['c'][0] == '(':
229
                title = value[i:-2]
230
                title[0]['c'] = title[0]['c'][1:]
231
                title[-1]['c'] = title[-1]['c'][:-1]
232
                del value[i-1:-2]
233
                break
234
    return title
235
236
def computeDescription(value):
237
    return value[:-2]
238
239
def computeBasicCategory(match, description):
240
    if match.group('prefix') == None:
241
        return toIdentifier(stringify(description))
242
    else:
243
        return match.group('prefix')
244
245
def computeLevels(match, basicCategory, meta):
246
    # Compute the levelInf and levelSup values
247
    levelInf = len(match.group('hidden')) // 2
248
    levelSup = len(match.group('header')) // 2
249
250
    # Get the default inf and sup level
251
    if levelInf == 0 and levelSup == 0:
252
        [levelInf, levelSup] = getDefaultLevels(basicCategory, meta)
253
254
    return [levelInf, levelSup]
255
256
def computeSectionNumber(levelSup):
257
    return '.'.join(map(str, headers[:levelSup]))
258
259
def computeLeading(levelSup, sectionNumber):
260
    # Compute the leading (composed of the section numbering and a dot)
261
    if levelSup != 0:
262
        return sectionNumber + '.'
263
    else:
264
        return ''
265
266
def computeCategory(basicCategory, leading):
267
    category = basicCategory + ':' + leading
268
269
    # Is it a new category?
270
    if category not in count:
271
        count[category] = 0
272
273
    count[category] = count[category] + 1
274
275
    return category
276
277
def computeTag(match, basicCategory, category, number):
278
    # Determine the final tag
279
    if match.group('name') == None:
280
        return category + number
281
    else:
282
        return basicCategory + ':' + match.group('name')
283
284
def computeLocalNumber(levelInf, levelSup, number):
285
    # Replace the '-.-.+.+...#' by the category count (omitting the hidden part)
286
    return '.'.join(map(str, headers[levelInf:levelSup] + [number]))
287
288
def computeGlobalNumber(sectionNumber, number):
289
    # Compute the globalNumber
290
    if sectionNumber:
291
        return sectionNumber + '.' + number
292
    else:
293
        return number
294
295
def computeTextLinkToc(meta, basicCategory, description, title, localNumber, globalNumber, sectionNumber):
296
    global replace, search
297
298
    # Is the automatic formatting required for this category?
299
    if getFormat(basicCategory, meta):
300
301
        # Prepare the final text
302
        if title:
303
            text = copy.deepcopy(getTextTitle(basicCategory, meta))
304
        else:
305
            text = copy.deepcopy(getText(basicCategory, meta))
306
307
        replace = description
308
        search = '%D'
309
        text = walk(text, replacing, format, meta)
310
311
        replace = walk(description, lowering, format, meta)
312
        search = '%d'
313
        text = walk(text, replacing, format, meta)
314
315
        replace = title
316
        search = '%T'
317
        text = walk(text, replacing, format, meta)
318
319
        replace = walk(title, lowering, format, meta)
320
        search = '%t'
321
        text = walk(text, replacing, format, meta)
322
323
        replace = [Str(sectionNumber)]
324
        search = '%s'
325
        text = walk(text, replacing, format, meta)
326
327
        replace = [Str(globalNumber)]
328
        search = '%g'
329
        text = walk(text, replacing, format, meta)
330
331
        replace = [Str(localNumber)]
332
        search = '%n'
333
        text = walk(text, replacing, format, meta)
334
335
        replace = [Str(localNumber)]
336
        search = '#'
337
        text = walk(text, replacing, format, meta)
338
339
        # Prepare the final link
340
        if title:
341
            link = copy.deepcopy(getLinkTitle(basicCategory, meta))
342
        else:
343
            link = copy.deepcopy(getLink(basicCategory, meta))
344
345
        replace = description
346
        search = '%D'
347
        link = walk(link, replacing, format, meta)
348
349
        replace = walk(description, lowering, format, meta)
350
        search = '%d'
351
        link = walk(link, replacing, format, meta)
352
353
        replace = title
354
        search = '%T'
355
        link = walk(link, replacing, format, meta)
356
357
        replace = walk(title, lowering, format, meta)
358
        search = '%t'
359
        link = walk(link, replacing, format, meta)
360
361
        replace = [Str(sectionNumber)]
362
        search = '%s'
363
        link = walk(link, replacing, format, meta)
364
365
        replace = [Str(globalNumber)]
366
        search = '%g'
367
        link = walk(link, replacing, format, meta)
368
369
        replace = [Str(localNumber)]
370
        search = '%n'
371
        link = walk(link, replacing, format, meta)
372
373
        replace = [Str(localNumber)]
374
        search = '#'
375
        link = walk(link, replacing, format, meta)
376
377
        # Prepare the final toc
378
        if title:
379
            toc = copy.deepcopy(getTocTitle(basicCategory, meta))
380
        else:
381
            toc = copy.deepcopy(getToc(basicCategory, meta))
382
383
        warning(toc)
384
385
        replace = description
386
        search = '%D'
387
        toc = walk(toc, replacing, format, meta)
388
389
        replace = walk(description, lowering, format, meta)
390
        search = '%d'
391
        toc = walk(toc, replacing, format, meta)
392
393
        replace = title
394
        search = '%T'
395
        toc = walk(toc, replacing, format, meta)
396
397
        replace = walk(title, lowering, format, meta)
398
        search = '%t'
399
        toc = walk(toc, replacing, format, meta)
400
401
    else:
402
        # Prepare the final text
403
        text = [
404
            Span(['', ['description'], []], description),
405
            Span(['', ['title'], []], title),
406
            Span(['', ['local'], []], [Str(localNumber)]),
407
            Span(['', ['global'], []], [Str(globalNumber)]),
408
            Span(['', ['section'], []], [Str(sectionNumber)]),
409
        ]
410
411
        # Compute the link
412
        link = [Span(['', ['pandoc-numbering-link'] + getClasses(basicCategory, meta), []], text)]
413
414
        # Compute the toc
415
        toc = [Span(['', ['pandoc-numbering-toc'] + getClasses(basicCategory, meta), []], text)]
416
    return [text, link, toc]
417
418
def addLaTeX(contents, basicCategory, toc, leading, number):
419
    latexCategory = re.sub('[^a-z]+', '', basicCategory)
420
    latex = '\\phantomsection\\addcontentsline{' + latexCategory + '}{' + latexCategory + '}{\\protect\\numberline {' + \
421
        leading + number + '}{\ignorespaces ' + toLatex(toc) + '}}'
422
    contents.insert(0, RawInline('tex', latex))
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', '#' 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
469
            # Keep # notation for compatibility
470
            value[i + 1][1] = value[i + 1][1].replace('#t', stringify(information[tag]['title']).lower())
471
            value[i + 1][1] = value[i + 1][1].replace('#T', stringify(information[tag]['title']))
472
            value[i + 1][1] = value[i + 1][1].replace('#d', stringify(information[tag]['description']).lower())
473
            value[i + 1][1] = value[i + 1][1].replace('#D', stringify(information[tag]['description']))
474
            value[i + 1][1] = value[i + 1][1].replace('#s', information[tag]['section'])
475
            value[i + 1][1] = value[i + 1][1].replace('#g', information[tag]['global'])
476
            value[i + 1][1] = value[i + 1][1].replace('#c', information[tag]['count'])
477
            value[i + 1][1] = value[i + 1][1].replace('#n', information[tag]['local'])
478
479
            value[i + 1][1] = value[i + 1][1].replace('#', information[tag]['local'])
480
481
            if text == []:
482
                # The link text is empty, replace it with the default label
483
                value[i] = information[tag]['link']
484
            else:
485
                # The link text is not empty
486
487
                # replace all '%t' with the title in lower case
488
                replace = walk(information[tag]['title'], lowering, format, meta)
489
                search = '%t'
490
                value[i] = walk(value[i], replacing, format, meta)
491
492
                # replace all '%T' with the title
493
                replace = information[tag]['title']
494
                search = '%T'
495
                value[i] = walk(value[i], replacing, format, meta)
496
497
                # replace all '%d' with the description in lower case
498
                replace = walk(information[tag]['description'], lowering, format, meta)
499
                search = '%d'
500
                value[i] = walk(value[i], replacing, format, meta)
501
502
                # replace all '%D' with the description
503
                replace = information[tag]['description']
504
                search = '%D'
505
                value[i] = walk(value[i], replacing, format, meta)
506
507
                # replace all '%s' with the corresponding number
508
                replace = [Str(information[tag]['section'])]
509
                search = '%s'
510
                value[i] = walk(value[i], replacing, format, meta)
511
512
                # replace all '%g' with the corresponding number
513
                replace = [Str(information[tag]['global'])]
514
                search = '%g'
515
                value[i] = walk(value[i], replacing, format, meta)
516
517
                # replace all '%c' with the corresponding number
518
                replace = [Str(information[tag]['count'])]
519
                search = '%c'
520
                value[i] = walk(value[i], replacing, format, meta)
521
522
                # replace all '%n' with the corresponding number
523
                replace = [Str(information[tag]['local'])]
524
                search = '%n'
525
                value[i] = walk(value[i], replacing, format, meta)
526
527
                # Keep # notation for compatibility
528
529
                # replace all '#t' with the title in lower case
530
                replace = walk(information[tag]['title'], lowering, format, meta)
531
                search = '#t'
532
                value[i] = walk(value[i], replacing, format, meta)
533
534
                # replace all '#T' with the title
535
                replace = information[tag]['title']
536
                search = '#T'
537
                value[i] = walk(value[i], replacing, format, meta)
538
539
                # replace all '#d' with the description in lower case
540
                replace = walk(information[tag]['description'], lowering, format, meta)
541
                search = '#d'
542
                value[i] = walk(value[i], replacing, format, meta)
543
544
                # replace all '#D' with the description
545
                replace = information[tag]['description']
546
                search = '#D'
547
                value[i] = walk(value[i], replacing, format, meta)
548
549
                # replace all '#s' with the corresponding number
550
                replace = [Str(information[tag]['section'])]
551
                search = '#s'
552
                value[i] = walk(value[i], replacing, format, meta)
553
554
                # replace all '#g' with the corresponding number
555
                replace = [Str(information[tag]['global'])]
556
                search = '#g'
557
                value[i] = walk(value[i], replacing, format, meta)
558
559
                # replace all '#c' with the corresponding number
560
                replace = [Str(information[tag]['count'])]
561
                search = '#c'
562
                value[i] = walk(value[i], replacing, format, meta)
563
564
                # replace all '#n' with the corresponding number
565
                replace = [Str(information[tag]['local'])]
566
                search = '#n'
567
                value[i] = walk(value[i], replacing, format, meta)
568
569
570
                # replace all '#' with the corresponding number
571
                replace = [Str(information[tag]['local'])]
572
                search = '#'
573
                value[i] = walk(value[i], replacing, format, meta)
574
575
def referencingCite(value, format, meta):
576
    match = re.match('^(@(?P<tag>(?P<category>[a-zA-Z][\w.-]*):(([a-zA-Z][\w.-]*)|(\d*(\.\d*)*))))$', value[1][0]['c'])
577
    if match != None and getCiteShortCut(match.group('category'), meta):
578
579
        # Deal with @prefix:name shortcut
580
        tag = match.group('tag')
581
        if tag in information:
582
            if pandocVersion() < '1.16':
583
                # pandoc 1.15
584
                return Link([Str(information[tag]['local'])], ['#' + tag, ''])
585
            else:
586
                # pandoc > 1.15
587
                return Link(['', [], []], [Str(information[tag]['local'])], ['#' + tag, ''])
588
589
def replacing(key, value, format, meta):
590
    if key == 'Str':
591
        prepare = value.split(search)
592
        if len(prepare) > 1:
593
594
            ret = []
595
596
            if prepare[0] != '':
597
                ret.append(Str(prepare[0]))
598
599
            for string in prepare[1:]:
600
                ret.extend(replace)
601
                if string != '':
602
                    ret.append(Str(string))
603
604
            return ret
605
606
def hasMeta(meta):
607
    return 'pandoc-numbering' in meta and meta['pandoc-numbering']['t'] == 'MetaList'
608
609
def isCorrect(definition):
610
    return definition['t'] == 'MetaMap' and\
611
        'category' in definition['c'] and\
612
        definition['c']['category']['t'] == 'MetaInlines' and\
613
        len(definition['c']['category']['c']) == 1 and\
614
        definition['c']['category']['c'][0]['t'] == 'Str'
615
616
def hasProperty(definition, name, type):
617
    return name in definition['c'] and definition['c'][name]['t'] == type
618
619
def getProperty(definition, name):
620
    return definition['c'][name]['c']
621
622
def getFirstValue(definition, name):
623
    return getProperty(definition, name)[0]['c']
624
625
def addListings(doc, format, meta):
626
    if hasMeta(meta):
627
        listings = []
628
629
        # Loop on all listings definition
630
        for definition in meta['pandoc-numbering']['c']:
631
            if isCorrect(definition) and hasProperty(definition, 'listing', 'MetaInlines'):
632
633
                # Get the category name
634
                category = getFirstValue(definition, 'category')
635
636
                # Get the title
637
                title = getProperty(definition, 'listing')
638
639
                listings.append(Header(1, ['', ['unnumbered'], []], title))
640
641
                if format == 'latex':
642
                    extendListingsLaTeX(listings, meta, definition, category)
643
                else:
644
                    extendListingsOther(listings, meta, definition, category)
645
646
        # Add listings to the document
647
        if 'blocks' in doc:
648
            doc['blocks'][0:0] = listings
649
        else:  # old API
650
            doc[1][0:0] = listings
651
652
def extendListingsLaTeX(listings, meta, definition, category):
653
    space = getSpace(definition, category)
654
    tab = getTab(definition, category)
655
    # Add a RawBlock
656
    latexCategory = re.sub('[^a-z]+', '', category)
657
    latex = [
658
        getLinkColor(meta),
659
        '\\makeatletter',
660
        '\\newcommand*\\l@' + latexCategory + '{\\@dottedtocline{1}{' + str(tab) + 'em}{'+ str(space) +'em}}',
661
        '\\@starttoc{' + latexCategory + '}',
662
        '\\makeatother'
663
    ]
664
    listings.append(RawBlock('tex', ''.join(latex)))
665
666
def getLinkColor(meta):
667
    # Get the link color
668
    if 'toccolor' in meta:
669
        return '\\hypersetup{linkcolor=' + stringify(meta['toccolor']['c']) + '}'
670
    else:
671
        return '\\hypersetup{linkcolor=black}'
672
673
def getTab(definition, category):
674
    # Get the tab
675
    if hasProperty(definition, 'tab', 'MetaString'):
676
        try:
677
            tab = float(getProperty(definition, 'tab'))
678
        except ValueError:
679
            tab = None
680
    else:
681
        tab = None
682
683
    # Deal with default tab length
684
    if tab == None:
685
        return 1.5
686
    else:
687
        return tab
688
689
def getSpace(definition, category):
690
    # Get the space
691
    if hasProperty(definition, 'space', 'MetaString'):
692
        try:
693
            space = float(getProperty(definition, 'space'))
694
        except ValueError:
695
            space = None
696
    else:
697
        space = None
698
699
    # Deal with default space length
700
    if space == None:
701
        level = 0
702
        if category in collections:
703
            # Loop on the collection
704
            for tag in collections[category]:
705
                level = max(level, information[tag]['section'].count('.'))
706
        return level + 2.3
707
    else:
708
        return space
709
710
def extendListingsOther(listings, meta, definition, category):
711
    if category in collections:
712
        # Prepare the list
713
        elements = []
714
715
        # Loop on the collection
716
        for tag in collections[category]:
717
718
            # Add an item to the list
719
            text = information[tag]['toc']
720
721
            if pandocVersion() < '1.16':
722
                # pandoc 1.15
723
                link = Link(text, ['#' + tag, ''])
724
            else:
725
                # pandoc 1.16
726
                link = Link(['', [], []], text, ['#' + tag, ''])
727
728
            elements.append([Plain([link])])
729
730
        # Add a bullet list
731
        listings.append(BulletList(elements))
732
733
def getValue(category, meta, fct, default, analyzeDefinition):
734
    if not hasattr(fct, 'value'):
735
        fct.value = {}
736
        if hasMeta(meta):
737
            # Loop on all listings definition
738
            for definition in meta['pandoc-numbering']['c']:
739
                if isCorrect(definition):
740
                    analyzeDefinition(definition)
741
742
    if not category in fct.value:
743
        fct.value[category] = default
744
745
    return fct.value[category]
746
747
def getFormat(category, meta):
748
    def analyzeDefinition(definition):
749
        if hasProperty(definition, 'format', 'MetaBool'):
750
            getFormat.value[getFirstValue(definition, 'category')] = getProperty(definition, 'format')
751
752
    return getValue(category, meta, getFormat, True, analyzeDefinition)
753
754 View Code Duplication
def getText(category, meta):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
755
    def analyzeDefinition(definition):
756
        if hasProperty(definition, 'text', 'MetaInlines'):
757
            getText.value[getFirstValue(definition, 'category')] = getProperty(definition, 'text')
758
        elif hasProperty(definition, 'text', 'MetaBlocks') and getProperty(definition, 'text') == []:
759
            getText.value[getFirstValue(definition, 'category')] = []
760
761
    return getValue(category, meta, getText, [Strong([Str('%D'), Space(), Str('%n')])], analyzeDefinition)
762
763
def getTextTitle(category, meta):
764
    def analyzeDefinition(definition):
765
        if hasProperty(definition, 'text-title', 'MetaInlines'):
766
            getTextTitle.value[getFirstValue(definition, 'category')] = getProperty(definition, 'text-title')
767
        elif hasProperty(definition, 'text-title', 'MetaBlocks') and getProperty(definition, 'text-title') == []:
768
            getTextTitle.value[getFirstValue(definition, 'category')] = []
769
770
    return getValue(category, meta, getTextTitle, [Strong([Str('%D'), Space(), Str('%n')]), Space(), Emph([Str('('), Str('%T'), Str(')')])], analyzeDefinition)
771
772 View Code Duplication
def getLink(category, meta):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
773
    def analyzeDefinition(definition):
774
        if hasProperty(definition, 'link', 'MetaInlines'):
775
            getLink.value[getFirstValue(definition, 'category')] = getProperty(definition, 'link')
776
        elif hasProperty(definition, 'link', 'MetaBlocks') and getProperty(definition, 'link') == []:
777
            getLink.value[getFirstValue(definition, 'category')] = []
778
779
    return getValue(category, meta, getLink, [Str('%D'), Space(), Str('%n')], analyzeDefinition)
780
781 View Code Duplication
def getLinkTitle(category, meta):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
782
    def analyzeDefinition(definition):
783
        if hasProperty(definition, 'link-title', 'MetaInlines'):
784
            getLinkTitle.value[getFirstValue(definition, 'category')] = getProperty(definition, 'link-title')
785
        elif hasProperty(definition, 'link-title', 'MetaBlocks') and getProperty(definition, 'link-title') == []:
786
            getLinkTitle.value[getFirstValue(definition, 'category')] = []
787
788
    return getValue(category, meta, getLinkTitle, [Str('%D'), Space(), Str('%n')], analyzeDefinition)
789
790
def getToc(category, meta):
791
    def analyzeDefinition(definition):
792
        if hasProperty(definition, 'toc', 'MetaInlines'):
793
            getToc.value[getFirstValue(definition, 'category')] = getProperty(definition, 'toc')
794
        elif hasProperty(definition, 'toc', 'MetaBlocks') and getProperty(definition, 'toc') == []:
795
            getToc.value[getFirstValue(definition, 'category')] = []
796
797
    return getValue(category, meta, getToc, [Str('%D')], analyzeDefinition)
798
799
def getTocTitle(category, meta):
800
    def analyzeDefinition(definition):
801
        if hasProperty(definition, 'toc-title', 'MetaInlines'):
802
            getTocTitle.value[getFirstValue(definition, 'category')] = getProperty(definition, 'toc-title')
803
        elif hasProperty(definition, 'toc-title', 'MetaBlocks') and getProperty(definition, 'toc-title') == []:
804
            getTocTitle.value[getFirstValue(definition, 'category')] = []
805
806
    return getValue(category, meta, getTocTitle, [Str('%T')], analyzeDefinition)
807
808
def getCiteShortCut(category, meta):
809
    def analyzeDefinition(definition):
810
        if hasProperty(definition, 'cite-shortcut', 'MetaBool'):
811
            getCiteShortCut.value[getFirstValue(definition, 'category')] = getProperty(definition, 'cite-shortcut')
812
813
    return getValue(category, meta, getCiteShortCut, False, analyzeDefinition)
814
815
def getLevelsFromYaml(definition):
816
    levelInf = 0
817
    levelSup = 0
818
    if hasProperty(definition, 'first', 'MetaString'):
819
        try:
820
            levelInf = max(min(int(getProperty(definition, 'first')) - 1, 6), 0)
821
        except ValueError:
822
            pass
823
    if hasProperty(definition, 'last', 'MetaString'):
824
        try:
825
            levelSup = max(min(int(getProperty(definition, 'last')), 6), levelInf)
826
        except ValueError:
827
            pass
828
    return [levelInf, levelSup]
829
830
def getLevelsFromRegex(definition):
831
    match = re.match('^' + headerRegex + '$', getFirstValue(definition, 'sectioning'))
832
    if match:
833
        # Compute the levelInf and levelSup values
834
        return [len(match.group('hidden')) // 2, len(match.group('header')) // 2]
835
    else:
836
        return [0, 0]
837
838
def getDefaultLevels(category, meta):
839
    def analyzeDefinition(definition):
840
        if hasProperty(definition, 'sectioning', 'MetaInlines') and\
841
           len(getProperty(definition, 'sectioning')) == 1 and\
842
           getProperty(definition, 'sectioning')[0]['t'] == 'Str':
843
844
            getDefaultLevels.value[getFirstValue(definition, 'category')] = getLevelsFromRegex(definition)
845
        else:
846
            getDefaultLevels.value[getFirstValue(definition, 'category')] = getLevelsFromYaml(definition)
847
848
    return getValue(category, meta, getDefaultLevels, [0, 0], analyzeDefinition)
849
850
def getClasses(category, meta):
851
    def analyzeDefinition(definition):
852
        if hasProperty(definition, 'classes', 'MetaList'):
853
            classes = []
854
            for elt in getProperty(definition, 'classes'):
855
                classes.append(stringify(elt))
856
            getClasses.value[getFirstValue(definition, 'category')] = classes
857
858
    return getValue(category, meta, getClasses, [category], analyzeDefinition)
859
860
def pandocVersion():
861
    if not hasattr(pandocVersion, 'value'):
862
        p = subprocess.Popen(['pandoc', '-v'], stdout=subprocess.PIPE,stderr=subprocess.PIPE)
863
        out, err = p.communicate()
864
        pandocVersion.value = re.search(b'pandoc (?P<version>.*)', out).group('version').decode('utf-8')
865
    return pandocVersion.value
866
867
def main():
868
    toJSONFilters([numbering, referencing])
869
870
if __name__ == '__main__':
871
    main()
872