dfabulich
|
1106
|
1
|
|
|
2
|
|
|
3
|
|
|
4
|
|
|
5
|
|
|
6
|
|
|
7
|
"""
|
|
8
|
LaTeX2e document tree Writer.
|
|
9
|
"""
|
|
10
|
|
|
11
|
|
|
12
|
|
|
13
|
|
|
14
|
|
|
15
|
__docformat__ = 'reStructuredText'
|
|
16
|
|
|
17
|
|
|
18
|
import re
|
|
19
|
import os.path
|
|
20
|
from types import ListType
|
|
21
|
|
|
22
|
import docutils
|
|
23
|
from docutils import nodes, writers, utils
|
|
24
|
from docutils.writers.newlatex2e import unicode_map
|
|
25
|
from docutils.transforms import writer_aux
|
|
26
|
|
|
27
|
|
|
28
|
class Writer(writers.Writer):
|
|
29
|
|
|
30
|
supported = ('newlatex', 'newlatex2e')
|
|
31
|
"""Formats this writer supports."""
|
|
32
|
|
|
33
|
default_stylesheet = 'base.tex'
|
|
34
|
|
|
35
|
default_stylesheet_path = utils.relative_path(
|
|
36
|
os.path.join(os.getcwd(), 'dummy'),
|
|
37
|
os.path.join(os.path.dirname(__file__), default_stylesheet))
|
|
38
|
|
|
39
|
settings_spec = (
|
|
40
|
'LaTeX-Specific Options',
|
|
41
|
'Note that this LaTeX writer is still EXPERIMENTAL. '
|
|
42
|
'You must specify the location of the tools/stylesheets/latex.tex '
|
|
43
|
'stylesheet file contained in the Docutils distribution tarball to '
|
|
44
|
'make the LaTeX output work.',
|
|
45
|
(('Specify a stylesheet file. The path is used verbatim to include '
|
|
46
|
'the file. Overrides --stylesheet-path.',
|
|
47
|
['--stylesheet'],
|
|
48
|
{'default': '', 'metavar': '<file>',
|
|
49
|
'overrides': 'stylesheet_path'}),
|
|
50
|
('Specify a stylesheet file, relative to the current working '
|
|
51
|
'directory. Overrides --stylesheet. Default: "%s"'
|
|
52
|
% default_stylesheet_path,
|
|
53
|
['--stylesheet-path'],
|
|
54
|
{'metavar': '<file>', 'overrides': 'stylesheet',
|
|
55
|
'default': default_stylesheet_path}),
|
|
56
|
('Specify a user stylesheet file. See --stylesheet.',
|
|
57
|
['--user-stylesheet'],
|
|
58
|
{'default': '', 'metavar': '<file>',
|
|
59
|
'overrides': 'user_stylesheet_path'}),
|
|
60
|
('Specify a user stylesheet file. See --stylesheet-path.',
|
|
61
|
['--user-stylesheet-path'],
|
|
62
|
{'metavar': '<file>', 'overrides': 'user_stylesheet'})
|
|
63
|
),)
|
|
64
|
|
|
65
|
settings_defaults = {
|
|
66
|
|
|
67
|
'output_encoding': 'ascii',
|
|
68
|
'output_encoding_error_handler': 'strict',
|
|
69
|
|
|
70
|
|
|
71
|
'trim_footnote_reference_space': 1,
|
|
72
|
|
|
73
|
'docinfo_xform': 0,
|
|
74
|
|
|
75
|
'traceback': 1
|
|
76
|
}
|
|
77
|
|
|
78
|
relative_path_settings = ('stylesheet_path', 'user_stylesheet_path')
|
|
79
|
|
|
80
|
config_section = 'newlatex2e writer'
|
|
81
|
config_section_dependencies = ('writers',)
|
|
82
|
|
|
83
|
output = None
|
|
84
|
"""Final translated form of `document`."""
|
|
85
|
|
|
86
|
def get_transforms(self):
|
|
87
|
return writers.Writer.get_transforms(self) + [writer_aux.Compound]
|
|
88
|
|
|
89
|
def __init__(self):
|
|
90
|
writers.Writer.__init__(self)
|
|
91
|
self.translator_class = LaTeXTranslator
|
|
92
|
|
|
93
|
def translate(self):
|
|
94
|
visitor = self.translator_class(self.document)
|
|
95
|
self.document.walkabout(visitor)
|
|
96
|
assert not visitor.context, 'context not empty: %s' % visitor.context
|
|
97
|
self.output = visitor.astext()
|
|
98
|
self.head = visitor.header
|
|
99
|
self.body = visitor.body
|
|
100
|
|
|
101
|
|
|
102
|
class LaTeXException(Exception):
|
|
103
|
"""
|
|
104
|
Exception base class to for exceptions which influence the
|
|
105
|
automatic generation of LaTeX code.
|
|
106
|
"""
|
|
107
|
|
|
108
|
|
|
109
|
class SkipAttrParentLaTeX(LaTeXException):
|
|
110
|
"""
|
|
111
|
Do not generate ``\Dattr`` and ``\renewcommand{\Dparent}{...}`` for this
|
|
112
|
node.
|
|
113
|
|
|
114
|
To be raised from ``before_...`` methods.
|
|
115
|
"""
|
|
116
|
|
|
117
|
|
|
118
|
class SkipParentLaTeX(LaTeXException):
|
|
119
|
"""
|
|
120
|
Do not generate ``\renewcommand{\DNparent}{...}`` for this node.
|
|
121
|
|
|
122
|
To be raised from ``before_...`` methods.
|
|
123
|
"""
|
|
124
|
|
|
125
|
|
|
126
|
class LaTeXTranslator(nodes.SparseNodeVisitor):
|
|
127
|
|
|
128
|
|
|
129
|
|
|
130
|
iso639_to_babel = {
|
|
131
|
'no': 'norsk',
|
|
132
|
'gd': 'scottish',
|
|
133
|
'sl': 'slovenian',
|
|
134
|
'af': 'afrikaans',
|
|
135
|
'bg': 'bulgarian',
|
|
136
|
'br': 'breton',
|
|
137
|
'ca': 'catalan',
|
|
138
|
'cs': 'czech',
|
|
139
|
'cy': 'welsh',
|
|
140
|
'da': 'danish',
|
|
141
|
'fr': 'french',
|
|
142
|
|
|
143
|
'de': 'ngerman',
|
|
144
|
|
|
145
|
'el': 'greek',
|
|
146
|
'en': 'english',
|
|
147
|
|
|
148
|
'eo': 'esperanto',
|
|
149
|
'es': 'spanish',
|
|
150
|
'et': 'estonian',
|
|
151
|
'eu': 'basque',
|
|
152
|
'fi': 'finnish',
|
|
153
|
'ga': 'irish',
|
|
154
|
'gl': 'galician',
|
|
155
|
'he': 'hebrew',
|
|
156
|
'hr': 'croatian',
|
|
157
|
'hu': 'hungarian',
|
|
158
|
'is': 'icelandic',
|
|
159
|
'it': 'italian',
|
|
160
|
'la': 'latin',
|
|
161
|
'nl': 'dutch',
|
|
162
|
'pl': 'polish',
|
|
163
|
'pt': 'portuguese',
|
|
164
|
'ro': 'romanian',
|
|
165
|
'ru': 'russian',
|
|
166
|
'sk': 'slovak',
|
|
167
|
'sr': 'serbian',
|
|
168
|
'sv': 'swedish',
|
|
169
|
'tr': 'turkish',
|
|
170
|
'uk': 'ukrainian'
|
|
171
|
}
|
|
172
|
|
|
173
|
|
|
174
|
left_quote = 1
|
|
175
|
|
|
176
|
def __init__(self, document):
|
|
177
|
nodes.NodeVisitor.__init__(self, document)
|
|
178
|
self.settings = document.settings
|
|
179
|
self.header = []
|
|
180
|
self.body = []
|
|
181
|
self.context = []
|
|
182
|
self.stylesheet_path = utils.get_stylesheet_reference(
|
|
183
|
self.settings, os.path.join(os.getcwd(), 'dummy'))
|
|
184
|
if self.stylesheet_path:
|
|
185
|
self.settings.record_dependencies.add(self.stylesheet_path)
|
|
186
|
|
|
187
|
|
|
188
|
self.settings.stylesheet = self.settings.user_stylesheet
|
|
189
|
self.settings.stylesheet_path = self.settings.user_stylesheet_path
|
|
190
|
self.user_stylesheet_path = utils.get_stylesheet_reference(
|
|
191
|
self.settings, os.path.join(os.getcwd(), 'dummy'))
|
|
192
|
if self.user_stylesheet_path:
|
|
193
|
self.settings.record_dependencies.add(self.user_stylesheet_path)
|
|
194
|
self.write_header()
|
|
195
|
|
|
196
|
def write_header(self):
|
|
197
|
a = self.header.append
|
|
198
|
a('%% Generated by Docutils %s <http://docutils.sourceforge.net>.'
|
|
199
|
% docutils.__version__)
|
|
200
|
a('')
|
|
201
|
a('% Docutils settings:')
|
|
202
|
lang = self.settings.language_code or ''
|
|
203
|
a(r'\providecommand{\Dlanguageiso}{%s}' % lang)
|
|
204
|
a(r'\providecommand{\Dlanguagebabel}{%s}' % self.iso639_to_babel.get(
|
|
205
|
lang, self.iso639_to_babel.get(lang.split('_')[0], '')))
|
|
206
|
a('')
|
|
207
|
if self.user_stylesheet_path:
|
|
208
|
a('% User stylesheet:')
|
|
209
|
a(r'\input{%s}' % self.user_stylesheet_path)
|
|
210
|
a('% Docutils stylesheet:')
|
|
211
|
a(r'\input{%s}' % self.stylesheet_path)
|
|
212
|
a('')
|
|
213
|
a('% Default definitions for Docutils nodes:')
|
|
214
|
for node_name in nodes.node_class_names:
|
|
215
|
a(r'\providecommand{\DN%s}[1]{#1}' % node_name.replace('_', ''))
|
|
216
|
a('')
|
|
217
|
a('% Auxiliary definitions:')
|
|
218
|
a(r'\providecommand{\Dsetattr}[2]{}')
|
|
219
|
a(r'\providecommand{\Dparent}{} % variable')
|
|
220
|
a(r'\providecommand{\Dattr}[5]{#5}')
|
|
221
|
a(r'\providecommand{\Dattrlen}{} % variable')
|
|
222
|
a(r'\providecommand{\Dtitleastext}{x} % variable')
|
|
223
|
a(r'\providecommand{\Dsinglebackref}{} % variable')
|
|
224
|
a(r'\providecommand{\Dmultiplebackrefs}{} % variable')
|
|
225
|
a(r'\providecommand{\Dparagraphindented}{false} % variable')
|
|
226
|
a('\n\n')
|
|
227
|
|
|
228
|
unicode_map = unicode_map.unicode_map
|
|
229
|
|
|
230
|
unicode_map.update({
|
|
231
|
|
|
232
|
|
|
233
|
u'\u201C': '{``}',
|
|
234
|
u'\u201D': "{''}",
|
|
235
|
u'\u201E': '{,,}',
|
|
236
|
})
|
|
237
|
|
|
238
|
character_map = {
|
|
239
|
'\\': r'{\textbackslash}',
|
|
240
|
'{': r'{\{}',
|
|
241
|
'}': r'{\}}',
|
|
242
|
'$': r'{\$}',
|
|
243
|
'&': r'{\&}',
|
|
244
|
'%': r'{\%}',
|
|
245
|
'#': r'{\#}',
|
|
246
|
'[': r'{[}',
|
|
247
|
']': r'{]}',
|
|
248
|
'-': r'{-}',
|
|
249
|
'`': r'{`}',
|
|
250
|
"'": r"{'}",
|
|
251
|
',': r'{,}',
|
|
252
|
'"': r'{"}',
|
|
253
|
'|': r'{\textbar}',
|
|
254
|
'<': r'{\textless}',
|
|
255
|
'>': r'{\textgreater}',
|
|
256
|
'^': r'{\textasciicircum}',
|
|
257
|
'~': r'{\textasciitilde}',
|
|
258
|
'_': r'{\Dtextunderscore}',
|
|
259
|
}
|
|
260
|
character_map.update(unicode_map)
|
|
261
|
|
|
262
|
|
|
263
|
|
|
264
|
|
|
265
|
|
|
266
|
|
|
267
|
|
|
268
|
att_map = {'#': '\\#',
|
|
269
|
'%': '\\%',
|
|
270
|
|
|
271
|
'\\': '',
|
|
272
|
'{': '\\{',
|
|
273
|
'}': '\\}',
|
|
274
|
|
|
275
|
'"': '"{}',
|
|
276
|
}
|
|
277
|
att_map.update(unicode_map)
|
|
278
|
|
|
279
|
def encode(self, text, attval=None):
|
|
280
|
"""
|
|
281
|
Encode special characters in ``text`` and return it.
|
|
282
|
|
|
283
|
If attval is true, preserve as much as possible verbatim (used
|
|
284
|
in attribute value encoding). If attval is 'width' or
|
|
285
|
'height', `text` is interpreted as a length value.
|
|
286
|
"""
|
|
287
|
if attval in ('width', 'height'):
|
|
288
|
match = re.match(r'([0-9.]+)(\S*)$', text)
|
|
289
|
assert match, '%s="%s" must be a length' % (attval, text)
|
|
290
|
value, unit = match.groups()
|
|
291
|
if unit == '%':
|
|
292
|
value = str(float(value) / 100)
|
|
293
|
unit = r'\Drelativeunit'
|
|
294
|
elif unit in ('', 'px'):
|
|
295
|
|
|
296
|
|
|
297
|
value = str(float(value) * 0.75)
|
|
298
|
unit = '\Dpixelunit'
|
|
299
|
return '%s%s' % (value, unit)
|
|
300
|
if attval:
|
|
301
|
get = self.att_map.get
|
|
302
|
else:
|
|
303
|
get = self.character_map.get
|
|
304
|
text = ''.join([get(c, c) for c in text])
|
|
305
|
if (self.literal_block or self.inline_literal) and not attval:
|
|
306
|
|
|
307
|
|
|
308
|
text = text.replace('\r\n', '\n')
|
|
309
|
|
|
310
|
|
|
311
|
|
|
312
|
|
|
313
|
|
|
314
|
|
|
315
|
if self.literal_block:
|
|
316
|
|
|
317
|
text = text.replace('\n', '\mbox{}\\\\')
|
|
318
|
replace_fn = self.encode_replace_for_literal_block_spaces
|
|
319
|
else:
|
|
320
|
replace_fn = self.encode_replace_for_inline_literal_spaces
|
|
321
|
text = re.sub(r'\s+', replace_fn, text)
|
|
322
|
|
|
323
|
|
|
324
|
|
|
325
|
text = text.replace('-', r'\mbox{-}')
|
|
326
|
text = text.replace("'", r'{\Dtextliteralsinglequote}')
|
|
327
|
return text
|
|
328
|
else:
|
|
329
|
if not attval:
|
|
330
|
|
|
331
|
text = re.sub(r'\s+', '{ }', text)
|
|
332
|
|
|
333
|
L = []
|
|
334
|
for part in text.split(self.character_map['"']):
|
|
335
|
if L:
|
|
336
|
|
|
337
|
L.append(self.left_quote and r'{\Dtextleftdblquote}'
|
|
338
|
or r'{\Dtextrightdblquote}')
|
|
339
|
self.left_quote = not self.left_quote
|
|
340
|
L.append(part)
|
|
341
|
return ''.join(L)
|
|
342
|
else:
|
|
343
|
return text
|
|
344
|
|
|
345
|
def encode_replace_for_literal_block_spaces(self, match):
|
|
346
|
return '~' * len(match.group())
|
|
347
|
|
|
348
|
def encode_replace_for_inline_literal_spaces(self, match):
|
|
349
|
return '{ }' + '~' * (len(match.group()) - 1)
|
|
350
|
|
|
351
|
def astext(self):
|
|
352
|
return '\n'.join(self.header) + (''.join(self.body))
|
|
353
|
|
|
354
|
def append(self, text, newline='%\n'):
|
|
355
|
"""
|
|
356
|
Append text, stripping newlines, producing nice LaTeX code.
|
|
357
|
"""
|
|
358
|
lines = [' ' * self.indentation_level + line + newline
|
|
359
|
for line in text.splitlines(0)]
|
|
360
|
self.body.append(''.join(lines))
|
|
361
|
|
|
362
|
def visit_Text(self, node):
|
|
363
|
self.append(self.encode(node.astext()))
|
|
364
|
|
|
365
|
def depart_Text(self, node):
|
|
366
|
pass
|
|
367
|
|
|
368
|
def is_indented(self, paragraph):
|
|
369
|
"""Return true if `paragraph` should be first-line-indented."""
|
|
370
|
assert isinstance(paragraph, nodes.paragraph)
|
|
371
|
siblings = [n for n in paragraph.parent if
|
|
372
|
self.is_visible(n) and not isinstance(n, nodes.Titular)]
|
|
373
|
index = siblings.index(paragraph)
|
|
374
|
if ('continued' in paragraph['classes'] or
|
|
375
|
index > 0 and isinstance(siblings[index-1], nodes.transition)):
|
|
376
|
return 0
|
|
377
|
|
|
378
|
return index > 0
|
|
379
|
|
|
380
|
def before_paragraph(self, node):
|
|
381
|
self.append(r'\renewcommand{\Dparagraphindented}{%s}'
|
|
382
|
% (self.is_indented(node) and 'true' or 'false'))
|
|
383
|
|
|
384
|
def before_title(self, node):
|
|
385
|
self.append(r'\renewcommand{\Dtitleastext}{%s}'
|
|
386
|
% self.encode(node.astext()))
|
|
387
|
self.append(r'\renewcommand{\Dhassubtitle}{%s}'
|
|
388
|
% ((len(node.parent) > 2 and
|
|
389
|
isinstance(node.parent[1], nodes.subtitle))
|
|
390
|
and 'true' or 'false'))
|
|
391
|
|
|
392
|
def before_generated(self, node):
|
|
393
|
if 'sectnum' in node['classes']:
|
|
394
|
node[0] = node[0].strip()
|
|
395
|
|
|
396
|
literal_block = 0
|
|
397
|
|
|
398
|
def visit_literal_block(self, node):
|
|
399
|
self.literal_block = 1
|
|
400
|
|
|
401
|
def depart_literal_block(self, node):
|
|
402
|
self.literal_block = 0
|
|
403
|
|
|
404
|
visit_doctest_block = visit_literal_block
|
|
405
|
depart_doctest_block = depart_literal_block
|
|
406
|
|
|
407
|
inline_literal = 0
|
|
408
|
|
|
409
|
def visit_literal(self, node):
|
|
410
|
self.inline_literal += 1
|
|
411
|
|
|
412
|
def depart_literal(self, node):
|
|
413
|
self.inline_literal -= 1
|
|
414
|
|
|
415
|
def visit_comment(self, node):
|
|
416
|
self.append('\n'.join(['% ' + line for line
|
|
417
|
in node.astext().splitlines(0)]), newline='\n')
|
|
418
|
raise nodes.SkipChildren
|
|
419
|
|
|
420
|
def before_topic(self, node):
|
|
421
|
if 'contents' in node['classes']:
|
|
422
|
for bullet_list in list(node.traverse(nodes.bullet_list)):
|
|
423
|
p = bullet_list.parent
|
|
424
|
if isinstance(p, nodes.list_item):
|
|
425
|
p.parent.insert(p.parent.index(p) + 1, bullet_list)
|
|
426
|
del p[1]
|
|
427
|
for paragraph in node.traverse(nodes.paragraph):
|
|
428
|
paragraph.attributes.update(paragraph[0].attributes)
|
|
429
|
paragraph[:] = paragraph[0]
|
|
430
|
paragraph.parent['tocrefid'] = paragraph['refid']
|
|
431
|
node['contents'] = 1
|
|
432
|
else:
|
|
433
|
node['contents'] = 0
|
|
434
|
|
|
435
|
bullet_list_level = 0
|
|
436
|
|
|
437
|
def visit_bullet_list(self, node):
|
|
438
|
self.append(r'\Dsetbullet{\labelitem%s}' %
|
|
439
|
['i', 'ii', 'iii', 'iv'][min(self.bullet_list_level, 3)])
|
|
440
|
self.bullet_list_level += 1
|
|
441
|
|
|
442
|
def depart_bullet_list(self, node):
|
|
443
|
self.bullet_list_level -= 1
|
|
444
|
|
|
445
|
enum_styles = {'arabic': 'arabic', 'loweralpha': 'alph', 'upperalpha':
|
|
446
|
'Alph', 'lowerroman': 'roman', 'upperroman': 'Roman'}
|
|
447
|
|
|
448
|
enum_counter = 0
|
|
449
|
|
|
450
|
def visit_enumerated_list(self, node):
|
|
451
|
|
|
452
|
|
|
453
|
|
|
454
|
self.enum_counter += 1
|
|
455
|
enum_prefix = self.encode(node['prefix'])
|
|
456
|
enum_suffix = self.encode(node['suffix'])
|
|
457
|
enum_type = '\\' + self.enum_styles.get(node['enumtype'], r'arabic')
|
|
458
|
start = node.get('start', 1) - 1
|
|
459
|
counter = 'Denumcounter%d' % self.enum_counter
|
|
460
|
self.append(r'\Dmakeenumeratedlist{%s}{%s}{%s}{%s}{%s}{'
|
|
461
|
% (enum_prefix, enum_type, enum_suffix, counter, start))
|
|
462
|
|
|
463
|
|
|
464
|
def depart_enumerated_list(self, node):
|
|
465
|
self.append('}')
|
|
466
|
|
|
467
|
def before_list_item(self, node):
|
|
468
|
|
|
469
|
if (len(node) and (isinstance(node[-1], nodes.TextElement) or
|
|
470
|
isinstance(node[-1], nodes.Text)) and
|
|
471
|
node.parent.index(node) == len(node.parent) - 1):
|
|
472
|
node['lastitem'] = 'true'
|
|
473
|
|
|
474
|
before_line = before_list_item
|
|
475
|
|
|
476
|
def before_raw(self, node):
|
|
477
|
if 'latex' in node.get('format', '').split():
|
|
478
|
|
|
479
|
|
|
480
|
|
|
481
|
self.append(node.astext())
|
|
482
|
raise nodes.SkipChildren
|
|
483
|
|
|
484
|
def process_backlinks(self, node, type):
|
|
485
|
self.append(r'\renewcommand{\Dsinglebackref}{}')
|
|
486
|
self.append(r'\renewcommand{\Dmultiplebackrefs}{}')
|
|
487
|
if len(node['backrefs']) > 1:
|
|
488
|
refs = []
|
|
489
|
for i in range(len(node['backrefs'])):
|
|
490
|
refs.append(r'\Dmulti%sbacklink{%s}{%s}'
|
|
491
|
% (type, node['backrefs'][i], i + 1))
|
|
492
|
self.append(r'\renewcommand{\Dmultiplebackrefs}{(%s){ }}'
|
|
493
|
% ', '.join(refs))
|
|
494
|
elif len(node['backrefs']) == 1:
|
|
495
|
self.append(r'\renewcommand{\Dsinglebackref}{%s}'
|
|
496
|
% node['backrefs'][0])
|
|
497
|
|
|
498
|
def visit_footnote(self, node):
|
|
499
|
self.process_backlinks(node, 'footnote')
|
|
500
|
|
|
501
|
def visit_citation(self, node):
|
|
502
|
self.process_backlinks(node, 'citation')
|
|
503
|
|
|
504
|
def before_table(self, node):
|
|
505
|
|
|
506
|
pass
|
|
507
|
|
|
508
|
def before_tgroup(self, node):
|
|
509
|
widths = []
|
|
510
|
total_width = 0
|
|
511
|
for i in range(int(node['cols'])):
|
|
512
|
assert isinstance(node[i], nodes.colspec)
|
|
513
|
widths.append(int(node[i]['colwidth']) + 1)
|
|
514
|
total_width += widths[-1]
|
|
515
|
del node[:len(widths)]
|
|
516
|
tablespec = '|'
|
|
517
|
for w in widths:
|
|
518
|
|
|
519
|
|
|
520
|
tablespec += r'p{%s\textwidth}|' % (0.93 * w /
|
|
521
|
max(total_width, 60))
|
|
522
|
self.append(r'\Dmaketable{%s}{' % tablespec)
|
|
523
|
self.context.append('}')
|
|
524
|
raise SkipAttrParentLaTeX
|
|
525
|
|
|
526
|
def depart_tgroup(self, node):
|
|
527
|
self.append(self.context.pop())
|
|
528
|
|
|
529
|
def before_row(self, node):
|
|
530
|
raise SkipAttrParentLaTeX
|
|
531
|
|
|
532
|
def before_thead(self, node):
|
|
533
|
raise SkipAttrParentLaTeX
|
|
534
|
|
|
535
|
def before_tbody(self, node):
|
|
536
|
raise SkipAttrParentLaTeX
|
|
537
|
|
|
538
|
def is_simply_entry(self, node):
|
|
539
|
return (len(node) == 1 and isinstance(node[0], nodes.paragraph) or
|
|
540
|
len(node) == 0)
|
|
541
|
|
|
542
|
def before_entry(self, node):
|
|
543
|
is_leftmost = 0
|
|
544
|
if node.hasattr('morerows'):
|
|
545
|
self.document.reporter.severe('Rowspans are not supported.')
|
|
546
|
|
|
547
|
|
|
548
|
if node.hasattr('morecols'):
|
|
549
|
|
|
550
|
|
|
551
|
if not self.is_simply_entry(node):
|
|
552
|
self.document.reporter.severe(
|
|
553
|
'Colspanning table cells may only contain one paragraph.')
|
|
554
|
|
|
555
|
|
|
556
|
colspan = int(node['morecols']) + 1
|
|
557
|
del node['morecols']
|
|
558
|
else:
|
|
559
|
colspan = 1
|
|
560
|
|
|
561
|
macro_name = r'\Dcolspan'
|
|
562
|
if node.parent.index(node) == 0:
|
|
563
|
|
|
564
|
macro_name += 'left'
|
|
565
|
is_leftmost = 1
|
|
566
|
if colspan > 1:
|
|
567
|
self.append('%s{%s}{' % (macro_name, colspan))
|
|
568
|
self.context.append('}')
|
|
569
|
else:
|
|
570
|
|
|
571
|
|
|
572
|
|
|
573
|
|
|
574
|
if not is_leftmost:
|
|
575
|
self.append(r'\Dsubsequententry{')
|
|
576
|
self.context.append('}')
|
|
577
|
else:
|
|
578
|
self.context.append('')
|
|
579
|
if isinstance(node.parent.parent, nodes.thead):
|
|
580
|
node['tableheaderentry'] = 'true'
|
|
581
|
|
|
582
|
|
|
583
|
|
|
584
|
raise SkipParentLaTeX
|
|
585
|
|
|
586
|
def depart_entry(self, node):
|
|
587
|
self.append(self.context.pop())
|
|
588
|
|
|
589
|
def before_substitution_definition(self, node):
|
|
590
|
raise nodes.SkipNode
|
|
591
|
|
|
592
|
indentation_level = 0
|
|
593
|
|
|
594
|
def node_name(self, node):
|
|
595
|
return node.__class__.__name__.replace('_', '')
|
|
596
|
|
|
597
|
|
|
598
|
attribute_order = ['align', 'classes', 'ids']
|
|
599
|
|
|
600
|
def attribute_cmp(self, a1, a2):
|
|
601
|
"""
|
|
602
|
Compare attribute names `a1` and `a2`. Used in
|
|
603
|
propagate_attributes to determine propagation order.
|
|
604
|
|
|
605
|
See built-in function `cmp` for return value.
|
|
606
|
"""
|
|
607
|
if a1 in self.attribute_order and a2 in self.attribute_order:
|
|
608
|
return cmp(self.attribute_order.index(a1),
|
|
609
|
self.attribute_order.index(a2))
|
|
610
|
if (a1 in self.attribute_order) != (a2 in self.attribute_order):
|
|
611
|
|
|
612
|
return a1 in self.attribute_order and -1 or 1
|
|
613
|
else:
|
|
614
|
return cmp(a1, a2)
|
|
615
|
|
|
616
|
def propagate_attributes(self, node):
|
|
617
|
|
|
618
|
node_name = self.node_name(node)
|
|
619
|
attlist = []
|
|
620
|
if isinstance(node, nodes.Element):
|
|
621
|
attlist = node.attlist()
|
|
622
|
attlist.sort(lambda pair1, pair2: self.attribute_cmp(pair1[0],
|
|
623
|
pair2[0]))
|
|
624
|
|
|
625
|
|
|
626
|
numatts = 0
|
|
627
|
pass_contents = self.pass_contents(node)
|
|
628
|
for key, value in attlist:
|
|
629
|
if isinstance(value, ListType):
|
|
630
|
self.append(r'\renewcommand{\Dattrlen}{%s}' % len(value))
|
|
631
|
for i in range(len(value)):
|
|
632
|
self.append(r'\Dattr{%s}{%s}{%s}{%s}{' %
|
|
633
|
(i+1, key, self.encode(value[i], attval=key),
|
|
634
|
node_name))
|
|
635
|
if not pass_contents:
|
|
636
|
self.append('}')
|
|
637
|
numatts += len(value)
|
|
638
|
else:
|
|
639
|
self.append(r'\Dattr{}{%s}{%s}{%s}{' %
|
|
640
|
(key, self.encode(unicode(value), attval=key),
|
|
641
|
node_name))
|
|
642
|
if not pass_contents:
|
|
643
|
self.append('}')
|
|
644
|
numatts += 1
|
|
645
|
if pass_contents:
|
|
646
|
self.context.append('}' * numatts)
|
|
647
|
else:
|
|
648
|
self.context.append('')
|
|
649
|
|
|
650
|
def visit_docinfo(self, node):
|
|
651
|
raise NotImplementedError('Docinfo not yet implemented.')
|
|
652
|
|
|
653
|
def visit_document(self, node):
|
|
654
|
document = node
|
|
655
|
|
|
656
|
|
|
657
|
for node in document.traverse(nodes.Element):
|
|
658
|
if node.has_key('ids') and not isinstance(node,
|
|
659
|
nodes.TextElement):
|
|
660
|
next_text_element = node.next_node(nodes.TextElement)
|
|
661
|
if next_text_element:
|
|
662
|
next_text_element['ids'].extend(node['ids'])
|
|
663
|
node['ids'] = []
|
|
664
|
|
|
665
|
def pass_contents(self, node):
|
|
666
|
r"""
|
|
667
|
Return true if the node contents should be passed in
|
|
668
|
parameters of \DN... and \Dattr.
|
|
669
|
"""
|
|
670
|
return not isinstance(node, (nodes.document, nodes.section))
|
|
671
|
|
|
672
|
def dispatch_visit(self, node):
|
|
673
|
skip_attr = skip_parent = 0
|
|
674
|
|
|
675
|
tree_pruning_exception = None
|
|
676
|
if hasattr(self, 'before_' + node.__class__.__name__):
|
|
677
|
try:
|
|
678
|
getattr(self, 'before_' + node.__class__.__name__)(node)
|
|
679
|
except SkipParentLaTeX:
|
|
680
|
skip_parent = 1
|
|
681
|
except SkipAttrParentLaTeX:
|
|
682
|
skip_attr = 1
|
|
683
|
skip_parent = 1
|
|
684
|
except nodes.SkipNode:
|
|
685
|
raise
|
|
686
|
except (nodes.SkipChildren, nodes.SkipSiblings), instance:
|
|
687
|
tree_pruning_exception = instance
|
|
688
|
except nodes.SkipDeparture:
|
|
689
|
raise NotImplementedError(
|
|
690
|
'SkipDeparture not usable in LaTeX writer')
|
|
691
|
|
|
692
|
if not isinstance(node, nodes.Text):
|
|
693
|
node_name = self.node_name(node)
|
|
694
|
|
|
695
|
attribute_deleters = []
|
|
696
|
if not skip_parent and not isinstance(node, nodes.document):
|
|
697
|
self.append(r'\renewcommand{\Dparent}{%s}'
|
|
698
|
% self.node_name(node.parent))
|
|
699
|
for name, value in node.attlist():
|
|
700
|
if not isinstance(value, ListType) and not ':' in name:
|
|
701
|
macro = r'\DcurrentN%sA%s' % (node_name, name)
|
|
702
|
self.append(r'\def%s{%s}' % (
|
|
703
|
macro, self.encode(unicode(value), attval=name)))
|
|
704
|
attribute_deleters.append(r'\let%s=\relax' % macro)
|
|
705
|
self.context.append('\n'.join(attribute_deleters))
|
|
706
|
if self.pass_contents(node):
|
|
707
|
self.append(r'\DN%s{' % node_name)
|
|
708
|
self.context.append('}')
|
|
709
|
else:
|
|
710
|
self.append(r'\Dvisit%s' % node_name)
|
|
711
|
self.context.append(r'\Ddepart%s' % node_name)
|
|
712
|
self.indentation_level += 1
|
|
713
|
if not skip_attr:
|
|
714
|
self.propagate_attributes(node)
|
|
715
|
else:
|
|
716
|
self.context.append('')
|
|
717
|
|
|
718
|
if (isinstance(node, nodes.TextElement) and
|
|
719
|
not isinstance(node.parent, nodes.TextElement)):
|
|
720
|
|
|
721
|
self.left_quote = 1
|
|
722
|
|
|
723
|
|
|
724
|
try:
|
|
725
|
nodes.SparseNodeVisitor.dispatch_visit(self, node)
|
|
726
|
except LaTeXException:
|
|
727
|
raise NotImplementedError(
|
|
728
|
'visit_... methods must not raise LaTeXExceptions')
|
|
729
|
|
|
730
|
if tree_pruning_exception:
|
|
731
|
|
|
732
|
raise tree_pruning_exception
|
|
733
|
|
|
734
|
def is_invisible(self, node):
|
|
735
|
|
|
736
|
|
|
737
|
return (not isinstance(node, nodes.Text) and
|
|
738
|
(isinstance(node, nodes.Invisible) or
|
|
739
|
isinstance(node, nodes.footnote) or
|
|
740
|
isinstance(node, nodes.citation) or
|
|
741
|
|
|
742
|
isinstance(node, nodes.raw) or
|
|
743
|
|
|
744
|
node.get('align') in ('left', 'right')))
|
|
745
|
|
|
746
|
def is_visible(self, node):
|
|
747
|
return not self.is_invisible(node)
|
|
748
|
|
|
749
|
def needs_space(self, node):
|
|
750
|
"""Two nodes for which `needs_space` is true need auxiliary space."""
|
|
751
|
|
|
752
|
return ((isinstance(node, nodes.Body) or
|
|
753
|
isinstance(node, nodes.topic)) and
|
|
754
|
not (self.is_invisible(node) or
|
|
755
|
isinstance(node.parent, nodes.TextElement)))
|
|
756
|
|
|
757
|
def always_needs_space(self, node):
|
|
758
|
"""
|
|
759
|
Always add space around nodes for which `always_needs_space()`
|
|
760
|
is true, regardless of whether the other node needs space as
|
|
761
|
well. (E.g. transition next to section.)
|
|
762
|
"""
|
|
763
|
return isinstance(node, nodes.transition)
|
|
764
|
|
|
765
|
def dispatch_departure(self, node):
|
|
766
|
|
|
767
|
nodes.SparseNodeVisitor.dispatch_departure(self, node)
|
|
768
|
|
|
769
|
if not isinstance(node, nodes.Text):
|
|
770
|
|
|
771
|
self.indentation_level -= 1
|
|
772
|
self.append(self.context.pop() + self.context.pop())
|
|
773
|
|
|
774
|
self.append(self.context.pop())
|
|
775
|
|
|
776
|
next_node = node.next_node(
|
|
777
|
ascend=0, siblings=1, descend=0,
|
|
778
|
condition=self.is_visible)
|
|
779
|
|
|
780
|
if (self.needs_space(node) and self.needs_space(next_node) or
|
|
781
|
self.always_needs_space(node) or
|
|
782
|
self.always_needs_space(next_node)):
|
|
783
|
if isinstance(node, nodes.paragraph) and isinstance(next_node, nodes.paragraph):
|
|
784
|
|
|
785
|
self.append(r'\Dparagraphspace')
|
|
786
|
else:
|
|
787
|
|
|
788
|
self.append(r'\Dauxiliaryspace')
|