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