xml2rfc 3.26.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xml2rfc/__init__.py +68 -0
- xml2rfc/boilerplate_id_guidelines.py +28 -0
- xml2rfc/boilerplate_rfc_7841.py +575 -0
- xml2rfc/boilerplate_tlp.py +258 -0
- xml2rfc/data/SVG-1.2-RFC.rnc +1666 -0
- xml2rfc/data/SVG-1.2-RFC.rng +7460 -0
- xml2rfc/data/Scripts.txt +2693 -0
- xml2rfc/data/metadata.js +218 -0
- xml2rfc/data/metadata.min.js +1 -0
- xml2rfc/data/reference.rnc +955 -0
- xml2rfc/data/reference.rng +2158 -0
- xml2rfc/data/referencegroup.rnc +16 -0
- xml2rfc/data/referencegroup.rng +2180 -0
- xml2rfc/data/rfc7991.rnc +955 -0
- xml2rfc/data/rfc7991.rng +2143 -0
- xml2rfc/data/v2.rnc +309 -0
- xml2rfc/data/v2.rng +720 -0
- xml2rfc/data/v3.rnc +1144 -0
- xml2rfc/data/v3.rng +2538 -0
- xml2rfc/data/v3compat.dtd +630 -0
- xml2rfc/data/v3compat.rnc +985 -0
- xml2rfc/data/xml2rfc-v3-emacs-nxml.rnc +1083 -0
- xml2rfc/data/xml2rfc.css +1171 -0
- xml2rfc/data/xml2rfc.js +7 -0
- xml2rfc/debug.py +221 -0
- xml2rfc/log.py +54 -0
- xml2rfc/parser.py +815 -0
- xml2rfc/run.py +801 -0
- xml2rfc/scripts.py +52 -0
- xml2rfc/strings.py +26 -0
- xml2rfc/templates/address_card.html +21 -0
- xml2rfc/templates/base.html +415 -0
- xml2rfc/templates/doc.xml +367 -0
- xml2rfc/templates/doc.yaml +2138 -0
- xml2rfc/templates/rfc2629-other.ent +61 -0
- xml2rfc/templates/rfc2629-xhtml.ent +170 -0
- xml2rfc/templates/rfc2629.dtd +328 -0
- xml2rfc/uniscripts/__init__.py +97 -0
- xml2rfc/uniscripts/unidata.py +306 -0
- xml2rfc/uniscripts/update/make_unidata.py +94 -0
- xml2rfc/util/__init__.py +7 -0
- xml2rfc/util/date.py +94 -0
- xml2rfc/util/fonts.py +53 -0
- xml2rfc/util/name.py +229 -0
- xml2rfc/util/num.py +119 -0
- xml2rfc/util/postal.py +285 -0
- xml2rfc/util/unicode.py +441 -0
- xml2rfc/utils.py +672 -0
- xml2rfc/walkpdf.py +113 -0
- xml2rfc/writers/__init__.py +25 -0
- xml2rfc/writers/base.py +2248 -0
- xml2rfc/writers/bib.py +43 -0
- xml2rfc/writers/doc.py +354 -0
- xml2rfc/writers/expand.py +51 -0
- xml2rfc/writers/expanded_xml.py +39 -0
- xml2rfc/writers/html.py +3110 -0
- xml2rfc/writers/legacy_html.py +860 -0
- xml2rfc/writers/nroff.py +345 -0
- xml2rfc/writers/paginated_txt.py +432 -0
- xml2rfc/writers/pdf.py +230 -0
- xml2rfc/writers/preptool.py +2428 -0
- xml2rfc/writers/raw_txt.py +1453 -0
- xml2rfc/writers/text.py +5000 -0
- xml2rfc/writers/unprep.py +187 -0
- xml2rfc/writers/v2v3.py +1150 -0
- xml2rfc-3.26.0.dist-info/LICENSE +29 -0
- xml2rfc-3.26.0.dist-info/METADATA +178 -0
- xml2rfc-3.26.0.dist-info/RECORD +71 -0
- xml2rfc-3.26.0.dist-info/WHEEL +5 -0
- xml2rfc-3.26.0.dist-info/entry_points.txt +2 -0
- xml2rfc-3.26.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1453 @@
|
|
|
1
|
+
# --------------------------------------------------
|
|
2
|
+
# Copyright The IETF Trust 2011, All Rights Reserved
|
|
3
|
+
# --------------------------------------------------
|
|
4
|
+
|
|
5
|
+
# Python libs
|
|
6
|
+
import textwrap
|
|
7
|
+
import lxml
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from xml2rfc import debug
|
|
12
|
+
assert debug
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass
|
|
15
|
+
|
|
16
|
+
# Local lib
|
|
17
|
+
from xml2rfc.writers.base import BaseRfcWriter, default_options
|
|
18
|
+
from xml2rfc.util.unicode import expand_unicode_element
|
|
19
|
+
import xml2rfc.utils
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class RawTextRfcWriter(BaseRfcWriter):
|
|
23
|
+
""" Writes to a text file, unpaginated, no headers or footers.
|
|
24
|
+
|
|
25
|
+
The page width is controlled by the *width* parameter.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, xmlrfc, width=72, margin=3, quiet=None, options=default_options, date=None):
|
|
29
|
+
if not quiet is None:
|
|
30
|
+
options.quiet = quiet
|
|
31
|
+
BaseRfcWriter.__init__(self, xmlrfc, options=options, date=date)
|
|
32
|
+
# Document processing data
|
|
33
|
+
self.width = width # Page width
|
|
34
|
+
self.margin = margin # Page margin
|
|
35
|
+
self.buf = [] # Main buffer during processing
|
|
36
|
+
self.output = [] # Final buffer that gets written to disk
|
|
37
|
+
self.toc_marker = 0 # Line number in buffer to write toc to
|
|
38
|
+
self.iref_marker = 0 # Line number in buffer to write index to
|
|
39
|
+
self.list_counters = {} # Maintain counters for 'format' type lists
|
|
40
|
+
self.edit_counter = 0 # Counter for edit marks
|
|
41
|
+
# Set this to False to permit utf+8 output:
|
|
42
|
+
self.ascii = not options.utf8 # Enable ascii flag
|
|
43
|
+
self.cref_counter = 0 # Counter for cref anchors
|
|
44
|
+
self.cref_list = []
|
|
45
|
+
|
|
46
|
+
# Marks position of iref elements
|
|
47
|
+
self.iref_marks = {}
|
|
48
|
+
|
|
49
|
+
# Text lookups
|
|
50
|
+
self.inline_tags = ['xref', 'eref', 'iref', 'cref', 'spanx', 'u', ]
|
|
51
|
+
|
|
52
|
+
# Custom textwrapper object
|
|
53
|
+
self.wrapper = xml2rfc.utils.TextWrapper(width=self.width,
|
|
54
|
+
fix_sentence_endings=True)
|
|
55
|
+
|
|
56
|
+
def _length(self, text):
|
|
57
|
+
return len(text)
|
|
58
|
+
|
|
59
|
+
def _lb(self, buf=None, text='', num=1):
|
|
60
|
+
""" Write a blank line to the file, with optional filler text
|
|
61
|
+
|
|
62
|
+
Filler text is usually used by editing marks
|
|
63
|
+
"""
|
|
64
|
+
if num > 0:
|
|
65
|
+
if buf is None:
|
|
66
|
+
buf = self.buf
|
|
67
|
+
buf.extend([text] * num)
|
|
68
|
+
|
|
69
|
+
def _vspace(self, num=0):
|
|
70
|
+
""" <vspace> line break wrapper to allow for overrides """
|
|
71
|
+
return self._lb(num=num)
|
|
72
|
+
|
|
73
|
+
def write_text(self, string, indent=0, sub_indent=0, bullet='',
|
|
74
|
+
align='left', leading_blankline=False, buf=None,
|
|
75
|
+
strip=True, edit=False, wrap_urls=True,
|
|
76
|
+
fix_sentence_endings=True, source_line=None,
|
|
77
|
+
fix_doublespace=True):
|
|
78
|
+
""" Writes a line or multiple lines of text to the buffer.
|
|
79
|
+
|
|
80
|
+
Several parameters are included here. All of the API calls
|
|
81
|
+
for text writers use this as the underlying method to write data
|
|
82
|
+
to the buffer, with the exception of write_raw() that handles
|
|
83
|
+
preserving of whitespace.
|
|
84
|
+
"""
|
|
85
|
+
if buf is None:
|
|
86
|
+
buf = self.buf
|
|
87
|
+
|
|
88
|
+
if leading_blankline:
|
|
89
|
+
if edit and self.pis['editing'] == 'yes':
|
|
90
|
+
# Render an editing mark
|
|
91
|
+
self.edit_counter += 1
|
|
92
|
+
self._lb(buf=buf, text=str('<' + str(self.edit_counter) + '>'))
|
|
93
|
+
else:
|
|
94
|
+
self._lb(buf=buf)
|
|
95
|
+
|
|
96
|
+
# We can take advantage of textwrap's initial_indent by using a bullet
|
|
97
|
+
# parameter and treating it separately. We still need to indent it.
|
|
98
|
+
subsequent = ' ' * (indent + sub_indent)
|
|
99
|
+
if bullet:
|
|
100
|
+
initial = ' ' * indent
|
|
101
|
+
bullet_parts = self.wrapper.wrap(bullet, initial=initial,
|
|
102
|
+
fix_doublespace=False, drop_whitespace=False)
|
|
103
|
+
if len(bullet_parts) > 1:
|
|
104
|
+
buf.extend(bullet_parts[:-1])
|
|
105
|
+
initial = bullet_parts[-1]
|
|
106
|
+
if not sub_indent:
|
|
107
|
+
# Use bullet length for subsequent indents
|
|
108
|
+
subsequent = ' ' * len(initial)
|
|
109
|
+
else:
|
|
110
|
+
# No bullet, so combine indent and sub_indent
|
|
111
|
+
initial = subsequent
|
|
112
|
+
|
|
113
|
+
if string:
|
|
114
|
+
if strip:
|
|
115
|
+
# Strip initial whitespace
|
|
116
|
+
string = string.lstrip()
|
|
117
|
+
if wrap_urls:
|
|
118
|
+
par = self.wrapper.wrap(string, initial=initial, subsequent_indent=subsequent,
|
|
119
|
+
fix_sentence_endings=fix_sentence_endings,
|
|
120
|
+
fix_doublespace=fix_doublespace)
|
|
121
|
+
else:
|
|
122
|
+
par = self.wrapper.wrap(xml2rfc.utils.urlkeep(string), initial=initial,
|
|
123
|
+
subsequent_indent=subsequent,
|
|
124
|
+
fix_sentence_endings=fix_sentence_endings,
|
|
125
|
+
fix_doublespace=fix_doublespace)
|
|
126
|
+
if len(par) == 0 and bullet:
|
|
127
|
+
par = [ initial ]
|
|
128
|
+
if align == 'left':
|
|
129
|
+
buf.extend(par)
|
|
130
|
+
elif align == 'center':
|
|
131
|
+
for line in par:
|
|
132
|
+
margin = ' ' * indent
|
|
133
|
+
if line.startswith(margin):
|
|
134
|
+
line = line.rstrip()
|
|
135
|
+
centered = ' ' * ((self.width - len(line.rstrip()))//2)
|
|
136
|
+
centered += line
|
|
137
|
+
# centered = margin + line[len(margin):].center(self.width-indent-4).rstrip()
|
|
138
|
+
buf.append(centered)
|
|
139
|
+
else:
|
|
140
|
+
buf.append(line.center(self.width).rstrip())
|
|
141
|
+
elif align == 'right':
|
|
142
|
+
for line in par:
|
|
143
|
+
buf.append(line.rjust(self.width))
|
|
144
|
+
elif bullet:
|
|
145
|
+
# If the string is empty but a bullet was declared, just
|
|
146
|
+
# print the bullet
|
|
147
|
+
buf.append(initial)
|
|
148
|
+
|
|
149
|
+
def write_list(self, list, level=0, indent=3):
|
|
150
|
+
""" Writes a <list> element """
|
|
151
|
+
bullet = ' '
|
|
152
|
+
hangIndent = None
|
|
153
|
+
# style comes from the node if one exists
|
|
154
|
+
style = list.attrib.get('style', '')
|
|
155
|
+
if not style:
|
|
156
|
+
# otherwise look for the nearest list parent with a style and use it
|
|
157
|
+
for parent in list.iterancestors():
|
|
158
|
+
if parent.tag == 'list':
|
|
159
|
+
style = parent.attrib.get('style', '')
|
|
160
|
+
if style:
|
|
161
|
+
break
|
|
162
|
+
if not style:
|
|
163
|
+
style = 'empty'
|
|
164
|
+
listlength = len(list.findall('t'))
|
|
165
|
+
if style == 'hanging' or style.startswith('format'):
|
|
166
|
+
# Check for optional hangIndent
|
|
167
|
+
try:
|
|
168
|
+
hangIndent = list.attrib.get('hangIndent', None)
|
|
169
|
+
if hangIndent != None:
|
|
170
|
+
hangIndent = int(hangIndent)
|
|
171
|
+
except (ValueError, TypeError):
|
|
172
|
+
xml2rfc.log.error("hangIndent value '%s' is not an integer" % hangIndent)
|
|
173
|
+
hangIndent = 6
|
|
174
|
+
format_str = None
|
|
175
|
+
counter_index = None
|
|
176
|
+
if style.startswith('format'):
|
|
177
|
+
format_str = style.partition('format ')[2]
|
|
178
|
+
allowed_formats = ('%c', '%C', '%d', '%i', '%I', '%o', '%x', '%X')
|
|
179
|
+
if not any(map(lambda format: format in format_str, allowed_formats)):
|
|
180
|
+
xml2rfc.log.warn('Invalid format specified: %s '
|
|
181
|
+
'(Must be one of %s)' % (style,
|
|
182
|
+
', '.join(allowed_formats)))
|
|
183
|
+
counter_index = list.attrib.get('counter', None)
|
|
184
|
+
if not counter_index:
|
|
185
|
+
counter_index = 'temp' + str(level)
|
|
186
|
+
self.list_counters[counter_index] = 0
|
|
187
|
+
elif counter_index not in self.list_counters:
|
|
188
|
+
# Initialize if we need to
|
|
189
|
+
self.list_counters[counter_index] = 0
|
|
190
|
+
t_count = 0
|
|
191
|
+
for element in list:
|
|
192
|
+
# Check for PI
|
|
193
|
+
if element.tag is lxml.etree.PI:
|
|
194
|
+
pidict = self.parse_pi(element)
|
|
195
|
+
if pidict and "needLines" in pidict:
|
|
196
|
+
self.needLines(pidict["needLines"])
|
|
197
|
+
elif element.tag == 't':
|
|
198
|
+
# Disable linebreak if subcompact=yes AND not first list element
|
|
199
|
+
leading_blankline = True
|
|
200
|
+
if t_count > 0 and element.pis['subcompact'] == 'yes':
|
|
201
|
+
leading_blankline = False
|
|
202
|
+
if style == 'symbols':
|
|
203
|
+
bullet = element.pis['text-list-symbols'][level % len(element.pis['text-list-symbols'])]
|
|
204
|
+
bullet += ' '
|
|
205
|
+
elif style == 'numbers':
|
|
206
|
+
bullet = self._format_counter("%d.", t_count+1, listlength)
|
|
207
|
+
elif style == 'letters':
|
|
208
|
+
letter_style = "%C." if (level % 2) else "%c."
|
|
209
|
+
bullet = self._format_counter(letter_style, t_count+1, listlength)
|
|
210
|
+
elif style == 'hanging':
|
|
211
|
+
bullet = element.attrib.get('hangText', '')
|
|
212
|
+
if hangIndent is None:
|
|
213
|
+
hangIndent = 3
|
|
214
|
+
if len(bullet) < hangIndent:
|
|
215
|
+
# Insert whitespace up to hangIndent
|
|
216
|
+
bullet = bullet.ljust(hangIndent)
|
|
217
|
+
else:
|
|
218
|
+
# Insert a single space
|
|
219
|
+
bullet += ' '
|
|
220
|
+
# Add an extra space in front of colon if colonspace enabled
|
|
221
|
+
if bullet.endswith(':') and \
|
|
222
|
+
element.pis['colonspace'] == 'yes':
|
|
223
|
+
bullet+= ' '
|
|
224
|
+
if element.text and len(bullet) > self.width//2:
|
|
225
|
+
# extra check of remaining space if the bullet is
|
|
226
|
+
# very long
|
|
227
|
+
first_word = self.wrapper._split(element.text)[0]
|
|
228
|
+
if len(first_word) > (self.width - len(bullet) - indent):
|
|
229
|
+
self.write_text('', bullet=bullet, indent=indent,
|
|
230
|
+
leading_blankline=leading_blankline)
|
|
231
|
+
self._vspace()
|
|
232
|
+
leading_blankline=False
|
|
233
|
+
indent = hangIndent
|
|
234
|
+
bullet = ''
|
|
235
|
+
elif style.startswith('format'):
|
|
236
|
+
self.list_counters[counter_index] += 1
|
|
237
|
+
count = self.list_counters[counter_index]
|
|
238
|
+
bullet = self._format_counter(format_str, count, listlength)
|
|
239
|
+
if hangIndent:
|
|
240
|
+
sub_indent = hangIndent
|
|
241
|
+
else:
|
|
242
|
+
sub_indent = len(bullet)
|
|
243
|
+
anchor = element.attrib.get('anchor')
|
|
244
|
+
if anchor and self.indexmode:
|
|
245
|
+
self._indexListParagraph(t_count+1, anchor)
|
|
246
|
+
self.write_t_rec(element, bullet=bullet, indent=indent, \
|
|
247
|
+
level=level + 1, \
|
|
248
|
+
sub_indent=sub_indent, leading_blankline=leading_blankline)
|
|
249
|
+
t_count += 1
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def pre_write_toc(self):
|
|
253
|
+
return ['', 'Table of Contents', '']
|
|
254
|
+
|
|
255
|
+
def post_write_toc(self):
|
|
256
|
+
return []
|
|
257
|
+
|
|
258
|
+
def write_toc(self, paging=False):
|
|
259
|
+
""" Write table of contents to a temporary buffer and return """
|
|
260
|
+
if self.toc_marker < 1:
|
|
261
|
+
# Toc is either disabled, or the pointer was messed up
|
|
262
|
+
return ['']
|
|
263
|
+
tmpbuf = []
|
|
264
|
+
tmpbuf.extend(self.pre_write_toc())
|
|
265
|
+
# Retrieve toc from the index
|
|
266
|
+
tocindex = self._getTocIndex()
|
|
267
|
+
tocdepth = self.pis['tocdepth']
|
|
268
|
+
try:
|
|
269
|
+
tocdepth = int(tocdepth)
|
|
270
|
+
except ValueError:
|
|
271
|
+
xml2rfc.log.warn('Invalid tocdepth specified, must be integer:', \
|
|
272
|
+
tocdepth)
|
|
273
|
+
tocdepth = 3
|
|
274
|
+
indent_scale = 2
|
|
275
|
+
if self.pis['tocnarrow'] == 'no':
|
|
276
|
+
indent_scale = 3
|
|
277
|
+
for item in tocindex:
|
|
278
|
+
# Add decoration to counter if it exists, otherwise leave empty
|
|
279
|
+
if item.level <= tocdepth:
|
|
280
|
+
counter = ''
|
|
281
|
+
if item.counter:
|
|
282
|
+
counter = item.counter + '. '
|
|
283
|
+
# Extra space on single digit counters
|
|
284
|
+
if len(item.counter.rsplit('.')[-1]) == 1:
|
|
285
|
+
counter += ' '
|
|
286
|
+
# Get item depth based on its section 'level' attribute
|
|
287
|
+
depth = item.level - 1
|
|
288
|
+
if depth < 0 or self.pis['tocindent'] == 'no':
|
|
289
|
+
depth = 0
|
|
290
|
+
# Prepend appendix at first level
|
|
291
|
+
if item.level == 1 and item.appendix:
|
|
292
|
+
counter = "Appendix " + counter
|
|
293
|
+
bullet = ' ' * (depth * indent_scale) + counter
|
|
294
|
+
indent = 3
|
|
295
|
+
sub_indent = indent + len(bullet)
|
|
296
|
+
pagestr = '%4s' % item.page
|
|
297
|
+
lines = textwrap.wrap(bullet + (item.title.strip() if item.title else ""),
|
|
298
|
+
self.width - len(pagestr),
|
|
299
|
+
initial_indent=' ' * indent,
|
|
300
|
+
subsequent_indent=' ' * sub_indent)
|
|
301
|
+
if paging:
|
|
302
|
+
# Construct dots
|
|
303
|
+
dots = len(lines[-1]) % 2 and ' ' or ''
|
|
304
|
+
dots += ' .' * int((self.width - len(lines[-1]) - len(dots) + 1)//2)
|
|
305
|
+
lines[-1] += dots
|
|
306
|
+
# Insert page
|
|
307
|
+
lines[-1] = lines[-1][:0 - len(pagestr)] + pagestr
|
|
308
|
+
tmpbuf.extend(lines)
|
|
309
|
+
tmpbuf.extend(self.post_write_toc())
|
|
310
|
+
return tmpbuf
|
|
311
|
+
|
|
312
|
+
def pre_write_iref_index(self):
|
|
313
|
+
return ['', 'Index']
|
|
314
|
+
|
|
315
|
+
def write_iref_index(self):
|
|
316
|
+
""" Write iref index to a temporary buffer and return """
|
|
317
|
+
def pagelist(pages):
|
|
318
|
+
pages = list(set(pages))
|
|
319
|
+
pages.sort()
|
|
320
|
+
# find ranges
|
|
321
|
+
items = []
|
|
322
|
+
prev = None
|
|
323
|
+
for page in pages:
|
|
324
|
+
if prev and (page - prev == 1):
|
|
325
|
+
items[-1].append(page)
|
|
326
|
+
else:
|
|
327
|
+
items.append([page])
|
|
328
|
+
prev = page
|
|
329
|
+
pagelist = []
|
|
330
|
+
for item in items:
|
|
331
|
+
if len(item) == 1:
|
|
332
|
+
pagelist.append("%d"%item[0])
|
|
333
|
+
else:
|
|
334
|
+
pagelist.append("%d-%d"%(item[0], item[-1]))
|
|
335
|
+
return ", ".join(pagelist)
|
|
336
|
+
|
|
337
|
+
if self.iref_marker < 1:
|
|
338
|
+
# iref is either disabled, or the pointer was messed up
|
|
339
|
+
return ['']
|
|
340
|
+
tmpbuf = self.pre_write_iref_index()
|
|
341
|
+
# Sort iref items alphabetically, store by first letter
|
|
342
|
+
alpha_bucket = {}
|
|
343
|
+
keys = list(self._iref_index.keys())
|
|
344
|
+
keys.sort(key=str.upper)
|
|
345
|
+
for key in keys:
|
|
346
|
+
letter = key[0].upper()
|
|
347
|
+
if letter in alpha_bucket:
|
|
348
|
+
alpha_bucket[letter].append(key)
|
|
349
|
+
else:
|
|
350
|
+
alpha_bucket[letter] = [key]
|
|
351
|
+
for letter in sorted(alpha_bucket.keys()):
|
|
352
|
+
# Write letter
|
|
353
|
+
self.write_text(letter, indent=3, leading_blankline=True, buf=tmpbuf)
|
|
354
|
+
for item in sorted(alpha_bucket[letter]):
|
|
355
|
+
pages = self._iref_index[item].pages
|
|
356
|
+
# Write item
|
|
357
|
+
self.write_text(item + ' ' + pagelist(pages)
|
|
358
|
+
, indent=6, buf=tmpbuf, fix_doublespace=False)
|
|
359
|
+
subkeys = list(self._iref_index[item].subitems.keys())
|
|
360
|
+
subkeys.sort(key=str.upper)
|
|
361
|
+
for subitem in subkeys:
|
|
362
|
+
pages = set(self._iref_index[item].subitems[subitem].pages)
|
|
363
|
+
# Write subitem
|
|
364
|
+
self.write_text(subitem + ' ' + pagelist(pages)
|
|
365
|
+
, indent=9, buf=tmpbuf, fix_doublespace=False)
|
|
366
|
+
return tmpbuf
|
|
367
|
+
|
|
368
|
+
def _expand_xref(self, xref):
|
|
369
|
+
""" Returns the proper text representation of an xref element """
|
|
370
|
+
target = xref.attrib.get('target', '')
|
|
371
|
+
format = xref.attrib.get('format', self.defaults['xref_format'])
|
|
372
|
+
item = self._getItemByAnchor(target)
|
|
373
|
+
if not self.indexmode:
|
|
374
|
+
if not item:
|
|
375
|
+
xml2rfc.log.warn("Can't resolve xref target %s" % target)
|
|
376
|
+
else:
|
|
377
|
+
item.used = True
|
|
378
|
+
if not item:
|
|
379
|
+
target_text = '[' + target + ']'
|
|
380
|
+
elif format == 'none':
|
|
381
|
+
if xref.text:
|
|
382
|
+
return xref.text.rstrip()
|
|
383
|
+
return ''
|
|
384
|
+
elif format == 'counter':
|
|
385
|
+
target_text = item.counter
|
|
386
|
+
elif format == 'title':
|
|
387
|
+
target_text = item.title.strip()
|
|
388
|
+
else: #Default
|
|
389
|
+
target_text = item.autoName
|
|
390
|
+
target_text = re.sub("-", u"\u2011", target_text) # switch to non-breaking hyphens
|
|
391
|
+
target_text = re.sub(" ", u"\u00A0", target_text) # switch to non-breaking space
|
|
392
|
+
if xref.text:
|
|
393
|
+
if not target_text.startswith('['):
|
|
394
|
+
target_text = '(' + target_text + ')'
|
|
395
|
+
return xref.text.rstrip() + ' ' + target_text
|
|
396
|
+
else:
|
|
397
|
+
return target_text
|
|
398
|
+
|
|
399
|
+
def _expand_u(self, u):
|
|
400
|
+
try:
|
|
401
|
+
text = expand_unicode_element(u)
|
|
402
|
+
except (RuntimeError, ValueError) as exc:
|
|
403
|
+
text = ''
|
|
404
|
+
xml2rfc.log.error('%s'%exc)
|
|
405
|
+
return text
|
|
406
|
+
|
|
407
|
+
def write_ref_element(self, key, text, sub_indent, source_line=None):
|
|
408
|
+
""" Render a single reference element """
|
|
409
|
+
# Use an empty first line if key is too long
|
|
410
|
+
min_spacing = 2
|
|
411
|
+
if len(key) + min_spacing > sub_indent:
|
|
412
|
+
self.write_text(key, indent=3, leading_blankline=True, wrap_urls=False, fix_sentence_endings=False, source_line=source_line)
|
|
413
|
+
self.write_text(text, indent=3 + sub_indent, wrap_urls=False, fix_sentence_endings=False, source_line=source_line)
|
|
414
|
+
else:
|
|
415
|
+
# Fill space to sub_indent in the bullet
|
|
416
|
+
self.write_text(text, indent=3, bullet=key.ljust(sub_indent), \
|
|
417
|
+
sub_indent=sub_indent, leading_blankline=True, wrap_urls=False, fix_sentence_endings=False, source_line=source_line)
|
|
418
|
+
|
|
419
|
+
def _combine_inline_elements(self, elements):
|
|
420
|
+
""" Shared function for <t> and <c> elements
|
|
421
|
+
|
|
422
|
+
Aggregates all the rendered text of the following elements:
|
|
423
|
+
- xref
|
|
424
|
+
- eref
|
|
425
|
+
- iref
|
|
426
|
+
- cref
|
|
427
|
+
- spanx
|
|
428
|
+
|
|
429
|
+
Plus their tails. If an element is encountered that isn't one
|
|
430
|
+
of these (such as a list, figure, etc) then the function
|
|
431
|
+
returns.
|
|
432
|
+
|
|
433
|
+
This function takes a list of elements as its argument.
|
|
434
|
+
|
|
435
|
+
This function returns TWO arguments, the aggregated text, and
|
|
436
|
+
a list containing the rest of the elements that were not processed,
|
|
437
|
+
so that the calling function can deal with them.
|
|
438
|
+
"""
|
|
439
|
+
line = ['']
|
|
440
|
+
for i, element in enumerate(elements):
|
|
441
|
+
if element.tag not in self.inline_tags:
|
|
442
|
+
# Not an inline element, exit
|
|
443
|
+
return ''.join(line), elements[i:]
|
|
444
|
+
|
|
445
|
+
if element.tag == 'xref':
|
|
446
|
+
line.append(self._expand_xref(element))
|
|
447
|
+
elif element.tag == 'eref':
|
|
448
|
+
if element.text and len(element.text)>0:
|
|
449
|
+
line.append(element.text.strip())
|
|
450
|
+
self.eref_count += 1
|
|
451
|
+
line.append(' [' + str(self.eref_count) + ']')
|
|
452
|
+
if self.indexmode:
|
|
453
|
+
self.eref_list.append([self.eref_count, element])
|
|
454
|
+
else:
|
|
455
|
+
line.append('<' + element.attrib['target'].strip() + '>')
|
|
456
|
+
elif element.tag == 'iref':
|
|
457
|
+
self._add_iref_to_index(element)
|
|
458
|
+
elif element.tag == 'cref':
|
|
459
|
+
if element.pis['comments'] == 'yes':
|
|
460
|
+
# Render if processing instruction is enabled
|
|
461
|
+
self.cref_counter += 1
|
|
462
|
+
anchor = element.attrib.get('anchor', None)
|
|
463
|
+
if anchor is None:
|
|
464
|
+
anchor = 'CREF' + str(self.cref_counter)
|
|
465
|
+
element.attrib['anchor'] = anchor
|
|
466
|
+
self._indexCref(self.cref_counter, anchor)
|
|
467
|
+
if element.pis['inline'] == 'yes':
|
|
468
|
+
if anchor:
|
|
469
|
+
anchor = anchor + ': '
|
|
470
|
+
source = element.attrib.get('source', "")
|
|
471
|
+
if source:
|
|
472
|
+
source = " --" + source
|
|
473
|
+
if element.text:
|
|
474
|
+
line.append('[[' + anchor + element.text + source + ']]')
|
|
475
|
+
else:
|
|
476
|
+
line.append('[' + anchor + ']')
|
|
477
|
+
self.cref_list.append(element)
|
|
478
|
+
elif element.tag == 'spanx':
|
|
479
|
+
style = element.attrib.get('style', 'emph')
|
|
480
|
+
edgechar = '_' # default to emph because the spanx element exists
|
|
481
|
+
if style == 'emph':
|
|
482
|
+
edgechar = '_'
|
|
483
|
+
elif style == 'strong':
|
|
484
|
+
edgechar = '*'
|
|
485
|
+
elif style == 'verb':
|
|
486
|
+
edgechar = '"'
|
|
487
|
+
text = ''
|
|
488
|
+
if element.text:
|
|
489
|
+
text = element.text
|
|
490
|
+
line.append(edgechar + text + edgechar)
|
|
491
|
+
elif element.tag == 'u':
|
|
492
|
+
line.append(self._expand_u(element))
|
|
493
|
+
else:
|
|
494
|
+
xml2rfc.log.error("Found unexpected inline element: <%s>" % element.tag)
|
|
495
|
+
|
|
496
|
+
# Add tail text before next element
|
|
497
|
+
if element.tail:
|
|
498
|
+
line.append(element.tail)
|
|
499
|
+
|
|
500
|
+
# Go to next sibling
|
|
501
|
+
element = element.getnext()
|
|
502
|
+
|
|
503
|
+
# Went through all elements, return text with an empty list
|
|
504
|
+
return ''.join(line), []
|
|
505
|
+
|
|
506
|
+
def _check_long_lines(self, buf_line, source_line):
|
|
507
|
+
long_lines = [ (num, line) for num, line in enumerate(self.buf[buf_line:]) if (len(line) > self.width) ]
|
|
508
|
+
for num, line in long_lines:
|
|
509
|
+
if source_line:
|
|
510
|
+
xml2rfc.log.warn("Output line (from source around line %s) is %s characters; longer than %s. Excess characters: '%s':\n '%s'\n"
|
|
511
|
+
% (source_line+num, len(line), self.width, line[self.width:], line))
|
|
512
|
+
else:
|
|
513
|
+
xml2rfc.log.warn("Output line (from source around line %s) is %s characters; longer than %s. Excess characters: '%s':\n '%s'\n"
|
|
514
|
+
% (buf_line+num, len(line), self.width, line[self.width:], line))
|
|
515
|
+
|
|
516
|
+
# ---------------------------------------------------------
|
|
517
|
+
# Base writer overrides
|
|
518
|
+
# ---------------------------------------------------------
|
|
519
|
+
|
|
520
|
+
def insert_toc(self):
|
|
521
|
+
""" Marks buffer position for post-writing table of contents """
|
|
522
|
+
self.toc_marker = len(self.buf)
|
|
523
|
+
|
|
524
|
+
def insert_iref_index(self):
|
|
525
|
+
""" Marks buffer position for post-writing index """
|
|
526
|
+
self.iref_marker = len(self.buf)
|
|
527
|
+
|
|
528
|
+
def write_raw(self, text, indent=3, align='left', blanklines=0, \
|
|
529
|
+
delimiter=None, leading_blankline=True, source_line=None):
|
|
530
|
+
""" Writes a raw stream of characters, preserving space and breaks """
|
|
531
|
+
|
|
532
|
+
if text:
|
|
533
|
+
if leading_blankline:
|
|
534
|
+
# Start with a newline
|
|
535
|
+
self._lb()
|
|
536
|
+
# Delimiter?
|
|
537
|
+
if delimiter:
|
|
538
|
+
self.buf.append(delimiter)
|
|
539
|
+
# Additional blank lines?
|
|
540
|
+
self.buf.extend([''] * blanklines)
|
|
541
|
+
start_line = len(self.buf)
|
|
542
|
+
# Format the input
|
|
543
|
+
if "\t" in text:
|
|
544
|
+
xml2rfc.log.warn("Text %scontains tab characters. These will be expanded, assuming a tab-size of 8." %
|
|
545
|
+
(("around line %s "%source_line) if source_line else ""))
|
|
546
|
+
lines = [line.rstrip() for line in text.expandtabs().split('\n')]
|
|
547
|
+
# Outdent if it helps anything
|
|
548
|
+
longest_line = max(len(line.rstrip()) for line in lines)
|
|
549
|
+
if (longest_line > self.width-indent):
|
|
550
|
+
new_indent = max(self.width - longest_line, 0)
|
|
551
|
+
if not self.indexmode:
|
|
552
|
+
xml2rfc.log.warn('artwork outdented %s characters to avoid overrunning right margin around input line %s)' % (indent - new_indent, source_line if source_line else 0))
|
|
553
|
+
indent = new_indent
|
|
554
|
+
|
|
555
|
+
# Trim first and last lines if they are blank, whitespace is handled
|
|
556
|
+
# by the `blanklines` and `delimiter` arguments
|
|
557
|
+
if len(lines) > 1:
|
|
558
|
+
if lines[0] == '':
|
|
559
|
+
lines.pop(0)
|
|
560
|
+
if lines[-1] == '':
|
|
561
|
+
lines.pop(-1)
|
|
562
|
+
if align == 'center':
|
|
563
|
+
# Find the longest line, and use that as a fixed center.
|
|
564
|
+
center_indent = indent + ((self.width - indent - longest_line) // 2)
|
|
565
|
+
indent_str = center_indent > indent and ' ' * center_indent or \
|
|
566
|
+
' ' * indent
|
|
567
|
+
for line in lines:
|
|
568
|
+
self.buf.append(indent_str + line.rstrip())
|
|
569
|
+
elif align == 'right':
|
|
570
|
+
indent_str = ' ' * (self.width - longest_line)
|
|
571
|
+
for line in lines:
|
|
572
|
+
self.buf.append(indent_str + line.rstrip())
|
|
573
|
+
else: # align == left
|
|
574
|
+
indent_str = ' ' * indent
|
|
575
|
+
for line in lines:
|
|
576
|
+
self.buf.append(indent_str + line.rstrip())
|
|
577
|
+
# Additional blank lines?
|
|
578
|
+
self.buf.extend([''] * blanklines)
|
|
579
|
+
# Delimiter?
|
|
580
|
+
if delimiter:
|
|
581
|
+
self.buf.append(delimiter)
|
|
582
|
+
if not self.indexmode:
|
|
583
|
+
self._check_long_lines(start_line, source_line)
|
|
584
|
+
|
|
585
|
+
def write_label(self, text, type='figure', source_line=None):
|
|
586
|
+
""" Writes a centered label """
|
|
587
|
+
self.write_text(text, indent=3, align='center', leading_blankline=True, source_line=source_line)
|
|
588
|
+
|
|
589
|
+
def write_title(self, title, docName=None, source_line=None):
|
|
590
|
+
""" Write the document title and (optional) name """
|
|
591
|
+
self.write_text(title, leading_blankline=True, align='center', source_line=source_line)
|
|
592
|
+
if docName and not self.rfcnumber:
|
|
593
|
+
self.write_text(docName, align='center')
|
|
594
|
+
|
|
595
|
+
def write_heading(self, text, bullet='', autoAnchor=None, anchor=None, \
|
|
596
|
+
level=1):
|
|
597
|
+
""" Write a generic header """
|
|
598
|
+
if bullet:
|
|
599
|
+
bullet += ' '
|
|
600
|
+
self.write_text(text, bullet=bullet, indent=0, leading_blankline=True)
|
|
601
|
+
|
|
602
|
+
def write_paragraph(self, text, align='left', autoAnchor=None):
|
|
603
|
+
""" Write a generic paragraph of text. Used for boilerplate. """
|
|
604
|
+
text = xml2rfc.utils.urlkeep(text)
|
|
605
|
+
self.write_text(text, indent=3, align=align, leading_blankline=True)
|
|
606
|
+
|
|
607
|
+
def write_t_rec(self, t, indent=3, sub_indent=0, bullet='',
|
|
608
|
+
autoAnchor=None, align='left', level=0, leading_blankline=True):
|
|
609
|
+
""" Recursively writes a <t> element """
|
|
610
|
+
# Grab any initial text in <t>
|
|
611
|
+
current_text = t.text or ''
|
|
612
|
+
if bullet and current_text == '':
|
|
613
|
+
current_text = ' '
|
|
614
|
+
|
|
615
|
+
# Render child elements
|
|
616
|
+
remainder = t.getchildren()
|
|
617
|
+
while len(remainder) > 0 or current_text:
|
|
618
|
+
# Process any inline elements
|
|
619
|
+
inline_text, remainder = self._combine_inline_elements(remainder)
|
|
620
|
+
current_text += inline_text
|
|
621
|
+
if (current_text and not current_text.isspace()) or bullet:
|
|
622
|
+
# Attempt to write a paragraph of inline text
|
|
623
|
+
self.write_text(current_text, indent=indent,
|
|
624
|
+
leading_blankline=leading_blankline,
|
|
625
|
+
sub_indent=sub_indent, bullet=bullet,
|
|
626
|
+
edit=True, align=align, source_line=t.sourceline)
|
|
627
|
+
# Clear text
|
|
628
|
+
current_text = ''
|
|
629
|
+
|
|
630
|
+
# Handle paragraph-based elements (list, figure, vspace)
|
|
631
|
+
if len(remainder) > 0:
|
|
632
|
+
# Get front element
|
|
633
|
+
element = remainder.pop(0)
|
|
634
|
+
|
|
635
|
+
if element.tag == 'list':
|
|
636
|
+
if sub_indent > 0:
|
|
637
|
+
new_indent = sub_indent + indent
|
|
638
|
+
else:
|
|
639
|
+
new_indent = len(bullet) + indent
|
|
640
|
+
# Call sibling function to construct list
|
|
641
|
+
self.write_list(element, indent=new_indent, level=level)
|
|
642
|
+
# Auto-break for tail paragraph
|
|
643
|
+
leading_blankline = True
|
|
644
|
+
bullet = ''
|
|
645
|
+
|
|
646
|
+
elif element.tag == 'figure':
|
|
647
|
+
self.write_figure(element)
|
|
648
|
+
# Auto-break for tail paragraph
|
|
649
|
+
leading_blankline = True
|
|
650
|
+
bullet = ''
|
|
651
|
+
|
|
652
|
+
elif element.tag == 'vspace':
|
|
653
|
+
# Insert `blankLines` blank lines into document
|
|
654
|
+
self._vspace(num=int(element.attrib.get('blankLines',
|
|
655
|
+
self.defaults['vspace_blanklines'])))
|
|
656
|
+
# Don't auto-break for tail paragraph
|
|
657
|
+
leading_blankline = False
|
|
658
|
+
# Keep indentation
|
|
659
|
+
bullet = ' ' * sub_indent
|
|
660
|
+
|
|
661
|
+
# Set tail of element as input text of next paragraph
|
|
662
|
+
if element.tail:
|
|
663
|
+
current_text = element.tail
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def write_top(self, left_header, right_header):
|
|
667
|
+
""" Combines left and right lists to write a document heading """
|
|
668
|
+
# Begin with a blank line on the first page. We'll add additional
|
|
669
|
+
# blank lines at the top later, but those won't be counted as part
|
|
670
|
+
# of the page linecount.
|
|
671
|
+
#self._lb(num=3)
|
|
672
|
+
self._lb()
|
|
673
|
+
heading = []
|
|
674
|
+
for i in range(max(len(left_header), len(right_header))):
|
|
675
|
+
if i < len(left_header):
|
|
676
|
+
left = left_header[i]
|
|
677
|
+
else:
|
|
678
|
+
left = ''
|
|
679
|
+
if i < len(right_header):
|
|
680
|
+
right = right_header[i]
|
|
681
|
+
else:
|
|
682
|
+
right = ''
|
|
683
|
+
heading.append(xml2rfc.utils.justify_inline(left, '', right, \
|
|
684
|
+
self.width))
|
|
685
|
+
self.write_raw('\n'.join(heading), align='left', indent=0,
|
|
686
|
+
leading_blankline=False, source_line=None)
|
|
687
|
+
# Extra blank line underneath top block
|
|
688
|
+
self._lb()
|
|
689
|
+
|
|
690
|
+
def write_address_card(self, author):
|
|
691
|
+
""" Writes a simple address card with no line breaks """
|
|
692
|
+
lines = []
|
|
693
|
+
if 'role' in author.attrib:
|
|
694
|
+
lines.append("%s (%s)" % (author.attrib.get('fullname', ''),
|
|
695
|
+
author.attrib.get('role', '')))
|
|
696
|
+
else:
|
|
697
|
+
lines.append(author.attrib.get('fullname', ''))
|
|
698
|
+
organization = author.find('organization')
|
|
699
|
+
if organization is not None and organization.text:
|
|
700
|
+
lines.append(organization.text.strip())
|
|
701
|
+
address = author.find('address')
|
|
702
|
+
if address is not None:
|
|
703
|
+
postal = address.find('postal')
|
|
704
|
+
if postal is not None:
|
|
705
|
+
for street in postal.findall('street'):
|
|
706
|
+
if street.text:
|
|
707
|
+
lines.append(street.text)
|
|
708
|
+
cityline = []
|
|
709
|
+
city = postal.find('city')
|
|
710
|
+
if city is not None and city.text:
|
|
711
|
+
cityline.append(city.text)
|
|
712
|
+
region = postal.find('region')
|
|
713
|
+
if region is not None and region.text:
|
|
714
|
+
if len(cityline) > 0: cityline.append(', ');
|
|
715
|
+
cityline.append(region.text)
|
|
716
|
+
code = postal.find('code')
|
|
717
|
+
if code is not None and code.text:
|
|
718
|
+
if len(cityline) > 0: cityline.append(' ');
|
|
719
|
+
cityline.append(code.text)
|
|
720
|
+
if len(cityline) > 0:
|
|
721
|
+
lines.append(''.join(cityline))
|
|
722
|
+
country = postal.find('country')
|
|
723
|
+
if country is not None and country.text:
|
|
724
|
+
lines.append(country.text)
|
|
725
|
+
lines.append('')
|
|
726
|
+
phone = address.find('phone')
|
|
727
|
+
if phone is not None and phone.text:
|
|
728
|
+
lines.append('Phone: ' + phone.text)
|
|
729
|
+
facsimile = address.find('facsimile')
|
|
730
|
+
if facsimile is not None and facsimile.text:
|
|
731
|
+
lines.append('Fax: ' + facsimile.text)
|
|
732
|
+
email = address.find('email')
|
|
733
|
+
if email is not None and email.text:
|
|
734
|
+
label = self.pis['rfcedstyle'] == 'yes' and 'EMail' or 'Email'
|
|
735
|
+
lines.append('%s: %s' % (label, email.text))
|
|
736
|
+
uri = address.find('uri')
|
|
737
|
+
if uri is not None and uri.text:
|
|
738
|
+
lines.append('URI: ' + uri.text)
|
|
739
|
+
self.write_raw('\n'.join(lines), indent=self.margin)
|
|
740
|
+
self._lb()
|
|
741
|
+
|
|
742
|
+
def write_reference_list(self, list):
|
|
743
|
+
""" Writes a formatted list of <reference> elements """
|
|
744
|
+
refdict = {}
|
|
745
|
+
annotationdict = {}
|
|
746
|
+
refkeys = []
|
|
747
|
+
refsource = {}
|
|
748
|
+
# [surname, initial.,] "title", (STD), (BCP), (RFC), (Month) Year.
|
|
749
|
+
i = 0
|
|
750
|
+
for i, ref in enumerate(list.findall('reference')):
|
|
751
|
+
refstring = []
|
|
752
|
+
authors = ref.findall('front/author')
|
|
753
|
+
author_string = self._format_author_string(authors)
|
|
754
|
+
refstring.append(author_string)
|
|
755
|
+
if len(author_string):
|
|
756
|
+
refstring.append(', ')
|
|
757
|
+
title = ref.find('front/title')
|
|
758
|
+
if title is not None and title.text:
|
|
759
|
+
if ref.attrib.get("quote-title", "true") == "true": # attribute default value: yes
|
|
760
|
+
refstring.append('"' + title.text.strip() + '"')
|
|
761
|
+
else:
|
|
762
|
+
refstring.append(title.text.strip())
|
|
763
|
+
else:
|
|
764
|
+
xml2rfc.log.warn('No title specified in reference',
|
|
765
|
+
ref.attrib.get('anchor', ''))
|
|
766
|
+
for seriesInfo in ref.findall('seriesInfo'):
|
|
767
|
+
if seriesInfo.attrib['name'] == "Internet-Draft":
|
|
768
|
+
refstring.append(', '+seriesInfo.attrib['value'] + ' (work in progress)')
|
|
769
|
+
else:
|
|
770
|
+
refstring.append(', '+seriesInfo.attrib['name'] + u'\u00A0' +
|
|
771
|
+
seriesInfo.attrib['value'].replace('/', '/' + u'\uE060'))
|
|
772
|
+
date = ref.find('front/date')
|
|
773
|
+
if date is not None:
|
|
774
|
+
month = date.attrib.get('month', '')
|
|
775
|
+
year = date.attrib.get('year', '')
|
|
776
|
+
if month or year:
|
|
777
|
+
if month:
|
|
778
|
+
month += ' '
|
|
779
|
+
refstring.append(', '+month + year)
|
|
780
|
+
# Target?
|
|
781
|
+
target = ref.attrib.get('target')
|
|
782
|
+
if target:
|
|
783
|
+
refstring.append(', <' + target + '>')
|
|
784
|
+
refstring.append('.')
|
|
785
|
+
annotation = ref.find('annotation')
|
|
786
|
+
# Use anchor or num depending on PI
|
|
787
|
+
if self.pis['symrefs'] == 'yes':
|
|
788
|
+
key = ref.attrib.get('anchor', str(i + self.ref_start))
|
|
789
|
+
else:
|
|
790
|
+
key = str(i + self.ref_start)
|
|
791
|
+
refdict[key] = ''.join(refstring)
|
|
792
|
+
refsource[key] = ref.sourceline
|
|
793
|
+
refkeys.append(key)
|
|
794
|
+
# Add annotation if it exists to a separate dict
|
|
795
|
+
if annotation is not None and annotation.text:
|
|
796
|
+
# Render annotation as a separate paragraph
|
|
797
|
+
remainder = annotation.getchildren()
|
|
798
|
+
annotationdict[key] = annotation.text
|
|
799
|
+
if len(remainder) > 0:
|
|
800
|
+
inline_txt, remainder = self._combine_inline_elements(remainder)
|
|
801
|
+
annotationdict[key] += inline_txt
|
|
802
|
+
self.ref_start += i + 1
|
|
803
|
+
# Don't sort if we're doing numbered refs; they are already in
|
|
804
|
+
# numeric order, and if we sort, they will be sorted alphabetically,
|
|
805
|
+
# rather than numerically ... ( i.e., [10], [11], ... [19], [1], ... )
|
|
806
|
+
if self.pis['sortrefs'] == 'yes' and self.pis['symrefs'] == 'yes' :
|
|
807
|
+
refkeys.sort(key=str.lower)
|
|
808
|
+
# Hard coded indentation amount
|
|
809
|
+
refindent = 11
|
|
810
|
+
for key in refkeys:
|
|
811
|
+
reflabel = '['+key+']'
|
|
812
|
+
self.write_ref_element(reflabel, refdict[key], refindent, source_line=refsource[key])
|
|
813
|
+
# Render annotation as a separate paragraph
|
|
814
|
+
if key in annotationdict:
|
|
815
|
+
self.write_text(annotationdict[key], indent=refindent + 3,
|
|
816
|
+
leading_blankline=True, source_line=refsource[key])
|
|
817
|
+
|
|
818
|
+
def write_erefs(self, refs_counter, refs_subsection):
|
|
819
|
+
bullet=str(refs_counter) + '.' + str(refs_subsection)
|
|
820
|
+
self.write_heading("URIs", bullet=bullet +'.',
|
|
821
|
+
level = 2, autoAnchor='rfc.references.' + str(refs_subsection))
|
|
822
|
+
for i, element in self.eref_list:
|
|
823
|
+
self.write_text(element.attrib['target'], bullet='['+str(i)+'] ',
|
|
824
|
+
leading_blankline=True, indent=3)
|
|
825
|
+
|
|
826
|
+
def write_crefs(self):
|
|
827
|
+
""" If we have any of these because the are not inline, then
|
|
828
|
+
write them out here
|
|
829
|
+
"""
|
|
830
|
+
if self.cref_list:
|
|
831
|
+
self.write_heading('Editorial Comments')
|
|
832
|
+
for cref in self.cref_list:
|
|
833
|
+
body = cref.text
|
|
834
|
+
source = cref.attrib.get('source', None)
|
|
835
|
+
if source:
|
|
836
|
+
body = source + ': ' + body
|
|
837
|
+
self.write_text(body, bullet='['+cref.attrib['anchor']+'] ',
|
|
838
|
+
leading_blankline=True)
|
|
839
|
+
|
|
840
|
+
def draw_table(self, table, table_num=None):
|
|
841
|
+
# First construct a 2d matrix from the table
|
|
842
|
+
matrix = []
|
|
843
|
+
matrix.append([])
|
|
844
|
+
row = 0
|
|
845
|
+
column_aligns = []
|
|
846
|
+
ttcol_width_attrs = []
|
|
847
|
+
style = table.attrib.get('style', self.defaults['table_style'])
|
|
848
|
+
|
|
849
|
+
# Extract information from the ttcol elements about each column
|
|
850
|
+
for ttcol in table.findall('ttcol'):
|
|
851
|
+
column_aligns.append(ttcol.attrib.get('align', self.defaults['ttcol_align']))
|
|
852
|
+
ttcol_width_attrs.append(ttcol.attrib.get('width', ''))
|
|
853
|
+
|
|
854
|
+
text = ttcol.text or ''
|
|
855
|
+
if len(ttcol) > 0:
|
|
856
|
+
# <c> has children, render their text and add to line
|
|
857
|
+
inline_text, null = \
|
|
858
|
+
self._combine_inline_elements(ttcol.getchildren())
|
|
859
|
+
text += inline_text
|
|
860
|
+
matrix[row].append(text)
|
|
861
|
+
|
|
862
|
+
num_columns = len(matrix[0])
|
|
863
|
+
|
|
864
|
+
# Get the text for each cell into the matrix
|
|
865
|
+
# Insert appropriate blank lines for filler at this time.
|
|
866
|
+
for i, cell in enumerate(table.findall('c')):
|
|
867
|
+
if i % num_columns == 0:
|
|
868
|
+
# Insert blank row if PI 'compact' is 'no'
|
|
869
|
+
if table.pis["compact"] == "no":
|
|
870
|
+
if (style == 'none' and row == 0) or (style in ['headers', 'full'] and row != 0):
|
|
871
|
+
row += 1
|
|
872
|
+
matrix.append(['']*num_columns)
|
|
873
|
+
pass
|
|
874
|
+
row += 1
|
|
875
|
+
matrix.append([])
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
text = cell.text or ''
|
|
879
|
+
if len(cell) > 0:
|
|
880
|
+
# <c> has children, render their text and add to line
|
|
881
|
+
inline_text, null = \
|
|
882
|
+
self._combine_inline_elements(cell.getchildren())
|
|
883
|
+
text += inline_text
|
|
884
|
+
matrix[row].append(text)
|
|
885
|
+
|
|
886
|
+
# Get table style and determine maximum width of table
|
|
887
|
+
if style in ['none', 'headers']:
|
|
888
|
+
table_max_chars = self.width - 3 - num_columns + 1
|
|
889
|
+
else:
|
|
890
|
+
table_max_chars = self.width - 3 - 3 * num_columns - 1 # indent+border
|
|
891
|
+
|
|
892
|
+
if table_max_chars < 0:
|
|
893
|
+
xml2rfc.log.error("too many <ttcol>s to even build <texttable> frame")
|
|
894
|
+
|
|
895
|
+
# Find the longest line and longest word in each column
|
|
896
|
+
longest_lines = [0] * num_columns
|
|
897
|
+
longest_words = [0] * num_columns
|
|
898
|
+
for col in range(num_columns):
|
|
899
|
+
for row in matrix:
|
|
900
|
+
if col < len(row) and len(row[col]) > 0: # Column exists
|
|
901
|
+
# Longest line
|
|
902
|
+
if self._length(row[col]) > longest_lines[col]:
|
|
903
|
+
longest_lines[col] = self._length(row[col])
|
|
904
|
+
# Longest word
|
|
905
|
+
if row[col].strip():
|
|
906
|
+
word = max(xml2rfc.utils.ascii_split(row[col]), key=self._length)
|
|
907
|
+
if self._length(word) > longest_words[col]:
|
|
908
|
+
longest_words[col] = self._length(word)
|
|
909
|
+
|
|
910
|
+
min_width = sum(longest_words)
|
|
911
|
+
|
|
912
|
+
# Assume one character per non-empty column - are there too many columns?
|
|
913
|
+
inf = []
|
|
914
|
+
for col in longest_words:
|
|
915
|
+
col = (col != 0)
|
|
916
|
+
inf.append(col)
|
|
917
|
+
if table_max_chars < sum(inf):
|
|
918
|
+
xml2rfc.log.error("too many non-empty columns in <texttable> to fit in page width")
|
|
919
|
+
|
|
920
|
+
# Translate width specs given available space
|
|
921
|
+
cols = []
|
|
922
|
+
rel_total = 0
|
|
923
|
+
ttcol_width_unspec = []
|
|
924
|
+
|
|
925
|
+
for col in ttcol_width_attrs:
|
|
926
|
+
# what to do with the unspecified ones is touchy.
|
|
927
|
+
u = 0
|
|
928
|
+
if not col:
|
|
929
|
+
x = longest_words[len(cols)]
|
|
930
|
+
u = 1
|
|
931
|
+
elif col == '*':
|
|
932
|
+
x = -1
|
|
933
|
+
rel_total += x
|
|
934
|
+
else:
|
|
935
|
+
m = re.match("^0*([0-9]+)(|em|[%*])$", col)
|
|
936
|
+
if not m:
|
|
937
|
+
xml2rfc.log.warn("Width attribute not in correct format: %s" & col)
|
|
938
|
+
x = longest_words[len(cols)]
|
|
939
|
+
u = 1
|
|
940
|
+
if m.group(2) == 'em':
|
|
941
|
+
x = int(m.group(1));
|
|
942
|
+
elif m.group(2) == '%':
|
|
943
|
+
x = (int(m.group(1)) * table_max_chars + 99)//100
|
|
944
|
+
elif m.group(2) == '*':
|
|
945
|
+
x = -int(m.group(1))
|
|
946
|
+
rel_total += x
|
|
947
|
+
|
|
948
|
+
cols.append(x)
|
|
949
|
+
ttcol_width_unspec.append(u)
|
|
950
|
+
|
|
951
|
+
ttcol_width_attrs = cols
|
|
952
|
+
|
|
953
|
+
# Compute final cell widths ( and their totals )
|
|
954
|
+
|
|
955
|
+
width = table_max_chars
|
|
956
|
+
if table_max_chars == sum(longest_words):
|
|
957
|
+
# Use the minimum widths if that is all we have
|
|
958
|
+
ttcol_widths = longest_words
|
|
959
|
+
elif table_max_chars == sum(inf):
|
|
960
|
+
# ??????????????
|
|
961
|
+
ttcol_widths = inf
|
|
962
|
+
elif table_max_chars < sum(longest_words):
|
|
963
|
+
if not self.indexmode:
|
|
964
|
+
xml2rfc.log.warn("so many <ttcol>s in <texttable> that some words need to be split near line %s" % table.sourceline)
|
|
965
|
+
|
|
966
|
+
excess = sum(longest_words) - table_max_chars
|
|
967
|
+
|
|
968
|
+
# First, try the mins that are greater than what they requested.
|
|
969
|
+
|
|
970
|
+
rel_total = -rel_total
|
|
971
|
+
tol = []
|
|
972
|
+
fnum = 1000 if (rel_total > 0) else 0
|
|
973
|
+
fden = 1
|
|
974
|
+
for s, n in zip(ttcol_width_attrs, longest_words):
|
|
975
|
+
t = 0
|
|
976
|
+
if s >= 0:
|
|
977
|
+
s += (s == 0)
|
|
978
|
+
y = n - s
|
|
979
|
+
if y > 0:
|
|
980
|
+
t = y
|
|
981
|
+
elif fnum > 0:
|
|
982
|
+
# Prepare in case we need to try the relative widths.
|
|
983
|
+
n -= 1
|
|
984
|
+
s = -s
|
|
985
|
+
# We keep the smallest acceptable factor to all as a mixed number.
|
|
986
|
+
if n <= 0:
|
|
987
|
+
fnum = 0
|
|
988
|
+
elif n * fden < s * fnum:
|
|
989
|
+
fnum = n
|
|
990
|
+
fden = s
|
|
991
|
+
|
|
992
|
+
tol.append(t)
|
|
993
|
+
|
|
994
|
+
excess, ttcol_widths = self.shave_cols_excess(longest_words, excess, tol, sum(tol))
|
|
995
|
+
|
|
996
|
+
# Second, try the unspecified widths.
|
|
997
|
+
if excess > 0:
|
|
998
|
+
tol = []
|
|
999
|
+
for w,u in zip(ttcol_widths, ttcol_width_unspec):
|
|
1000
|
+
t = (w-1) if (u and w > 0) else 0
|
|
1001
|
+
tol.append(t)
|
|
1002
|
+
|
|
1003
|
+
excess, ttcol_widths = self.shave_cols_excess(ttcol_widths, excess, tol, sum(tol))
|
|
1004
|
+
|
|
1005
|
+
# Third, try the relative widths (to shrink, not expand).
|
|
1006
|
+
if excess > 0 and fnum > 0:
|
|
1007
|
+
if excess * fden < rel_total * fnum:
|
|
1008
|
+
fnum = excess
|
|
1009
|
+
fden = rel_total
|
|
1010
|
+
|
|
1011
|
+
tol = []
|
|
1012
|
+
for s in ttcol_width_attrs:
|
|
1013
|
+
t = ((fnum * -(s)) // fden) if (s < 0) else 0
|
|
1014
|
+
tol.append(t)
|
|
1015
|
+
|
|
1016
|
+
excess, ttcol_widths = self.shave_cols_excess(ttcol_widths, excess, tol, sum(tol))
|
|
1017
|
+
|
|
1018
|
+
# Finally, try anything that's left, leaving as little as one column.
|
|
1019
|
+
# Start with largest.
|
|
1020
|
+
|
|
1021
|
+
p = 1
|
|
1022
|
+
for w in ttcol_widths:
|
|
1023
|
+
if w > p:
|
|
1024
|
+
p = w
|
|
1025
|
+
|
|
1026
|
+
while excess > 0:
|
|
1027
|
+
if p == 1:
|
|
1028
|
+
xml2rfc.log.error("bug in reducing <texttable> column widths")
|
|
1029
|
+
|
|
1030
|
+
# Find second largest.
|
|
1031
|
+
q = 1
|
|
1032
|
+
for w in ttcol_widths:
|
|
1033
|
+
if w > q and w < p:
|
|
1034
|
+
q = w
|
|
1035
|
+
|
|
1036
|
+
tol = []
|
|
1037
|
+
for w in ttcol_widths:
|
|
1038
|
+
t = (w - q) if (w > q) else 0
|
|
1039
|
+
tol.append(t)
|
|
1040
|
+
|
|
1041
|
+
excess, ttcol_widths = self.shave_cols_excess(ttcol_widths, excess, tol, sum(tol))
|
|
1042
|
+
|
|
1043
|
+
p = q
|
|
1044
|
+
|
|
1045
|
+
else:
|
|
1046
|
+
# There is room beyond the minimum space
|
|
1047
|
+
# First, try and give everyone what they want or need
|
|
1048
|
+
# longest_words + ttcol_width_attrs + longest_lines(all)
|
|
1049
|
+
ttcol_widths = []
|
|
1050
|
+
cols_width = 0
|
|
1051
|
+
|
|
1052
|
+
# longest_words + ttcol_width_attrs
|
|
1053
|
+
mnsa = []
|
|
1054
|
+
|
|
1055
|
+
# longest_words + ttcol_width_attrs + longest_lines(rel)
|
|
1056
|
+
mnsb = []
|
|
1057
|
+
|
|
1058
|
+
# longest_words + ttcol_width_attrs + longest_lines(rel + ttcol_width_unspec)
|
|
1059
|
+
mnsc = []
|
|
1060
|
+
|
|
1061
|
+
for m, n, s, u in zip(longest_lines, longest_words, ttcol_width_attrs, ttcol_width_unspec):
|
|
1062
|
+
t = n
|
|
1063
|
+
if (t < s):
|
|
1064
|
+
t = s
|
|
1065
|
+
mnsa.append( t )
|
|
1066
|
+
|
|
1067
|
+
if (s < 0):
|
|
1068
|
+
t = m
|
|
1069
|
+
mnsb.append( t )
|
|
1070
|
+
|
|
1071
|
+
if u:
|
|
1072
|
+
t = m
|
|
1073
|
+
mnsc.append(t)
|
|
1074
|
+
|
|
1075
|
+
if (t < m):
|
|
1076
|
+
t = m
|
|
1077
|
+
ttcol_widths.append(t)
|
|
1078
|
+
|
|
1079
|
+
cols_width = sum(ttcol_widths)
|
|
1080
|
+
|
|
1081
|
+
# In decreasing order by width
|
|
1082
|
+
if table_max_chars >= cols_width:
|
|
1083
|
+
width = cols_width
|
|
1084
|
+
|
|
1085
|
+
elif table_max_chars > sum(mnsc):
|
|
1086
|
+
# partially expand columns with greater maximal width from mnsc
|
|
1087
|
+
target = table_max_chars - sum(mnsc)
|
|
1088
|
+
target, ttcol_widths = self.expand_cols_by_height(mnsc, target, ttcol_widths)
|
|
1089
|
+
|
|
1090
|
+
elif table_max_chars == sum(mnsc):
|
|
1091
|
+
ttcol_widths = mnsc
|
|
1092
|
+
|
|
1093
|
+
elif table_max_chars > sum(mnsb):
|
|
1094
|
+
# Partially expand columns with unspecified width from mnsb
|
|
1095
|
+
target = table_max_chars - sum(mnsb)
|
|
1096
|
+
target, ttcol_widths = self.expand_cols_by_height(mnsb, target, ttcol_widths)
|
|
1097
|
+
|
|
1098
|
+
elif table_max_chars == sum(mnsb):
|
|
1099
|
+
ttcol_widths = mnsb
|
|
1100
|
+
|
|
1101
|
+
elif table_max_chars > sum(mnsa):
|
|
1102
|
+
#Partially expand columns with relative width from mnsa.
|
|
1103
|
+
|
|
1104
|
+
ttcol_widths = mnsa
|
|
1105
|
+
target = table_max_chars - sum(mnsa)
|
|
1106
|
+
while target > 0:
|
|
1107
|
+
rel = []
|
|
1108
|
+
num_rel = 0
|
|
1109
|
+
rel_total = 0
|
|
1110
|
+
fnum = 1000
|
|
1111
|
+
fden = 1
|
|
1112
|
+
for m,s,w in zip(longest_lines, ttcol_width_attrs, ttcol_widths):
|
|
1113
|
+
x = 0
|
|
1114
|
+
if s < 0:
|
|
1115
|
+
d = m - w
|
|
1116
|
+
if d > 0:
|
|
1117
|
+
x = -s
|
|
1118
|
+
if d * fden < x * fnum:
|
|
1119
|
+
fnum = d
|
|
1120
|
+
fden = x
|
|
1121
|
+
num_rel += 1;
|
|
1122
|
+
rel_total += x
|
|
1123
|
+
rel.append(x)
|
|
1124
|
+
|
|
1125
|
+
if target * fden < rel_total * fnum:
|
|
1126
|
+
fnum = target
|
|
1127
|
+
fden = rel_total
|
|
1128
|
+
|
|
1129
|
+
plus_width = 0
|
|
1130
|
+
if (num_rel > 0) and (fnum > 0):
|
|
1131
|
+
kols = []
|
|
1132
|
+
for r,w in zip(rel, ttcol_widths):
|
|
1133
|
+
p = r*fnum//fden
|
|
1134
|
+
kols.append(w + p)
|
|
1135
|
+
plus_width += p
|
|
1136
|
+
|
|
1137
|
+
if plus_width > 0:
|
|
1138
|
+
ttcol_widths = kols
|
|
1139
|
+
target += -plus_width
|
|
1140
|
+
|
|
1141
|
+
elif num_rel > 0:
|
|
1142
|
+
dd = target
|
|
1143
|
+
kols = []
|
|
1144
|
+
for r,w in zip(rel, ttcol_widths):
|
|
1145
|
+
if r > 0:
|
|
1146
|
+
# prefer expanding the rightmost ones
|
|
1147
|
+
if dd >= num_rel:
|
|
1148
|
+
w += 1
|
|
1149
|
+
target -= 1
|
|
1150
|
+
dd += 1
|
|
1151
|
+
kols.append(w)
|
|
1152
|
+
ttcol_widths = kols
|
|
1153
|
+
|
|
1154
|
+
else:
|
|
1155
|
+
xml2rfc.log.error("bug in expanding <texttable> columns with relative widths")
|
|
1156
|
+
|
|
1157
|
+
elif table_max_chars == sum(mnsa):
|
|
1158
|
+
ttcol_widths = mnsa
|
|
1159
|
+
else:
|
|
1160
|
+
# Partially expand comns with greater specified width from longest_words
|
|
1161
|
+
target = table_max_chars - min_width
|
|
1162
|
+
plus = []
|
|
1163
|
+
for a,b in zip(mnsa, longest_words):
|
|
1164
|
+
plus.append(a-b)
|
|
1165
|
+
target, ttcol_widths = self.expand_cols(longest_words, target, plus)
|
|
1166
|
+
|
|
1167
|
+
# Ensure we don't have any zero-width columns; it breaks textwrap
|
|
1168
|
+
ttcol_widths = [ k or 1 for k in ttcol_widths ]
|
|
1169
|
+
# Now construct the cells using textwrap against ttcol_widths
|
|
1170
|
+
cell_lines = [
|
|
1171
|
+
[ textwrap.wrap(cell, ttcol_widths[j]) or [''] for j, cell in enumerate(matrix[i]) ]
|
|
1172
|
+
for i in range(0, len(matrix))
|
|
1173
|
+
]
|
|
1174
|
+
|
|
1175
|
+
output = []
|
|
1176
|
+
# Create the border
|
|
1177
|
+
if style == 'none':
|
|
1178
|
+
pass
|
|
1179
|
+
elif style == 'headers':
|
|
1180
|
+
borderstring = []
|
|
1181
|
+
for i in range(num_columns):
|
|
1182
|
+
borderstring.append('-' * ttcol_widths[i])
|
|
1183
|
+
borderstring.append(' ')
|
|
1184
|
+
else:
|
|
1185
|
+
borderstring = ['+']
|
|
1186
|
+
for i in range(num_columns):
|
|
1187
|
+
borderstring.append('-' * (ttcol_widths[i] + 2))
|
|
1188
|
+
borderstring.append('+')
|
|
1189
|
+
output.append(''.join(borderstring))
|
|
1190
|
+
|
|
1191
|
+
# Draw the table
|
|
1192
|
+
for i, cell_line in enumerate(cell_lines):
|
|
1193
|
+
if i==0 and cell_line == [['']]*num_columns:
|
|
1194
|
+
if style in ['headers']:
|
|
1195
|
+
# This is the header row, append the header decoration
|
|
1196
|
+
output.append(''.join(borderstring))
|
|
1197
|
+
continue
|
|
1198
|
+
# produce as many output rows as the number of wrapped
|
|
1199
|
+
# text lines in the cell with most lines, but at least 1
|
|
1200
|
+
for row in range(0, max(map(len, cell_line))):
|
|
1201
|
+
if style == 'headers' or style == 'none':
|
|
1202
|
+
line = ['']
|
|
1203
|
+
else:
|
|
1204
|
+
line = ['|']
|
|
1205
|
+
for col, cell in enumerate(cell_line):
|
|
1206
|
+
align = column_aligns[col]
|
|
1207
|
+
width = ttcol_widths[col]
|
|
1208
|
+
if row < len(cell):
|
|
1209
|
+
# width = width + len(cell[row]) - self._length(cell[row])
|
|
1210
|
+
if align == 'center':
|
|
1211
|
+
text = cell[row].center(width)
|
|
1212
|
+
elif align == 'right':
|
|
1213
|
+
text = cell[row].rjust(width)
|
|
1214
|
+
else: # align == left
|
|
1215
|
+
text = cell[row].ljust(width)
|
|
1216
|
+
if style == 'headers' or style == 'none':
|
|
1217
|
+
line.append(text)
|
|
1218
|
+
line.append(' ')
|
|
1219
|
+
else:
|
|
1220
|
+
line.append(' ')
|
|
1221
|
+
line.append(text)
|
|
1222
|
+
line.append(' |')
|
|
1223
|
+
else:
|
|
1224
|
+
if style == 'headers' or style == 'none':
|
|
1225
|
+
line.append(' ' * (ttcol_widths[col] + 1))
|
|
1226
|
+
else:
|
|
1227
|
+
line.append(' ' * (ttcol_widths[col] + 2) + '|')
|
|
1228
|
+
output.append(''.join(line))
|
|
1229
|
+
if style in ['headers', 'full']:
|
|
1230
|
+
if i == 0:
|
|
1231
|
+
# This is the header row, append the header decoration
|
|
1232
|
+
output.append(''.join(borderstring))
|
|
1233
|
+
if style in ['full']:
|
|
1234
|
+
if i == len(cell_lines)-1:
|
|
1235
|
+
output.append(''.join(borderstring))
|
|
1236
|
+
if style in ['all']:
|
|
1237
|
+
output.append(''.join(borderstring))
|
|
1238
|
+
|
|
1239
|
+
|
|
1240
|
+
# Finally, write the table to the buffer with proper alignment
|
|
1241
|
+
align = table.attrib.get('align', self.defaults['table_align'])
|
|
1242
|
+
self.write_raw('\n'.join(output), align=align, indent=self.margin,
|
|
1243
|
+
source_line=table.sourceline)
|
|
1244
|
+
|
|
1245
|
+
|
|
1246
|
+
def expand_cols_by_height(self, cols, target, bound):
|
|
1247
|
+
# cols - current column width
|
|
1248
|
+
# target - expand by this amount
|
|
1249
|
+
# bound - this is the lower boundary of the column width
|
|
1250
|
+
|
|
1251
|
+
while target > 0:
|
|
1252
|
+
# Compute the current width and boundary width for those where current is less than boundary
|
|
1253
|
+
m_width = 0
|
|
1254
|
+
w_width = 0
|
|
1255
|
+
for m,w in zip(bound, cols):
|
|
1256
|
+
if (m > w):
|
|
1257
|
+
# Only cover those columns below the bound.
|
|
1258
|
+
m_width += m
|
|
1259
|
+
w_width += w
|
|
1260
|
+
|
|
1261
|
+
# This mixed number is the average height we could achieve.
|
|
1262
|
+
|
|
1263
|
+
fnum = m_width
|
|
1264
|
+
fden = w_width + target
|
|
1265
|
+
plus_num = 0
|
|
1266
|
+
plus = []
|
|
1267
|
+
plus_width = 0
|
|
1268
|
+
kols = []
|
|
1269
|
+
for m,w in zip(bound, cols):
|
|
1270
|
+
p = 0
|
|
1271
|
+
k = w
|
|
1272
|
+
if m > w:
|
|
1273
|
+
x = m * fden
|
|
1274
|
+
y = w * fnum
|
|
1275
|
+
# Seek columns of above average height.
|
|
1276
|
+
if x >= y:
|
|
1277
|
+
# Nearly reduce height to average by augmenting width
|
|
1278
|
+
p = 1
|
|
1279
|
+
k = x // (fnum + fden - 1)
|
|
1280
|
+
if k < w:
|
|
1281
|
+
# but no smaller than before.
|
|
1282
|
+
k = w
|
|
1283
|
+
if k > w + target:
|
|
1284
|
+
k = w + target
|
|
1285
|
+
q = k - w
|
|
1286
|
+
plus_num += 1
|
|
1287
|
+
plus_width += q
|
|
1288
|
+
|
|
1289
|
+
plus.append(p)
|
|
1290
|
+
kols.append(k)
|
|
1291
|
+
|
|
1292
|
+
if plus_width > 0:
|
|
1293
|
+
cols = kols
|
|
1294
|
+
target += (-plus_width)
|
|
1295
|
+
|
|
1296
|
+
elif plus_num > 0:
|
|
1297
|
+
# No progress due to integer division. Make some.
|
|
1298
|
+
dd = target
|
|
1299
|
+
cols = []
|
|
1300
|
+
for p,w in zip(plus, kols):
|
|
1301
|
+
if p > 0:
|
|
1302
|
+
# Prefer expanding the rightmost ones.
|
|
1303
|
+
if dd >= plus_num:
|
|
1304
|
+
w += 1
|
|
1305
|
+
target -= 1
|
|
1306
|
+
dd += 1
|
|
1307
|
+
cols.append(w)
|
|
1308
|
+
|
|
1309
|
+
else:
|
|
1310
|
+
# Should not happen, but just in case we were given a bad target
|
|
1311
|
+
cols = cols
|
|
1312
|
+
break;
|
|
1313
|
+
|
|
1314
|
+
return [target, cols]
|
|
1315
|
+
|
|
1316
|
+
def shave_cols_excess (self, cols, excess, give, give_width=-1):
|
|
1317
|
+
if give_width < 0:
|
|
1318
|
+
give_width =sum(give)
|
|
1319
|
+
|
|
1320
|
+
if excess <= 0 or give_width <= 0:
|
|
1321
|
+
# Nothing to do.
|
|
1322
|
+
pass
|
|
1323
|
+
elif excess >= give_width:
|
|
1324
|
+
# Take everything; it may still not be enough.
|
|
1325
|
+
plus = []
|
|
1326
|
+
for a,b in zip(cols, give):
|
|
1327
|
+
plus.append(a-b)
|
|
1328
|
+
cols = plus
|
|
1329
|
+
excess += -(give_width)
|
|
1330
|
+
else:
|
|
1331
|
+
# Take only what we need, in relative proportion.
|
|
1332
|
+
dd = 0
|
|
1333
|
+
kols = []
|
|
1334
|
+
for c, g in zip(cols, give):
|
|
1335
|
+
if g > 0:
|
|
1336
|
+
d =(g * excess) // give_width
|
|
1337
|
+
c += -(d)
|
|
1338
|
+
dd += d
|
|
1339
|
+
kols.append( c )
|
|
1340
|
+
excess += -(dd)
|
|
1341
|
+
|
|
1342
|
+
# Do the ones we missed because of integer division.
|
|
1343
|
+
if excess > 0:
|
|
1344
|
+
cols = []
|
|
1345
|
+
for k, g in zip(kols, give):
|
|
1346
|
+
# Prefer shaving the leftmost ones.
|
|
1347
|
+
if excess > 0 and g > 0:
|
|
1348
|
+
k += -1
|
|
1349
|
+
excess += -1
|
|
1350
|
+
cols.append( k )
|
|
1351
|
+
else:
|
|
1352
|
+
cols = kols
|
|
1353
|
+
|
|
1354
|
+
return [excess, cols]
|
|
1355
|
+
|
|
1356
|
+
def expand_cols(self, cols, target, plus, plus_width=-1):
|
|
1357
|
+
if plus_width < 0:
|
|
1358
|
+
plus_width =sum(plus)
|
|
1359
|
+
|
|
1360
|
+
if target <= 0 or plus_width <= 0:
|
|
1361
|
+
# Nothing to do.
|
|
1362
|
+
pass
|
|
1363
|
+
elif target >= plus_width:
|
|
1364
|
+
# Take everything; it may still not be enough.
|
|
1365
|
+
tmp = []
|
|
1366
|
+
for a,b in zip(cols, plus):
|
|
1367
|
+
tmp.append(a+b)
|
|
1368
|
+
cols = tmp
|
|
1369
|
+
target += -(plus_width)
|
|
1370
|
+
else:
|
|
1371
|
+
# Take only what we need, in relative proportion.
|
|
1372
|
+
dd = 0
|
|
1373
|
+
dr = 0
|
|
1374
|
+
kols = []
|
|
1375
|
+
for c,p in zip(cols, plus):
|
|
1376
|
+
if p > 0:
|
|
1377
|
+
d = (p * target) // plus_width
|
|
1378
|
+
c += d
|
|
1379
|
+
dd += d
|
|
1380
|
+
dr += 1
|
|
1381
|
+
kols.append(c)
|
|
1382
|
+
|
|
1383
|
+
target += -(dd)
|
|
1384
|
+
|
|
1385
|
+
# Do the ones we missed because of integer division.
|
|
1386
|
+
if target > 0:
|
|
1387
|
+
dd = target
|
|
1388
|
+
cols = []
|
|
1389
|
+
for k, p in zip(kols, plus):
|
|
1390
|
+
if p > 0:
|
|
1391
|
+
# Prefer expanding the rightmost ones.
|
|
1392
|
+
if dd >= dr:
|
|
1393
|
+
k += 1
|
|
1394
|
+
target -= 1
|
|
1395
|
+
dd += 1
|
|
1396
|
+
cols.append( k )
|
|
1397
|
+
else:
|
|
1398
|
+
cols = kols
|
|
1399
|
+
|
|
1400
|
+
return [target, cols]
|
|
1401
|
+
|
|
1402
|
+
def insert_anchor(self, text):
|
|
1403
|
+
# No anchors for text
|
|
1404
|
+
pass
|
|
1405
|
+
|
|
1406
|
+
def pre_indexing(self):
|
|
1407
|
+
pass
|
|
1408
|
+
|
|
1409
|
+
def pre_rendering(self):
|
|
1410
|
+
# Discard buffer from indexing pass
|
|
1411
|
+
self.buf = []
|
|
1412
|
+
|
|
1413
|
+
# Reset document counters from indexing pass
|
|
1414
|
+
self.list_counters = {}
|
|
1415
|
+
self.edit_counter = 0 # Counter for edit marks
|
|
1416
|
+
self.cref_counter = 0
|
|
1417
|
+
self.cref_list = []
|
|
1418
|
+
|
|
1419
|
+
def post_rendering(self):
|
|
1420
|
+
# Insert the TOC and IREF into the main buffer
|
|
1421
|
+
self.output = self.buf[:self.toc_marker] + \
|
|
1422
|
+
self.write_toc()
|
|
1423
|
+
if self.iref_marker > 0:
|
|
1424
|
+
self.output += self.buf[self.toc_marker:self.iref_marker] + \
|
|
1425
|
+
self.write_iref_index() + \
|
|
1426
|
+
self.buf[self.iref_marker:]
|
|
1427
|
+
else:
|
|
1428
|
+
self.output += self.buf[self.toc_marker:]
|
|
1429
|
+
|
|
1430
|
+
def post_process_lines(self, lines):
|
|
1431
|
+
output = []
|
|
1432
|
+
for line in lines:
|
|
1433
|
+
if isinstance(line, type(u'')):
|
|
1434
|
+
# line = line.replace(u"\u8209", '-')
|
|
1435
|
+
line = line.replace(u'\u00A0', u' ')
|
|
1436
|
+
line = line.replace(u'\u2011', u'-')
|
|
1437
|
+
line = line.replace(u'\u200B', u'')
|
|
1438
|
+
line = line.replace(u'\u2028', u' ')
|
|
1439
|
+
line = line.replace(u'\u2060', u'')
|
|
1440
|
+
assert line == line.replace(u'\uE060', u'')
|
|
1441
|
+
output.append(line);
|
|
1442
|
+
return output
|
|
1443
|
+
|
|
1444
|
+
def write_to_file(self, file):
|
|
1445
|
+
""" Writes the buffer to the specified file """
|
|
1446
|
+
# write initial blank lines, not counted against page size...
|
|
1447
|
+
if self.draft:
|
|
1448
|
+
file.write("\n"*3)
|
|
1449
|
+
else:
|
|
1450
|
+
file.write("\n"*5)
|
|
1451
|
+
for line in self.post_process_lines(self.output):
|
|
1452
|
+
file.write(line.rstrip(u" \t"))
|
|
1453
|
+
file.write("\n")
|