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
xml2rfc/writers/html.py
ADDED
|
@@ -0,0 +1,3110 @@
|
|
|
1
|
+
# Copyright The IETF Trust 2018, All Rights Reserved
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
from __future__ import unicode_literals, print_function, division
|
|
4
|
+
|
|
5
|
+
import lxml
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import unicodedata
|
|
9
|
+
import xml2rfc
|
|
10
|
+
|
|
11
|
+
from contextlib import closing
|
|
12
|
+
from io import open
|
|
13
|
+
from lxml.html import html_parser
|
|
14
|
+
from lxml.html.builder import ElementMaker
|
|
15
|
+
|
|
16
|
+
from urllib.request import urlopen
|
|
17
|
+
from urllib.parse import urlparse, urljoin
|
|
18
|
+
|
|
19
|
+
try:
|
|
20
|
+
from xml2rfc import debug
|
|
21
|
+
debug.debug = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
from xml2rfc import log, strings
|
|
26
|
+
from xml2rfc.writers.base import default_options, BaseV3Writer, RfcWriterError, SUBSERIES
|
|
27
|
+
from xml2rfc.uniscripts import is_script
|
|
28
|
+
from xml2rfc.util.date import extract_date, augment_date, format_date, format_date_iso, get_expiry_date
|
|
29
|
+
from xml2rfc.util.name import ( full_author_name_expansion, short_author_role,
|
|
30
|
+
ref_author_name_first, ref_author_name_last,
|
|
31
|
+
short_author_name_set, full_author_name_set,
|
|
32
|
+
short_org_name_set, full_org_name, )
|
|
33
|
+
from xml2rfc.util.postal import ( get_normalized_address_info, address_hcard_properties,
|
|
34
|
+
get_address_format_rules, address_field_mapping, )
|
|
35
|
+
from xml2rfc.util.unicode import expand_unicode_element
|
|
36
|
+
from xml2rfc.utils import namespaces, is_htmlblock, find_duplicate_html_ids, build_dataurl, sdict, clean_text
|
|
37
|
+
|
|
38
|
+
#from xml2rfc import utils
|
|
39
|
+
|
|
40
|
+
# ------------------------------------------------------------------------------
|
|
41
|
+
|
|
42
|
+
seen = set()
|
|
43
|
+
|
|
44
|
+
def wrap(h, tag, **kwargs):
|
|
45
|
+
w = build(tag, **kwargs)
|
|
46
|
+
w.append(h)
|
|
47
|
+
return w
|
|
48
|
+
|
|
49
|
+
def slugify(s):
|
|
50
|
+
s = s.strip().lower()
|
|
51
|
+
s = re.sub(r'[^\w\s/|-]', '', s)
|
|
52
|
+
s = re.sub(r'[-_\s/|]+', '-', s)
|
|
53
|
+
s = s.strip('-')
|
|
54
|
+
return s
|
|
55
|
+
|
|
56
|
+
def maybefloat(f):
|
|
57
|
+
try:
|
|
58
|
+
return float(f)
|
|
59
|
+
except (ValueError, TypeError):
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
def wrap_ascii(tag, name, ascii, role='', classes=None):
|
|
63
|
+
"""Build a tag containing a name optionally formatted to show its ascii representation"""
|
|
64
|
+
role = ('','') if role in ['',None] else (', ', role)
|
|
65
|
+
if ascii:
|
|
66
|
+
e = build(tag,
|
|
67
|
+
build.span(name, classes='non-ascii'),
|
|
68
|
+
' (',
|
|
69
|
+
build.span(ascii, classes='ascii'),
|
|
70
|
+
')',
|
|
71
|
+
*role,
|
|
72
|
+
classes=classes
|
|
73
|
+
)
|
|
74
|
+
else:
|
|
75
|
+
e = build(tag, name, *role, classes=classes)
|
|
76
|
+
return e
|
|
77
|
+
|
|
78
|
+
def id_for_pn(pn):
|
|
79
|
+
"""Convert a pn to an HTML element ID"""
|
|
80
|
+
if HtmlWriter.is_appendix(pn):
|
|
81
|
+
_, num, para = HtmlWriter.split_pn(pn)
|
|
82
|
+
frag = 'appendix-%s' % num.title()
|
|
83
|
+
if para is None or len(para) == 0:
|
|
84
|
+
return frag
|
|
85
|
+
else:
|
|
86
|
+
return '%s-%s' % (frag, para)
|
|
87
|
+
# not an appendix
|
|
88
|
+
return pn
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class ClassElementMaker(ElementMaker):
|
|
92
|
+
|
|
93
|
+
def __call__(self, tag, *children, **attrib):
|
|
94
|
+
classes = attrib.pop('classes', None)
|
|
95
|
+
attrib = sdict(dict( (k,v) for k,v in attrib.items() if v != None))
|
|
96
|
+
elem = super(ClassElementMaker, self).__call__(tag, *children, **attrib)
|
|
97
|
+
if classes:
|
|
98
|
+
elem.set('class', classes)
|
|
99
|
+
if is_htmlblock(elem) and (elem.tail is None or elem.tail.strip() == ''):
|
|
100
|
+
elem.tail = '\n'
|
|
101
|
+
return elem
|
|
102
|
+
build = ClassElementMaker(makeelement=html_parser.makeelement)
|
|
103
|
+
|
|
104
|
+
class ExtendingElementMaker(ClassElementMaker):
|
|
105
|
+
|
|
106
|
+
def __call__(self, tag, parent, precursor, *children, **attrib):
|
|
107
|
+
elem = super(ExtendingElementMaker, self).__call__(tag, *children, **attrib)
|
|
108
|
+
is_block = is_htmlblock(elem)
|
|
109
|
+
#
|
|
110
|
+
child = elem
|
|
111
|
+
if precursor != None:
|
|
112
|
+
pn = precursor.get('pn')
|
|
113
|
+
sn = precursor.get('slugifiedName')
|
|
114
|
+
an = precursor.get('anchor')
|
|
115
|
+
if pn != None:
|
|
116
|
+
id = id_for_pn(pn)
|
|
117
|
+
elem.set('id', id)
|
|
118
|
+
if an != None:
|
|
119
|
+
if is_block:
|
|
120
|
+
child = wrap(elem, 'div', id=an)
|
|
121
|
+
else:
|
|
122
|
+
# cannot wrap a non-block in <div>, so we invert the
|
|
123
|
+
# wrapping by swapping the tags:
|
|
124
|
+
child = wrap(elem, 'div', id=id, **attrib)
|
|
125
|
+
elem.tag = child.tag
|
|
126
|
+
child.tag = tag
|
|
127
|
+
for k in attrib:
|
|
128
|
+
if k in elem.attrib:
|
|
129
|
+
del elem.attrib[k]
|
|
130
|
+
elem.set('id', an)
|
|
131
|
+
elif sn != None:
|
|
132
|
+
elem.set('id', sn)
|
|
133
|
+
elif an != None:
|
|
134
|
+
elem.set('id', an)
|
|
135
|
+
kp = precursor.get('keepWithPrevious', '')
|
|
136
|
+
if kp:
|
|
137
|
+
elem.set('class', ' '.join(s for s in [elem.get('class', ''), 'keepWithPrevious'] if s ))
|
|
138
|
+
kn = precursor.get('keepWithNext', '')
|
|
139
|
+
if kn:
|
|
140
|
+
elem.set('class', ' '.join(s for s in [elem.get('class', ''), 'keepWithNext'] if s ))
|
|
141
|
+
if not elem.text or elem.text.strip() == '':
|
|
142
|
+
elem.text = precursor.text
|
|
143
|
+
elem.tail = precursor.tail
|
|
144
|
+
if parent != None:
|
|
145
|
+
parent.append(child)
|
|
146
|
+
if is_block and (elem.tail is None or elem.tail.strip() == ''):
|
|
147
|
+
elem.tail = '\n'
|
|
148
|
+
if is_block and child != elem and (child.tail is None or child.tail.strip() == ''):
|
|
149
|
+
child.tail = '\n'
|
|
150
|
+
return elem
|
|
151
|
+
add = ExtendingElementMaker(makeelement=html_parser.makeelement)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
pilcrow = '\u00b6'
|
|
155
|
+
mdash = '\u2014'
|
|
156
|
+
|
|
157
|
+
# ------------------------------------------------------------------------------
|
|
158
|
+
# Address formatting functions, based on i18naddress functions, but rewritten to
|
|
159
|
+
# produce html entities, rather than text lines.
|
|
160
|
+
|
|
161
|
+
def _format_address_line(line_format, address, rules):
|
|
162
|
+
def _get_field(name):
|
|
163
|
+
field = []
|
|
164
|
+
value = address.get(name, '')
|
|
165
|
+
if value:
|
|
166
|
+
span = None
|
|
167
|
+
if name == 'name':
|
|
168
|
+
role = address.get('role', '')
|
|
169
|
+
if role:
|
|
170
|
+
span = build.span(value,
|
|
171
|
+
' (',
|
|
172
|
+
build.span(role, classes='role'),
|
|
173
|
+
')',
|
|
174
|
+
classes=address_hcard_properties[name])
|
|
175
|
+
if span == None:
|
|
176
|
+
span = build.span(value, classes=address_hcard_properties[name])
|
|
177
|
+
field.append(span)
|
|
178
|
+
return field
|
|
179
|
+
|
|
180
|
+
replacements = {
|
|
181
|
+
'%%%s' % code: _get_field(field_name)
|
|
182
|
+
for code, field_name in address_field_mapping.items()}
|
|
183
|
+
|
|
184
|
+
fields = re.split('(%.)', line_format)
|
|
185
|
+
has_content = any([ replacements.get(f) for f in fields if (f.startswith('%') and f!= '%%') ])
|
|
186
|
+
if not has_content:
|
|
187
|
+
return ''
|
|
188
|
+
values = [ f for n in fields for f in replacements.get(n, n) ]
|
|
189
|
+
# strip left comma and space
|
|
190
|
+
for i in range(len(values)):
|
|
191
|
+
if values[i] not in [' ', ',']:
|
|
192
|
+
break
|
|
193
|
+
values = values[i:]
|
|
194
|
+
return values
|
|
195
|
+
|
|
196
|
+
def format_address(address, latin=False, normalize=False):
|
|
197
|
+
def hasword(item):
|
|
198
|
+
if item is None:
|
|
199
|
+
return False
|
|
200
|
+
line = ''.join(list(item.itertext()))
|
|
201
|
+
return re.search(r'\w', line, re.U) != None
|
|
202
|
+
address_format, rules = get_address_format_rules(address, latin, normalize)
|
|
203
|
+
address_line_formats = address_format.split('%n')
|
|
204
|
+
address_lines = [
|
|
205
|
+
build.div(*_format_address_line(lf, address, rules), dir='auto')
|
|
206
|
+
for lf in address_line_formats]
|
|
207
|
+
address_lines = filter(hasword, address_lines)
|
|
208
|
+
return address_lines
|
|
209
|
+
|
|
210
|
+
def get_bidi_alignment(address):
|
|
211
|
+
# We don't attempt to control the bidi layout in detail, but leave that to
|
|
212
|
+
#the html layout engine; but want to know whether we have Right-to-left content
|
|
213
|
+
#in order to set the overall alignment of the address block.
|
|
214
|
+
for field in address:
|
|
215
|
+
line = address[field]
|
|
216
|
+
if line:
|
|
217
|
+
for ch in line:
|
|
218
|
+
if isinstance(ch, str):
|
|
219
|
+
dir = unicodedata.bidirectional(ch)
|
|
220
|
+
if dir in ['R', 'AL']:
|
|
221
|
+
return 'right'
|
|
222
|
+
return 'left'
|
|
223
|
+
|
|
224
|
+
# ------------------------------------------------------------------------------
|
|
225
|
+
|
|
226
|
+
class HtmlWriter(BaseV3Writer):
|
|
227
|
+
|
|
228
|
+
def __init__(self, xmlrfc, quiet=None, options=default_options, date=None):
|
|
229
|
+
super(HtmlWriter, self).__init__(xmlrfc, quiet=quiet, options=options, date=date)
|
|
230
|
+
self.anchor_tags = self.get_tags_with_anchor()
|
|
231
|
+
self.duplicate_html_ids = set()
|
|
232
|
+
self.filename = None
|
|
233
|
+
self.refname_mapping = self.get_refname_mapping()
|
|
234
|
+
|
|
235
|
+
def get_tags_with_anchor(self):
|
|
236
|
+
anchor_nodes = self.schema.xpath("//x:define/x:element//x:attribute[@name='anchor']", namespaces=namespaces)
|
|
237
|
+
element_nodes = set()
|
|
238
|
+
for a in anchor_nodes:
|
|
239
|
+
for e in a.iterancestors():
|
|
240
|
+
if e.tag.endswith('element'):
|
|
241
|
+
element_nodes.add(e.get('name'))
|
|
242
|
+
break
|
|
243
|
+
return element_nodes
|
|
244
|
+
|
|
245
|
+
def html_tree(self):
|
|
246
|
+
if not self.root.get('prepTime'):
|
|
247
|
+
prep = xml2rfc.PrepToolWriter(self.xmlrfc, options=self.options, date=self.options.date, liberal=True, keep_pis=[xml2rfc.V3_PI_TARGET])
|
|
248
|
+
tree = prep.prep()
|
|
249
|
+
self.tree = tree
|
|
250
|
+
self.root = self.tree.getroot()
|
|
251
|
+
html_tree = self.render(None, self.root)
|
|
252
|
+
html_tree = self.post_process(html_tree)
|
|
253
|
+
return html_tree
|
|
254
|
+
|
|
255
|
+
def html(self, html_tree=None):
|
|
256
|
+
if html_tree is None:
|
|
257
|
+
html_tree = self.html_tree()
|
|
258
|
+
# 6.1. DOCTYPE
|
|
259
|
+
#
|
|
260
|
+
# The DOCTYPE of the document is "html", which declares that the
|
|
261
|
+
# document is compliant with HTML5. The document will start with
|
|
262
|
+
# exactly this string:
|
|
263
|
+
#
|
|
264
|
+
# <!DOCTYPE html>
|
|
265
|
+
html = lxml.etree.tostring(html_tree, method='html', encoding='unicode', pretty_print=True, doctype="<!DOCTYPE html>")
|
|
266
|
+
html = re.sub(r'[\x00-\x09\x0B-\x1F]+', ' ', html)
|
|
267
|
+
return html
|
|
268
|
+
|
|
269
|
+
def write(self, filename):
|
|
270
|
+
self.filename = filename
|
|
271
|
+
|
|
272
|
+
"""Write the document to a file """
|
|
273
|
+
html_tree = self.html_tree()
|
|
274
|
+
|
|
275
|
+
# Check for duplicate IDs
|
|
276
|
+
dups = set(find_duplicate_html_ids(html_tree)) - self.duplicate_html_ids
|
|
277
|
+
for attr, id, e in dups:
|
|
278
|
+
self.warn(self.root[-1], 'Duplicate %s="%s" found in generated HTML.' % (attr, id, ))
|
|
279
|
+
|
|
280
|
+
if self.errors:
|
|
281
|
+
raise RfcWriterError("Not creating output file due to errors (see above)")
|
|
282
|
+
|
|
283
|
+
# Use lxml's built-in serialization
|
|
284
|
+
with open(filename, 'w', encoding='utf-8') as file:
|
|
285
|
+
text = self.html(html_tree)
|
|
286
|
+
file.write(text)
|
|
287
|
+
|
|
288
|
+
if not self.options.quiet:
|
|
289
|
+
self.log(' Created file %s' % filename)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def read_css(self, data_dir, fn):
|
|
293
|
+
try:
|
|
294
|
+
if urlparse(fn).scheme:
|
|
295
|
+
with closing(urlopen(fn)) as f:
|
|
296
|
+
return f.read(), fn
|
|
297
|
+
else:
|
|
298
|
+
for path in ['.', data_dir, ]:
|
|
299
|
+
for ext in ['', '.css', ]:
|
|
300
|
+
cssin = os.path.join(path, fn + ext)
|
|
301
|
+
if os.path.exists(cssin):
|
|
302
|
+
with open(cssin) as f:
|
|
303
|
+
return f.read(), cssin
|
|
304
|
+
except IOError as e:
|
|
305
|
+
self.err(self.root, "Error when trying to read external css: %s" % e)
|
|
306
|
+
return None, None
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def render(self, h, x):
|
|
310
|
+
res = None
|
|
311
|
+
if x.tag in (lxml.etree.PI, lxml.etree.Comment):
|
|
312
|
+
tail = x.tail if x.tail and x.tail.strip() else ''
|
|
313
|
+
if len(h):
|
|
314
|
+
last = h[-1]
|
|
315
|
+
last.tail = (last.tail or '') + tail
|
|
316
|
+
else:
|
|
317
|
+
h.text = (h.text or '') + tail
|
|
318
|
+
else:
|
|
319
|
+
func_name = "render_%s" % (x.tag.lower(),)
|
|
320
|
+
func = getattr(self, func_name, None)
|
|
321
|
+
if func == None:
|
|
322
|
+
func = self.default_renderer
|
|
323
|
+
if x.tag in self.__class__.deprecated_element_tags:
|
|
324
|
+
self.warn(x, "Was asked to render a deprecated element: <%s>", (x.tag, ))
|
|
325
|
+
elif not x.tag in seen:
|
|
326
|
+
self.warn(x, "No renderer for <%s> found" % (x.tag, ))
|
|
327
|
+
seen.add(x.tag)
|
|
328
|
+
res = func(h, x)
|
|
329
|
+
return res
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def default_renderer(self, h, x):
|
|
333
|
+
hh = add(x.tag, h, x)
|
|
334
|
+
for c in x.getchildren():
|
|
335
|
+
self.render(hh, c)
|
|
336
|
+
return hh
|
|
337
|
+
|
|
338
|
+
def skip_renderer(self, h, x):
|
|
339
|
+
part = self.part
|
|
340
|
+
for c in x.getchildren():
|
|
341
|
+
self.part = part
|
|
342
|
+
self.render(h, c)
|
|
343
|
+
|
|
344
|
+
def address_line_renderer(self, h, x, classes=None):
|
|
345
|
+
if x.text:
|
|
346
|
+
div = build.div(build.span(x.text.strip(), classes=classes))
|
|
347
|
+
h.append(div)
|
|
348
|
+
else:
|
|
349
|
+
div = None
|
|
350
|
+
return div
|
|
351
|
+
|
|
352
|
+
def inline_text_renderer(self, h, x):
|
|
353
|
+
h.text = x.text.lstrip() if x.text else ''
|
|
354
|
+
children = list(x.getchildren())
|
|
355
|
+
if children:
|
|
356
|
+
for c in children:
|
|
357
|
+
self.render(h, c)
|
|
358
|
+
last = h[-1]
|
|
359
|
+
last.tail = last.tail.rstrip() if last.tail else ''
|
|
360
|
+
else:
|
|
361
|
+
h.text = h.text.rstrip()
|
|
362
|
+
h.tail = x.tail
|
|
363
|
+
|
|
364
|
+
def null_renderer(self, h, x):
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
def contains_pilcrow_in_sub_element(self, e):
|
|
368
|
+
return( len(e.xpath('.//*[@class="pilcrow"]')) > 0 )
|
|
369
|
+
|
|
370
|
+
def part_of_table_of_contents(self, e):
|
|
371
|
+
return( len(e.xpath('ancestor::*[contains(@class, "toc")]')) > 0 )
|
|
372
|
+
|
|
373
|
+
def maybe_add_pilcrow(self, e, first=False):
|
|
374
|
+
if not self.contains_pilcrow_in_sub_element(e) and not self.part_of_table_of_contents(e):
|
|
375
|
+
id = e.get('id')
|
|
376
|
+
if id:
|
|
377
|
+
if len(e):
|
|
378
|
+
if e[-1].tail:
|
|
379
|
+
e[-1].tail = e[-1].tail.rstrip()
|
|
380
|
+
elif e.text:
|
|
381
|
+
e.text = e.text.rstrip()
|
|
382
|
+
else:
|
|
383
|
+
pass
|
|
384
|
+
if ''.join(list(e.itertext())):
|
|
385
|
+
p = build.a(pilcrow, classes='pilcrow', href='#%s'%id)
|
|
386
|
+
if first:
|
|
387
|
+
e.insert(0, p)
|
|
388
|
+
else:
|
|
389
|
+
e.append(p)
|
|
390
|
+
else:
|
|
391
|
+
self.warn(e, 'Tried to add a pilcrow to <%s>, but found no "id" attribute' % e.tag)
|
|
392
|
+
|
|
393
|
+
# --- element rendering functions ------------------------------------------
|
|
394
|
+
|
|
395
|
+
def render_rfc(self, h, x):
|
|
396
|
+
self.part = x.tag
|
|
397
|
+
# 6.2. Root Element
|
|
398
|
+
#
|
|
399
|
+
# The root element of the document is <html>. This element includes a
|
|
400
|
+
# "lang" attribute, whose value is a language tag, as discussed in
|
|
401
|
+
# [RFC5646], that describes the natural language of the document. The
|
|
402
|
+
# language tag to be included is "en". The class of the <html> element
|
|
403
|
+
# will be copied verbatim from the XML <rfc> element's <front>
|
|
404
|
+
# element's <seriesInfo> element's "name" attributes (separated by
|
|
405
|
+
# spaces; see Section 2.47.3 of [RFC7991]), allowing CSS to style RFCs
|
|
406
|
+
# and Internet-Drafts differently from one another (if needed):
|
|
407
|
+
#
|
|
408
|
+
# <html lang="en" class="RFC">
|
|
409
|
+
|
|
410
|
+
classes = ' '.join( i.get('name') for i in x.xpath('./front/seriesInfo') )
|
|
411
|
+
#
|
|
412
|
+
html = h if h != None else build.html(classes=classes, lang='en')
|
|
413
|
+
self.html_root = html
|
|
414
|
+
|
|
415
|
+
# 6.3. <head> Element
|
|
416
|
+
#
|
|
417
|
+
# The root <html> will contain a <head> element that contains the
|
|
418
|
+
# following elements, as needed.
|
|
419
|
+
|
|
420
|
+
head = add.head(html, None)
|
|
421
|
+
|
|
422
|
+
# 6.3.1. Charset Declaration
|
|
423
|
+
#
|
|
424
|
+
# In order to be correctly processed by browsers that load the HTML
|
|
425
|
+
# using a mechanism that does not provide a valid content-type or
|
|
426
|
+
# charset (such as from a local file system using a "file:" URL), the
|
|
427
|
+
# HTML <head> element contains a <meta> element, whose "charset"
|
|
428
|
+
# attribute value is "utf-8":
|
|
429
|
+
#
|
|
430
|
+
# <meta charset="utf-8">
|
|
431
|
+
|
|
432
|
+
add.meta(head, None, charset='utf-8')
|
|
433
|
+
add.meta(head, None, name="scripts", content=x.get('scripts'))
|
|
434
|
+
add.meta(head, None, name="viewport", content="initial-scale=1.0")
|
|
435
|
+
|
|
436
|
+
# 6.3.2. Document Title
|
|
437
|
+
#
|
|
438
|
+
# The contents of the <title> element from the XML source will be
|
|
439
|
+
# placed inside an HTML <title> element in the header.
|
|
440
|
+
|
|
441
|
+
title = x.find('./front/title')
|
|
442
|
+
text = clean_text(' '.join(title.itertext()))
|
|
443
|
+
if self.options.rfc:
|
|
444
|
+
text = ("RFC %s: " % self.root.get('number')) + text
|
|
445
|
+
add.title(head, None, text)
|
|
446
|
+
|
|
447
|
+
# 6.3.3. Document Metadata
|
|
448
|
+
#
|
|
449
|
+
# The following <meta> elements will be included:
|
|
450
|
+
#
|
|
451
|
+
# o author - one each for the each of the "fullname"s and
|
|
452
|
+
# "asciiFullname"s of all of the <author>s from the <front> of the
|
|
453
|
+
# XML source
|
|
454
|
+
for a in x.xpath('./front/author'):
|
|
455
|
+
name = full_author_name_expansion(a) or full_org_name(a)
|
|
456
|
+
add.meta(head, None, name='author', content=name )
|
|
457
|
+
|
|
458
|
+
# o description - the <abstract> from the XML source
|
|
459
|
+
|
|
460
|
+
abstract = x.find('./front/abstract')
|
|
461
|
+
if abstract != None:
|
|
462
|
+
abstract_text = ' '.join(abstract.itertext())
|
|
463
|
+
add.meta(head, None, name='description', content=abstract_text)
|
|
464
|
+
|
|
465
|
+
# o generator - the name and version number of the software used to
|
|
466
|
+
# create the HTML
|
|
467
|
+
|
|
468
|
+
generator = "%s %s" % (xml2rfc.NAME, xml2rfc.__version__)
|
|
469
|
+
add.meta(head, None, name='generator', content=generator)
|
|
470
|
+
|
|
471
|
+
# o keywords - comma-separated <keyword>s from the XML source
|
|
472
|
+
|
|
473
|
+
for keyword in x.xpath('./front/keyword'):
|
|
474
|
+
add.meta(head, None, name='keyword', content=keyword.text)
|
|
475
|
+
|
|
476
|
+
## Additional meta information, not specified in RFC 7992:
|
|
477
|
+
if self.options.rfc:
|
|
478
|
+
add.meta(head, None, name='rfc.number', content=self.root.get('number'))
|
|
479
|
+
else:
|
|
480
|
+
add.meta(head, None, name='ietf.draft', content=self.root.get('docName'))
|
|
481
|
+
|
|
482
|
+
if self.options.inline_version_info:
|
|
483
|
+
versions = lxml.etree.Comment(' Generator version information:\n '
|
|
484
|
+
+('\n '.join(['%s %s'%v for v in xml2rfc.get_versions()]) )
|
|
485
|
+
+'\n'
|
|
486
|
+
)
|
|
487
|
+
versions.tail = '\n'
|
|
488
|
+
head.append(versions)
|
|
489
|
+
|
|
490
|
+
# For example:
|
|
491
|
+
#
|
|
492
|
+
# <meta name="author" content="Joe Hildebrand">
|
|
493
|
+
# <meta name="author" content="JOE HILDEBRAND">
|
|
494
|
+
# <meta name="author" content="Heather Flanagan">
|
|
495
|
+
# <meta name="description" content="This document defines...">
|
|
496
|
+
# <meta name="generator" content="xmljade v0.2.4">
|
|
497
|
+
# <meta name="keywords" content="html,css,rfc">
|
|
498
|
+
#
|
|
499
|
+
# Note: the HTML <meta> tag does not contain a closing slash.
|
|
500
|
+
#
|
|
501
|
+
# 6.3.4. Link to XML Source
|
|
502
|
+
#
|
|
503
|
+
# The <head> element contains a <link> tag, with "rel" attribute of
|
|
504
|
+
# "alternate", "type" attribute of "application/rfc+xml", and "href"
|
|
505
|
+
# attribute pointing to the prepared XML source that was used to
|
|
506
|
+
# generate this document.
|
|
507
|
+
#
|
|
508
|
+
# <link rel="alternate" type="application/rfc+xml" href="source.xml">
|
|
509
|
+
|
|
510
|
+
add.link(head, None, href=os.path.basename(self.xmlrfc.source), rel='alternate', type='application/rfc+xml')
|
|
511
|
+
|
|
512
|
+
# 6.3.5. Link to License
|
|
513
|
+
#
|
|
514
|
+
# The <head> element contains a <link> tag, with "rel" attribute of
|
|
515
|
+
# "license" and "href" attribute pointing to the an appropriate
|
|
516
|
+
# copyright license for the document.
|
|
517
|
+
#
|
|
518
|
+
# <link rel="license"
|
|
519
|
+
# href="https://trustee.ietf.org/trust-legal-provisions.html">
|
|
520
|
+
|
|
521
|
+
add.link(head, None, href="#copyright", rel='license')
|
|
522
|
+
|
|
523
|
+
# 6.3.6. Style
|
|
524
|
+
#
|
|
525
|
+
# The <head> element contains an embedded CSS in a <style> element.
|
|
526
|
+
# The styles in the style sheet are to be set consistently between
|
|
527
|
+
# documents by the RFC Editor, according to the best practices of the
|
|
528
|
+
# day.
|
|
529
|
+
#
|
|
530
|
+
# To ensure consistent formatting, individual style attributes should
|
|
531
|
+
# not be used in the main portion of the document.
|
|
532
|
+
#
|
|
533
|
+
# Different readers of a specification will desire different formatting
|
|
534
|
+
# when reading the HTML versions of RFCs. To facilitate this, the
|
|
535
|
+
# <head> element also includes a <link> to a style sheet in the same
|
|
536
|
+
# directory as the HTML file, named "rfc-local.css". Any formatting in
|
|
537
|
+
# the linked style sheet will override the formatting in the included
|
|
538
|
+
# style sheet. For example:
|
|
539
|
+
#
|
|
540
|
+
# <style>
|
|
541
|
+
# body {}
|
|
542
|
+
# ...
|
|
543
|
+
# </style>
|
|
544
|
+
# <link rel="stylesheet" type="text/css" href="rfc-local.css">
|
|
545
|
+
|
|
546
|
+
data_dir = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data')
|
|
547
|
+
|
|
548
|
+
css = None
|
|
549
|
+
self.css_js = os.path.join(data_dir, 'xml2rfc.js')
|
|
550
|
+
if self.options.css:
|
|
551
|
+
css, cssin = self.read_css(data_dir, self.options.css)
|
|
552
|
+
if not css:
|
|
553
|
+
cssin = os.path.join(data_dir, 'xml2rfc.css')
|
|
554
|
+
with open(cssin, encoding='utf-8') as f:
|
|
555
|
+
css = f.read()
|
|
556
|
+
else:
|
|
557
|
+
jsin = os.path.splitext(cssin)[0] + '.js'
|
|
558
|
+
if os.path.exists(jsin):
|
|
559
|
+
self.css_js = jsin
|
|
560
|
+
|
|
561
|
+
if self.options.external_css:
|
|
562
|
+
cssout = os.path.join(os.path.dirname(self.filename), 'xml2rfc.css')
|
|
563
|
+
with open(cssout, 'w', encoding='utf-8') as f:
|
|
564
|
+
f.write(css)
|
|
565
|
+
add.link(head, None, href="xml2rfc.css", rel="stylesheet")
|
|
566
|
+
elif self.options.no_css:
|
|
567
|
+
pass
|
|
568
|
+
else:
|
|
569
|
+
add.style(head, None, css, type="text/css")
|
|
570
|
+
|
|
571
|
+
if self.options.rfc_local and (not self.options.pdf or os.path.exists('rfc-local.css')):
|
|
572
|
+
add.link(head, None, href="rfc-local.css", rel="stylesheet", type="text/css")
|
|
573
|
+
|
|
574
|
+
# 6.3.7. Links
|
|
575
|
+
#
|
|
576
|
+
# Each <link> element from the XML source is copied into the HTML
|
|
577
|
+
# header. Note: the HTML <link> element does not include a closing
|
|
578
|
+
# slash.
|
|
579
|
+
|
|
580
|
+
for link in x.xpath('./link'):
|
|
581
|
+
head.append(link)
|
|
582
|
+
|
|
583
|
+
body = add.body(html, None, classes='xml2rfc')
|
|
584
|
+
|
|
585
|
+
scheme = urlparse(self.options.metadata_js_url).scheme
|
|
586
|
+
if scheme in ['http', 'https', 'ftp', 'file', ]:
|
|
587
|
+
with closing(urlopen(self.options.metadata_js_url)) as f:
|
|
588
|
+
js = f.read()
|
|
589
|
+
elif scheme:
|
|
590
|
+
self.err(x, "Cannot handle scheme: %s in --metadata-js-url value" % scheme)
|
|
591
|
+
js = ''
|
|
592
|
+
else:
|
|
593
|
+
jsin = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'data', self.options.metadata_js_url)
|
|
594
|
+
with open(jsin) as f:
|
|
595
|
+
js = f.read()
|
|
596
|
+
|
|
597
|
+
if js:
|
|
598
|
+
if self.filename:
|
|
599
|
+
dest_dir = os.path.dirname(self.filename)
|
|
600
|
+
if dest_dir:
|
|
601
|
+
dest_dir += os.sep
|
|
602
|
+
jsout = urljoin(dest_dir, self.options.metadata_js_url)
|
|
603
|
+
# Only write to the destination if it's a local file:
|
|
604
|
+
if not urlparse(jsout).scheme and jsout.startswith(dest_dir):
|
|
605
|
+
if self.options.external_js:
|
|
606
|
+
try:
|
|
607
|
+
with open(jsout, 'w', encoding='utf-8') as f:
|
|
608
|
+
f.write(js)
|
|
609
|
+
except IOError as exception:
|
|
610
|
+
log.warn("Could not write to %s: %s" % (jsout, exception))
|
|
611
|
+
else:
|
|
612
|
+
add.script(head, None, js, type="application/javascript")
|
|
613
|
+
# Add external script tag -- the content might be newer than the
|
|
614
|
+
# JS we included above
|
|
615
|
+
s = add.script(body, None, src=self.options.metadata_js_url)
|
|
616
|
+
s.tail = '\n'
|
|
617
|
+
|
|
618
|
+
# 6.4. Page Headers and Footers
|
|
619
|
+
#
|
|
620
|
+
# In order to simplify printing by HTML renderers that implement
|
|
621
|
+
# [W3C.WD-css3-page-20130314], a hidden HTML <table> tag of class
|
|
622
|
+
# "ears" is added at the beginning of the HTML <body> tag, containing
|
|
623
|
+
# HTML <thead> and <tfoot> tags, each of which contains an HTML <tr>
|
|
624
|
+
# tag, which contains three HTML <td> tags with class "left", "center",
|
|
625
|
+
# and "right", respectively.
|
|
626
|
+
#
|
|
627
|
+
# The <thead> corresponds to the top of the page, the <tfoot> to the
|
|
628
|
+
# bottom. The string "[Page]" can be used as a placeholder for the
|
|
629
|
+
# page number. In practice, this must always be in the <tfoot>'s right
|
|
630
|
+
# <td>, and no control of the formatting of the page number is implied.
|
|
631
|
+
#
|
|
632
|
+
# <table class="ears">
|
|
633
|
+
# <thead>
|
|
634
|
+
# <tr>
|
|
635
|
+
# <td class="left">Internet-Draft</td>
|
|
636
|
+
# <td class="center">HTML RFC</td>
|
|
637
|
+
# <td class="right">March 2016</td>
|
|
638
|
+
# </tr>
|
|
639
|
+
# </thead>
|
|
640
|
+
# <tfoot>
|
|
641
|
+
# <tr>
|
|
642
|
+
# <td class="left">Hildebrand</td>
|
|
643
|
+
# <td class="center">Expires September 2, 2016</td>
|
|
644
|
+
# <td class="right">[Page]</td>
|
|
645
|
+
# </tr>
|
|
646
|
+
# </tfoot>
|
|
647
|
+
# </table>
|
|
648
|
+
|
|
649
|
+
body.append(
|
|
650
|
+
build.table(
|
|
651
|
+
build.thead(
|
|
652
|
+
build.tr(
|
|
653
|
+
build.td(self.page_top_left(), classes='left'),
|
|
654
|
+
build.td(self.page_top_center(), classes='center'),
|
|
655
|
+
build.td(self.page_top_right(), classes='right'),
|
|
656
|
+
),
|
|
657
|
+
),
|
|
658
|
+
build.tfoot(
|
|
659
|
+
build.tr(
|
|
660
|
+
build.td(self.page_bottom_left(), classes='left'),
|
|
661
|
+
build.td(self.page_bottom_center(), classes='center'),
|
|
662
|
+
build.td("[Page]", classes='right'),
|
|
663
|
+
),
|
|
664
|
+
),
|
|
665
|
+
classes='ears',
|
|
666
|
+
)
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
for c in [ e for e in [ x.find('front'), x.find('middle'), x.find('back') ] if e != None]:
|
|
670
|
+
self.part = c.tag
|
|
671
|
+
self.render(body, c)
|
|
672
|
+
|
|
673
|
+
with open(self.css_js, encoding='utf-8') as f:
|
|
674
|
+
js = f.read()
|
|
675
|
+
add.script(body, None, js)
|
|
676
|
+
|
|
677
|
+
return html
|
|
678
|
+
|
|
679
|
+
# 9.1. <abstract>
|
|
680
|
+
#
|
|
681
|
+
# The abstract is rendered in a similar fashion to a <section> with
|
|
682
|
+
# anchor="abstract" and <name>Abstract</name>, but without a section
|
|
683
|
+
# number.
|
|
684
|
+
#
|
|
685
|
+
# <section id="abstract">
|
|
686
|
+
# <h2><a href="#abstract" class="selfRef">Abstract</a></h2>
|
|
687
|
+
# <p id="s-abstract-1">This document defines...
|
|
688
|
+
# <a href="#s-abstract-1" class="pilcrow">¶</a>
|
|
689
|
+
# </p>
|
|
690
|
+
# </section>
|
|
691
|
+
#
|
|
692
|
+
|
|
693
|
+
def render_abstract(self, h, x):
|
|
694
|
+
if self.part == 'front':
|
|
695
|
+
section = add.section(h, x)
|
|
696
|
+
section.append( build.h2( build.a('Abstract', classes='selfRef', href="#abstract"), id="abstract"))
|
|
697
|
+
for c in x.getchildren():
|
|
698
|
+
self.render(section, c)
|
|
699
|
+
return section
|
|
700
|
+
else:
|
|
701
|
+
return None
|
|
702
|
+
|
|
703
|
+
# 9.2. <address>
|
|
704
|
+
#
|
|
705
|
+
# This element is used in the Authors' Addresses section. It is
|
|
706
|
+
# rendered as an HTML <address> tag of class "vcard". If none of the
|
|
707
|
+
# descendant XML elements has an "ascii" attribute, the <address> HTML
|
|
708
|
+
# tag includes the HTML rendering of each of the descendant XML
|
|
709
|
+
# elements. Otherwise, the <address> HTML tag includes an HTML <div>
|
|
710
|
+
# tag of class "ascii" (containing the HTML rendering of the ASCII
|
|
711
|
+
# variants of each of the descendant XML elements), an HTML <div> tag
|
|
712
|
+
# of class "alternative-contact", (containing the text "Alternate
|
|
713
|
+
# contact information:"), and an HTML <div> tag of class "non-ascii"
|
|
714
|
+
# (containing the HTML rendering of the non-ASCII variants of each of
|
|
715
|
+
# the descendant XML elements).
|
|
716
|
+
#
|
|
717
|
+
# Note: the following example shows some ASCII equivalents that are the
|
|
718
|
+
# same as their nominal equivalents for clarity; normally, the ASCII
|
|
719
|
+
# equivalents would not be included for these cases.
|
|
720
|
+
#
|
|
721
|
+
# <address class="vcard">
|
|
722
|
+
# <div class="ascii">
|
|
723
|
+
# <div class="nameRole"><span class="fn">Joe Hildebrand</span>
|
|
724
|
+
# (<span class="role">editor</span>)</div>
|
|
725
|
+
# <div class="org">Cisco Systems, Inc.</div>
|
|
726
|
+
# </div>
|
|
727
|
+
# <div class="alternative-contact">
|
|
728
|
+
# Alternate contact information:
|
|
729
|
+
# </div>
|
|
730
|
+
# <div class="non-ascii">
|
|
731
|
+
# <div class="nameRole"><span class="fn">Joe Hildebrand</span>
|
|
732
|
+
# (<span class="role">editor</span>)</div>
|
|
733
|
+
# <div class="org">Cisco Systems, Inc.</div>
|
|
734
|
+
# </div>
|
|
735
|
+
# </address>
|
|
736
|
+
|
|
737
|
+
## The above text is reasonable for author name and org, but nonsense for
|
|
738
|
+
## the <address> element. The following text will be used:
|
|
739
|
+
##
|
|
740
|
+
## The <address> element will be rendered as a sequence of <div> elements,
|
|
741
|
+
## each corresponding to a child element of <address>. Element classes
|
|
742
|
+
## will be taken from hcard, as specified on http://microformats.org/wiki/hcard
|
|
743
|
+
##
|
|
744
|
+
## <address class="vcard">
|
|
745
|
+
##
|
|
746
|
+
## <!-- ... name, role, and organization elements ... -->
|
|
747
|
+
##
|
|
748
|
+
## <div class="adr">
|
|
749
|
+
## <div class="street-address">1 Main Street</div>
|
|
750
|
+
## <div class="street-address">Suite 1</div>
|
|
751
|
+
## <div class="city-region-code">
|
|
752
|
+
## <span class="city">Denver</span>,
|
|
753
|
+
## <span class="region">CO</span>
|
|
754
|
+
## <span class="postal-code">80202</span>
|
|
755
|
+
## </div>
|
|
756
|
+
## <div class="country-name">USA</div>
|
|
757
|
+
## </div>
|
|
758
|
+
## <div class="tel">
|
|
759
|
+
## <span>Phone:</span>
|
|
760
|
+
## <a class="tel" href="tel:+1-720-555-1212">+1-720-555-1212</a>
|
|
761
|
+
## </div>
|
|
762
|
+
## <div class="fax">
|
|
763
|
+
## <span>Fax:</span>
|
|
764
|
+
## <span class="tel">+1-303-555-1212</span>
|
|
765
|
+
## </div>
|
|
766
|
+
## <div class="email">
|
|
767
|
+
## <span>Email:</span>
|
|
768
|
+
## <a class="email" href="mailto:author@example.com">author@example.com</a>
|
|
769
|
+
## </div>
|
|
770
|
+
## </address>
|
|
771
|
+
render_address = skip_renderer
|
|
772
|
+
|
|
773
|
+
# 9.3. <annotation>
|
|
774
|
+
#
|
|
775
|
+
# This element is rendered as the text ", " (a comma and a space)
|
|
776
|
+
# followed by a <span> of class "annotation" at the end of a
|
|
777
|
+
# <reference> element, the <span> containing appropriately transformed
|
|
778
|
+
# elements from the children of the <annotation> tag.
|
|
779
|
+
#
|
|
780
|
+
# <span class="annotation">Some <em>thing</em>.</span>
|
|
781
|
+
def render_annotation(self, h, x):
|
|
782
|
+
span = add.span(h, x, classes='annotation')
|
|
783
|
+
for c in x.getchildren():
|
|
784
|
+
self.render(span, c)
|
|
785
|
+
return span
|
|
786
|
+
|
|
787
|
+
# 9.4. <area>
|
|
788
|
+
#
|
|
789
|
+
# Not currently rendered to HTML.
|
|
790
|
+
#
|
|
791
|
+
|
|
792
|
+
def render_artset(self, h, x):
|
|
793
|
+
preflist = ['svg', 'binary-art', 'ascii-art', ]
|
|
794
|
+
div = add.div(h, x)
|
|
795
|
+
for t in preflist:
|
|
796
|
+
for a in x.xpath('./artwork[@type="%s"]' % t):
|
|
797
|
+
artwork = self.render(div, a)
|
|
798
|
+
return artwork
|
|
799
|
+
else:
|
|
800
|
+
artwork = self.render(div, x[0])
|
|
801
|
+
return div
|
|
802
|
+
|
|
803
|
+
# 9.5. <artwork>
|
|
804
|
+
#
|
|
805
|
+
# Artwork can consist of either inline text or SVG. If the artwork is
|
|
806
|
+
# not inside a <figure> element, a pilcrow (Section 5.2) is included.
|
|
807
|
+
# Inside a <figure> element, the figure title serves the purpose of the
|
|
808
|
+
# pilcrow. If the "align" attribute has the value "right", the CSS
|
|
809
|
+
# class "alignRight" will be added. If the "align" attribute has the
|
|
810
|
+
# value "center", the CSS class "alignCenter" will be added.
|
|
811
|
+
def render_artwork(self, h, x):
|
|
812
|
+
type = x.get('type')
|
|
813
|
+
align = x.get('align', 'left')
|
|
814
|
+
|
|
815
|
+
# 9.5.1. Text Artwork
|
|
816
|
+
#
|
|
817
|
+
# Text artwork is rendered inside an HTML <pre> element, which is
|
|
818
|
+
# contained by a <div> element for consistency with SVG artwork. Note
|
|
819
|
+
# that CDATA blocks are not a part of HTML, so angle brackets and
|
|
820
|
+
# ampersands (i.e., <, >, and &) must be escaped as <, >, and
|
|
821
|
+
# &, respectively.
|
|
822
|
+
#
|
|
823
|
+
# The <div> element will have CSS classes of "artwork", "art-text", and
|
|
824
|
+
# "art-" prepended to the value of the <artwork> element's "type"
|
|
825
|
+
# attribute, if it exists.
|
|
826
|
+
#
|
|
827
|
+
# <div class="artwork art-text art-ascii-art" id="s-1-2">
|
|
828
|
+
# <pre>
|
|
829
|
+
# ______________
|
|
830
|
+
# < hello, world >
|
|
831
|
+
# --------------
|
|
832
|
+
# \ ^__^
|
|
833
|
+
# \ (oo)\_______
|
|
834
|
+
# (__)\ )\/\
|
|
835
|
+
# ||----w |
|
|
836
|
+
# || ||
|
|
837
|
+
# </pre>
|
|
838
|
+
# <a class="pilcrow" href="#s-1-2">¶</a>
|
|
839
|
+
# </div>
|
|
840
|
+
if type not in ['svg', 'binary-art', ]:
|
|
841
|
+
if not x.text or not x.text.strip():
|
|
842
|
+
self.err(x, 'Expected ascii-art artwork for <artwork type="%s">, but found %s...' % (x.get('type',''), lxml.etree.tostring(x)[:128]))
|
|
843
|
+
return None
|
|
844
|
+
else:
|
|
845
|
+
text = x.text + ''.join([ c.tail for c in x.getchildren() ])
|
|
846
|
+
text = text.expandtabs()
|
|
847
|
+
text = '\n'.join( [ l.rstrip() for l in text.split('\n') ] )
|
|
848
|
+
pre = build.pre(text)
|
|
849
|
+
classes = 'artwork art-text align%s' % align.capitalize()
|
|
850
|
+
if type and type != 'text':
|
|
851
|
+
classes += ' art-%s' % type
|
|
852
|
+
div = add.div(h, x, pre, classes=classes)
|
|
853
|
+
div.text = None
|
|
854
|
+
if x.getparent().tag != 'figure':
|
|
855
|
+
self.maybe_add_pilcrow(div)
|
|
856
|
+
return div
|
|
857
|
+
|
|
858
|
+
# 9.5.2. SVG Artwork
|
|
859
|
+
#
|
|
860
|
+
# SVG artwork will be included inline. The SVG is wrapped in a <div>
|
|
861
|
+
# element with CSS classes "artwork" and "art-svg".
|
|
862
|
+
#
|
|
863
|
+
# If the SVG "artwork" element is a child of <figure> and the artwork
|
|
864
|
+
# is specified as align="right", an empty HTML <span> element is added
|
|
865
|
+
# directly after the <svg> element, in order to get right alignment to
|
|
866
|
+
# work correctly in HTML rendering engines that do not support the
|
|
867
|
+
# flex-box model.
|
|
868
|
+
#
|
|
869
|
+
# Note: the "alt" attribute of <artwork> is not currently used for SVG;
|
|
870
|
+
# instead, the <title> and <desc> tags are used in the SVG.
|
|
871
|
+
#
|
|
872
|
+
# <div class="artwork art-svg" id="s-2-17">
|
|
873
|
+
# <svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
|
|
874
|
+
# <desc>Alt text here</desc>
|
|
875
|
+
# <circle
|
|
876
|
+
# cx="50" cy="50" r="40"
|
|
877
|
+
# stroke="green" stroke-width="4" fill="yellow" />
|
|
878
|
+
# </svg>
|
|
879
|
+
# <a href="#s-2-17" class="pilcrow">¶</a>
|
|
880
|
+
# </div>
|
|
881
|
+
elif type == 'svg':
|
|
882
|
+
classes = 'artwork art-svg align%s' % align.capitalize()
|
|
883
|
+
div = add.div(h, x, classes=classes)
|
|
884
|
+
div.text = None
|
|
885
|
+
src = x.get('src')
|
|
886
|
+
if src:
|
|
887
|
+
svgfile = x.get('originalSrc') or x.get('src')[:37]+' ...'
|
|
888
|
+
if not src.startswith('data:'):
|
|
889
|
+
self.err(x, "Internal error: Got an <artwork> src: attribute that did not start with 'data:' after prepping")
|
|
890
|
+
try:
|
|
891
|
+
with closing(urlopen(src)) as f:
|
|
892
|
+
data = f.read()
|
|
893
|
+
svg = lxml.etree.fromstring(data)
|
|
894
|
+
except IOError as e:
|
|
895
|
+
self.err(x, str(e))
|
|
896
|
+
svg = None
|
|
897
|
+
else:
|
|
898
|
+
svg = x.find('svg:svg', namespaces=namespaces)
|
|
899
|
+
if svg != None:
|
|
900
|
+
svgfile = "inline:%s ..." % lxml.etree.tostring(svg)[:31]
|
|
901
|
+
if svg == None:
|
|
902
|
+
self.err(x, 'Expected <artwork> with type="svg" to have an <svg> child element, but did not find it:\n %s ...' % (lxml.etree.tostring(x)[:256], ))
|
|
903
|
+
return None
|
|
904
|
+
# For w3.org validator compatibility
|
|
905
|
+
if svg.get('attribute', None):
|
|
906
|
+
del svg.attrib['version']
|
|
907
|
+
|
|
908
|
+
if self.options.pdf:
|
|
909
|
+
# Correct font families
|
|
910
|
+
font_family = svg.get('font-family')
|
|
911
|
+
svg.set('font-family', self.get_font_family(font_family))
|
|
912
|
+
svg = self.set_font_family(svg)
|
|
913
|
+
|
|
914
|
+
#
|
|
915
|
+
# Deal with possible svg scaling issues.
|
|
916
|
+
vbox = svg.get('viewBox')
|
|
917
|
+
svgw = maybefloat(svg.get('width'))
|
|
918
|
+
svgh = maybefloat(svg.get('height'))
|
|
919
|
+
if svgw or svgh:
|
|
920
|
+
self.warn(x, "Found SVG with width or height specified, which will make the artwork not scale. Specify a viewBox only to let the artwork scale.")
|
|
921
|
+
try:
|
|
922
|
+
if vbox:
|
|
923
|
+
xo,yo,w,h = re.split(',? +', vbox.strip('()'))
|
|
924
|
+
# rewrite viewbox in the simplest syntax, in case needed for pdf lib
|
|
925
|
+
svg.set('viewBox', '%s %s %s %s' % (xo,yo,w,h))
|
|
926
|
+
if not (svgw and svgh):
|
|
927
|
+
svgw = float(w)-float(xo)
|
|
928
|
+
svgh = float(h)-float(yo)
|
|
929
|
+
else:
|
|
930
|
+
if svgw and svgh:
|
|
931
|
+
svg.set('viewBox', '0 0 %s %s' % (svgw, svgh))
|
|
932
|
+
else:
|
|
933
|
+
self.err(x, "Cannot place SVG properly when neither viewBox nor width and height is available")
|
|
934
|
+
return None
|
|
935
|
+
except ValueError as e:
|
|
936
|
+
self.err(x, "Error when calculating SVG size: %s" % e)
|
|
937
|
+
imgw = 660 if self.options.image_svg else 724
|
|
938
|
+
if imgw < svgw and svg.get('width') == None:
|
|
939
|
+
# maybe issue a warning here?
|
|
940
|
+
pass
|
|
941
|
+
#
|
|
942
|
+
if self.options.image_svg:
|
|
943
|
+
if not svg.get('width'):
|
|
944
|
+
svg.set('width', "%s"%svgw) # Needed by the PDF renderer for proper scaling
|
|
945
|
+
data = build_dataurl('image/svg+xml', lxml.etree.tostring(svg))
|
|
946
|
+
add.img(div, None, src=data, alt=x.get('alt'))
|
|
947
|
+
else:
|
|
948
|
+
div.append(svg)
|
|
949
|
+
if x.getparent().tag != 'figure':
|
|
950
|
+
self.maybe_add_pilcrow(div)
|
|
951
|
+
else:
|
|
952
|
+
if x.get('align') == 'right':
|
|
953
|
+
add.span(div, None)
|
|
954
|
+
#
|
|
955
|
+
dups = set(find_duplicate_html_ids(self.html_root))
|
|
956
|
+
new = dups - self.duplicate_html_ids
|
|
957
|
+
for attr, id, e in new:
|
|
958
|
+
self.warn(x, 'Duplicate attribute %s="%s" found after including svg from %s. This can cause problems with some browsers.' % (attr, id, svgfile))
|
|
959
|
+
self.duplicate_html_ids = self.duplicate_html_ids | dups
|
|
960
|
+
|
|
961
|
+
# 9.5.3. Other Artwork
|
|
962
|
+
#
|
|
963
|
+
# Other artwork will have a "src" attribute that uses the "data" URI
|
|
964
|
+
# scheme defined in [RFC2397]. Such artwork is rendered in an HTML
|
|
965
|
+
# <img> element. Note: the HTML <img> element does not have a closing
|
|
966
|
+
# slash.
|
|
967
|
+
#
|
|
968
|
+
# Note: such images are not yet allowed in RFCs even though the format
|
|
969
|
+
# supports them. A limited set of "data:" mediatypes for artwork may
|
|
970
|
+
# be allowed in the future.
|
|
971
|
+
#
|
|
972
|
+
# <div class="artwork art-logo" id="s-2-58">
|
|
973
|
+
# <img alt="IETF logo"
|
|
974
|
+
# src="data:image/gif;charset=utf-8;base64,...">
|
|
975
|
+
# <a class="pilcrow" href="#s-2-58">¶</a>
|
|
976
|
+
# </div>
|
|
977
|
+
elif type == 'binary-art':
|
|
978
|
+
if self.options.rfc:
|
|
979
|
+
self.err(x, "Found <artwork> with type='binary-art'. This is not currently supported in RFCs.")
|
|
980
|
+
else:
|
|
981
|
+
self.warn(x, "Found <artwork> with type='binary-art'. Note that this is not currently supported in RFCs.")
|
|
982
|
+
div = add.div(h, x, classes='artwork art-binary')
|
|
983
|
+
data = x.get('src')
|
|
984
|
+
if data:
|
|
985
|
+
add.img(div, None, src=data)
|
|
986
|
+
else:
|
|
987
|
+
self.err(x, 'Expected <img> data given by src="" for <artwork type="binary-art">, but did not find it: %s ...' % (lxml.etree.tostring(x)[:128], ))
|
|
988
|
+
if x.getparent().tag != 'figure':
|
|
989
|
+
self.maybe_add_pilcrow(div)
|
|
990
|
+
|
|
991
|
+
# 9.6. <aside>
|
|
992
|
+
#
|
|
993
|
+
# This element is rendered as an HTML <aside> element, with all child
|
|
994
|
+
# content appropriately transformed.
|
|
995
|
+
#
|
|
996
|
+
# <aside id="s-2.1-2">
|
|
997
|
+
# <p id="s-2.1-2.1">
|
|
998
|
+
# A little more than kin, and less than kind.
|
|
999
|
+
# <a class="pilcrow" href="#s-2.1-2.1">¶</a>
|
|
1000
|
+
# </p>
|
|
1001
|
+
# </aside>
|
|
1002
|
+
render_aside = default_renderer
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
#
|
|
1006
|
+
# 9.7. <author>
|
|
1007
|
+
#
|
|
1008
|
+
# The <author> element is used in several places in the output.
|
|
1009
|
+
# Different rendering is used for each.
|
|
1010
|
+
def render_author(self, h, x):
|
|
1011
|
+
if len(x)==0 and len(x.attrib)==0:
|
|
1012
|
+
return None
|
|
1013
|
+
p = x.getparent()
|
|
1014
|
+
|
|
1015
|
+
# 9.7.1. Authors in Document Information
|
|
1016
|
+
#
|
|
1017
|
+
# As seen in the Document Information at the beginning of the HTML,
|
|
1018
|
+
# each document author is rendered as an HTML <div> tag of class
|
|
1019
|
+
# "author".
|
|
1020
|
+
#
|
|
1021
|
+
# Inside the <div class="author"> HTML tag, the author's initials and
|
|
1022
|
+
# surname (or the fullname, if it exists and the others do not) will be
|
|
1023
|
+
# rendered in an HTML <div> tag of class "author-name". If the
|
|
1024
|
+
# <author> contains "asciiInitials" and "asciiSurname" attributes, or
|
|
1025
|
+
# contains as "asciiFullname" attribute, the author's name is rendered
|
|
1026
|
+
# twice, with the first being the non-ASCII version, wrapped in an HTML
|
|
1027
|
+
# <span> tag of class "non-ascii", followed by the ASCII version
|
|
1028
|
+
# wrapped in an HTML <span> tag of class "ascii", wrapped in
|
|
1029
|
+
# parentheses. If the <author> has a "role" attribute of "editor", the
|
|
1030
|
+
# <div class="author-name"> will also contain the text ", " (comma,
|
|
1031
|
+
# space), followed by an HTML <span> tag of class "editor", which
|
|
1032
|
+
# contains the text "Ed.".
|
|
1033
|
+
#
|
|
1034
|
+
# If the <author> element contains an <organization> element, it is
|
|
1035
|
+
# also rendered inside the <div class="author"> HTML tag.
|
|
1036
|
+
#
|
|
1037
|
+
# <div class="author">
|
|
1038
|
+
# <div class="author-name">
|
|
1039
|
+
# H. Flanagan,
|
|
1040
|
+
# <span class="editor">Ed.</span></div>
|
|
1041
|
+
# <div class="org">Test Org</div>
|
|
1042
|
+
# </div>
|
|
1043
|
+
# <div class="author">
|
|
1044
|
+
# <div class="author-name">
|
|
1045
|
+
# <span class="non-ascii">Hildebrand</span>
|
|
1046
|
+
# (<span class="ascii">HILDEBRAND</span>)
|
|
1047
|
+
# </div>
|
|
1048
|
+
# <div class="org">
|
|
1049
|
+
# <span class="non-ascii">Test Org</span>
|
|
1050
|
+
# (<span class="ascii">TEST ORG</span>)
|
|
1051
|
+
# </div>
|
|
1052
|
+
# </div>
|
|
1053
|
+
if self.part == 'front':
|
|
1054
|
+
name, ascii = short_author_name_set(x)
|
|
1055
|
+
role = short_author_role(x)
|
|
1056
|
+
div = add.div(h, x, classes='author')
|
|
1057
|
+
if role:
|
|
1058
|
+
role = build.span(role, classes=x.get('role'))
|
|
1059
|
+
if name:
|
|
1060
|
+
div.append(wrap_ascii('div', name, ascii, role, classes='author-name'))
|
|
1061
|
+
o = x.find('organization')
|
|
1062
|
+
if o != None and o.get('showOnFrontPage') == 'true':
|
|
1063
|
+
org, ascii = short_org_name_set(x)
|
|
1064
|
+
if org:
|
|
1065
|
+
div.append(wrap_ascii('div', org, ascii, None, classes='org'))
|
|
1066
|
+
return div
|
|
1067
|
+
|
|
1068
|
+
# New handling for inline <contact>
|
|
1069
|
+
elif p.tag == 't':
|
|
1070
|
+
name, ascii = full_author_name_set(x)
|
|
1071
|
+
span = wrap_ascii('span', name, ascii, '', classes='contact-name')
|
|
1072
|
+
span.tail = x.tail
|
|
1073
|
+
h.append(span)
|
|
1074
|
+
return span
|
|
1075
|
+
|
|
1076
|
+
# 9.7.2. Authors of This Document
|
|
1077
|
+
#
|
|
1078
|
+
# As seen in the Authors' Addresses section, at the end of the HTML,
|
|
1079
|
+
# each document author is rendered into an HTML <address> element with
|
|
1080
|
+
# the CSS class "vcard".
|
|
1081
|
+
#
|
|
1082
|
+
# The HTML <address> element will contain an HTML <div> with CSS class
|
|
1083
|
+
# "nameRole". That div will contain an HTML <span> element with CSS
|
|
1084
|
+
# class "fn" containing the value of the "fullname" attribute of the
|
|
1085
|
+
# <author> XML element and an HTML <span> element with CSS class "role"
|
|
1086
|
+
# containing the value of the "role" attribute of the <author> XML
|
|
1087
|
+
# element (if there is a role). Parentheses will surround the <span
|
|
1088
|
+
# class="role">, if it exists.
|
|
1089
|
+
#
|
|
1090
|
+
# <address class="vcard">
|
|
1091
|
+
# <div class="nameRole">
|
|
1092
|
+
# <span class="fn">Joe Hildebrand</span>
|
|
1093
|
+
# (<span class="role">editor</span>)
|
|
1094
|
+
# </div>
|
|
1095
|
+
# ...
|
|
1096
|
+
#
|
|
1097
|
+
# After the name, the <organization> and <address> child elements of
|
|
1098
|
+
# the author are rendered inside the HTML <address> tag.
|
|
1099
|
+
#
|
|
1100
|
+
# When the <author> element, or any of its descendant elements, has any
|
|
1101
|
+
# attribute that starts with "ascii", all of the author information is
|
|
1102
|
+
# displayed twice. The first version is wrapped in an HTML <div> tag
|
|
1103
|
+
# with class "ascii"; this version prefers the ASCII version of
|
|
1104
|
+
# information, such as "asciiFullname", but falls back on the non-ASCII
|
|
1105
|
+
# version if the ASCII version doesn't exist. The second version is
|
|
1106
|
+
# wrapped in an HTML <div> tag with class "non-ascii"; this version
|
|
1107
|
+
# prefers the non-ASCII version of information, such as "fullname", but
|
|
1108
|
+
# falls back on the ASCII version if the non-ASCII version does not
|
|
1109
|
+
# exist. Between these two HTML <div>s, a third <div> is inserted,
|
|
1110
|
+
# with class "alternative-contact", containing the text "Alternate
|
|
1111
|
+
# contact information:".
|
|
1112
|
+
#
|
|
1113
|
+
# <address class="vcard">
|
|
1114
|
+
# <div class="ascii">
|
|
1115
|
+
# <div class="nameRole">
|
|
1116
|
+
# <span class="fn">The ASCII name</span>
|
|
1117
|
+
# </div>
|
|
1118
|
+
# </div>
|
|
1119
|
+
# <div class="alternative-contact">
|
|
1120
|
+
# Alternate contact information:
|
|
1121
|
+
# </div>
|
|
1122
|
+
# <div class="non-ascii">
|
|
1123
|
+
# <div class="nameRole">
|
|
1124
|
+
# <span class="fn">The non-ASCII name</span>
|
|
1125
|
+
# (<span class="role">editor</span>)
|
|
1126
|
+
# </div>
|
|
1127
|
+
# </div>
|
|
1128
|
+
# </address>
|
|
1129
|
+
elif self.part == 'back' or p.tag == 'section':
|
|
1130
|
+
# ascii will be set only if name has codepoints not in the Latin script blocks
|
|
1131
|
+
name, ascii = full_author_name_set(x)
|
|
1132
|
+
#
|
|
1133
|
+
addr = add.address(h, x, classes='vcard')
|
|
1134
|
+
#
|
|
1135
|
+
address = x.find('./address')
|
|
1136
|
+
postal = x.find('./address/postal')
|
|
1137
|
+
if address is None:
|
|
1138
|
+
address = lxml.etree.Element('address')
|
|
1139
|
+
x.append(address)
|
|
1140
|
+
if postal is None:
|
|
1141
|
+
# We render author name as part of postal, so make sure it's there
|
|
1142
|
+
address.insert(0, lxml.etree.Element('postal'))
|
|
1143
|
+
if ascii:
|
|
1144
|
+
ascii_div = add.div(addr, None, classes='ascii')
|
|
1145
|
+
for c in x.getchildren():
|
|
1146
|
+
self.render(ascii_div, c)
|
|
1147
|
+
add.div(addr, None, 'Additional contact information:', classes='alternative-contact')
|
|
1148
|
+
nonasc_div = add.div(addr, None, classes='non-ascii')
|
|
1149
|
+
for c in x.getchildren():
|
|
1150
|
+
self.render(nonasc_div, c)
|
|
1151
|
+
else:
|
|
1152
|
+
for c in x.getchildren():
|
|
1153
|
+
self.render(addr, c)
|
|
1154
|
+
return addr
|
|
1155
|
+
|
|
1156
|
+
# 9.7.3. Authors of References
|
|
1157
|
+
#
|
|
1158
|
+
# In the output generated from a reference element, author tags are
|
|
1159
|
+
# rendered inside an HTML <span> element with CSS class "refAuthor".
|
|
1160
|
+
# See Section 4.8.6.2 of [RFC7322] for guidance on how author names are
|
|
1161
|
+
# to appear.
|
|
1162
|
+
#
|
|
1163
|
+
# <span class="refAuthor">Flanagan, H.</span> and
|
|
1164
|
+
# <span class="refAuthor">N. Brownlee</span>
|
|
1165
|
+
elif self.part == 'references':
|
|
1166
|
+
prev = x.getprevious()
|
|
1167
|
+
if prev.tag == 'author':
|
|
1168
|
+
prev_name = ref_author_name_first(prev)[0]
|
|
1169
|
+
else:
|
|
1170
|
+
prev = None
|
|
1171
|
+
prev_name = ''
|
|
1172
|
+
|
|
1173
|
+
next = x.getnext()
|
|
1174
|
+
if next is not None and next.tag == 'author':
|
|
1175
|
+
after_next = next.getnext()
|
|
1176
|
+
if after_next is not None and after_next.tag != 'author':
|
|
1177
|
+
after_next = None
|
|
1178
|
+
else:
|
|
1179
|
+
next = None
|
|
1180
|
+
after_next = None
|
|
1181
|
+
|
|
1182
|
+
role = short_author_role(x)
|
|
1183
|
+
|
|
1184
|
+
# I do not know why "prev_name == ''" is a condition only for prev, but keeping behavior
|
|
1185
|
+
is_first = (prev is None) or (prev_name == '')
|
|
1186
|
+
is_last = next is None
|
|
1187
|
+
is_second_to_last = (not is_last) and (after_next is None)
|
|
1188
|
+
|
|
1189
|
+
# Choose name format.
|
|
1190
|
+
if is_last and not is_first:
|
|
1191
|
+
# This is the last author in a list. Use the "last author" name format.
|
|
1192
|
+
name, ascii = ref_author_name_last(x)
|
|
1193
|
+
else:
|
|
1194
|
+
# All other cases use the "first author" name format.
|
|
1195
|
+
name, ascii = ref_author_name_first(x)
|
|
1196
|
+
# Get a span Element.
|
|
1197
|
+
span = wrap_ascii('span', name, ascii, role, classes='refAuthor')
|
|
1198
|
+
|
|
1199
|
+
# Now determine whether the span should be trailed by a conjunction.
|
|
1200
|
+
tail = span.tail or ''
|
|
1201
|
+
if not is_last:
|
|
1202
|
+
# This is not the last author in the list. A conjunction may be needed.
|
|
1203
|
+
if not is_second_to_last:
|
|
1204
|
+
# This is not last and not second-to-last, so insert a comma.
|
|
1205
|
+
tail += ', '
|
|
1206
|
+
else:
|
|
1207
|
+
# This is the second-to-last author, so 'and' is needed. May also need a comma.
|
|
1208
|
+
if is_first:
|
|
1209
|
+
# Both first and second-to-last means only two authors. No comma needed.
|
|
1210
|
+
tail += ' and '
|
|
1211
|
+
else:
|
|
1212
|
+
# There must be at least 3 authors, so include a comma.
|
|
1213
|
+
tail += ', and '
|
|
1214
|
+
span.tail = tail
|
|
1215
|
+
|
|
1216
|
+
if ''.join(span.itertext()).strip():
|
|
1217
|
+
h.append(span)
|
|
1218
|
+
return span
|
|
1219
|
+
|
|
1220
|
+
else:
|
|
1221
|
+
self.err(x, "Did not expect to be asked to render <%s> while in <%s>" % (x.tag, x.getparent().tag))
|
|
1222
|
+
|
|
1223
|
+
render_contact = render_author
|
|
1224
|
+
|
|
1225
|
+
# 9.8. <back>
|
|
1226
|
+
#
|
|
1227
|
+
# If there is exactly one <references> child, render that child in a
|
|
1228
|
+
# similar way to a <section>. If there are more than one <references>
|
|
1229
|
+
# children, render as a <section> whose name is "References",
|
|
1230
|
+
# containing a <section> for each <references> child.
|
|
1231
|
+
#
|
|
1232
|
+
# After any <references> sections, render each <section> child of
|
|
1233
|
+
# <back> as an appendix.
|
|
1234
|
+
#
|
|
1235
|
+
# <section id="n-references">
|
|
1236
|
+
# <h2 id="s-2">
|
|
1237
|
+
# <a class="selfRef" href="#s-2">2.</a>
|
|
1238
|
+
# <a class="selfRef" href="#n-references">References</a>
|
|
1239
|
+
# </h2>
|
|
1240
|
+
# <section id="n-normative">
|
|
1241
|
+
# <h3 id="s-2.1">
|
|
1242
|
+
# <a class="selfRef" href="#s-2.1">2.1.</a>
|
|
1243
|
+
# <a class="selfRef" href="#n-normative">Normative</a>
|
|
1244
|
+
# </h3>
|
|
1245
|
+
# <dl class="reference"></dl>
|
|
1246
|
+
# </section>
|
|
1247
|
+
# <section id="n-informational">
|
|
1248
|
+
# <h3 id="s-2.2">
|
|
1249
|
+
# <a class="selfRef" href="#s-2.2">2.2.</a>
|
|
1250
|
+
# <a class="selfRef" href="#n-informational">Informational</a>
|
|
1251
|
+
# </h3>
|
|
1252
|
+
# <dl class="reference"></dl>
|
|
1253
|
+
# </section>
|
|
1254
|
+
# </section>
|
|
1255
|
+
# <section id="n-unimportant">
|
|
1256
|
+
# <h2 id="s-A">
|
|
1257
|
+
# <a class="selfRef" href="#s-A">Appendix A.</a>
|
|
1258
|
+
# <a class="selfRef" href="#n-unimportant">Unimportant</a>
|
|
1259
|
+
# </h2>
|
|
1260
|
+
# </section>
|
|
1261
|
+
render_back = skip_renderer
|
|
1262
|
+
|
|
1263
|
+
# 9.9. <bcp14>
|
|
1264
|
+
#
|
|
1265
|
+
# This element marks up words like MUST and SHOULD [BCP14] with an HTML
|
|
1266
|
+
# <span> element with the CSS class "bcp14".
|
|
1267
|
+
#
|
|
1268
|
+
# You <span class="bcp14">MUST</span> be joking.
|
|
1269
|
+
def render_bcp14(self, h, x):
|
|
1270
|
+
return add.span(h, x, classes='bcp14')
|
|
1271
|
+
|
|
1272
|
+
# 9.10. <blockquote>
|
|
1273
|
+
#
|
|
1274
|
+
# This element renders in a way similar to the HTML <blockquote>
|
|
1275
|
+
# element. If there is a "cite" attribute, it is copied to the HTML
|
|
1276
|
+
# "cite" attribute. If there is a "quoteFrom" attribute, it is placed
|
|
1277
|
+
# inside a <cite> element at the end of the quote, with an <a> element
|
|
1278
|
+
# surrounding it (if there is a "cite" attribute), linking to the cited
|
|
1279
|
+
# URL.
|
|
1280
|
+
#
|
|
1281
|
+
# If the <blockquote> does not contain another element that gets a
|
|
1282
|
+
# pilcrow (Section 5.2), a pilcrow is added.
|
|
1283
|
+
#
|
|
1284
|
+
# Note that the "—" at the beginning of the <cite> element should
|
|
1285
|
+
# be a proper emdash, which is difficult to show in the display of the
|
|
1286
|
+
# current format.
|
|
1287
|
+
#
|
|
1288
|
+
# <blockquote id="s-1.2-1"
|
|
1289
|
+
# cite="http://...">
|
|
1290
|
+
# <p id="s-1.2-2">Four score and seven years ago our fathers
|
|
1291
|
+
# brought forth on this continent, a new nation, conceived
|
|
1292
|
+
# in Liberty, and dedicated to the proposition that all men
|
|
1293
|
+
# are created equal.
|
|
1294
|
+
# <a href="#s-1.2-2" class="pilcrow">¶</a>
|
|
1295
|
+
# </p>
|
|
1296
|
+
# <cite>— <a href="http://...">Abraham Lincoln</a></cite>
|
|
1297
|
+
# </blockquote>
|
|
1298
|
+
def render_blockquote(self, h, x):
|
|
1299
|
+
frm = x.get('quotedFrom')
|
|
1300
|
+
cite = x.get('cite')
|
|
1301
|
+
quote = add.blockquote(h, x)
|
|
1302
|
+
if cite:
|
|
1303
|
+
quote.set('cite', cite)
|
|
1304
|
+
for c in x.getchildren():
|
|
1305
|
+
self.render(quote, c)
|
|
1306
|
+
self.maybe_add_pilcrow(quote)
|
|
1307
|
+
if frm:
|
|
1308
|
+
if cite:
|
|
1309
|
+
frm = build.a(frm, href=cite)
|
|
1310
|
+
add.cite(quote, None, mdash, ' ', frm)
|
|
1311
|
+
return quote
|
|
1312
|
+
|
|
1313
|
+
# 9.11. <boilerplate>
|
|
1314
|
+
#
|
|
1315
|
+
# The Status of This Memo and the Copyright statement, together
|
|
1316
|
+
# commonly referred to as the document boilerplate, appear after the
|
|
1317
|
+
# Abstract. The children of the input <boilerplate> element are
|
|
1318
|
+
# treated in a similar fashion to unnumbered sections.
|
|
1319
|
+
#
|
|
1320
|
+
# <section id="status-of-this-memo">
|
|
1321
|
+
# <h2 id="s-boilerplate-1">
|
|
1322
|
+
# <a href="#status-of-this-memo" class="selfRef">
|
|
1323
|
+
# Status of this Memo</a>
|
|
1324
|
+
# </h2>
|
|
1325
|
+
# <p id="s-boilerplate-1-1">This Internet-Draft is submitted in full
|
|
1326
|
+
# conformance with the provisions of BCP 78 and BCP 79.
|
|
1327
|
+
# <a href="#s-boilerplate-1-1" class="pilcrow">¶</a>
|
|
1328
|
+
# </p>
|
|
1329
|
+
# ...
|
|
1330
|
+
render_boilerplate = skip_renderer
|
|
1331
|
+
|
|
1332
|
+
# 9.12. <br>
|
|
1333
|
+
#
|
|
1334
|
+
# This element is directly rendered as its HTML counterpart. Note: in
|
|
1335
|
+
# HTML, <br> does not have a closing slash.
|
|
1336
|
+
## Removed from schema
|
|
1337
|
+
def render_br(self, h, x):
|
|
1338
|
+
return add.br(h, x)
|
|
1339
|
+
|
|
1340
|
+
#
|
|
1341
|
+
# 9.13. <city>
|
|
1342
|
+
#
|
|
1343
|
+
# This element is rendered as a <span> element with CSS class
|
|
1344
|
+
# "locality".
|
|
1345
|
+
#
|
|
1346
|
+
# <span class="locality">Guilford</span>
|
|
1347
|
+
def render_city(self, h, x):
|
|
1348
|
+
return self.address_line_renderer(h, x, classes='locality')
|
|
1349
|
+
|
|
1350
|
+
def render_cityarea(self, h, x):
|
|
1351
|
+
return self.address_line_renderer(h, x, classes='locality')
|
|
1352
|
+
|
|
1353
|
+
#
|
|
1354
|
+
# 9.14. <code>
|
|
1355
|
+
#
|
|
1356
|
+
# This element is rendered as a <span> element with CSS class "postal-
|
|
1357
|
+
# code".
|
|
1358
|
+
#
|
|
1359
|
+
# <span class="postal-code">GU16 7HF<span>
|
|
1360
|
+
def render_code(self, h, x):
|
|
1361
|
+
return self.address_line_renderer(h, x, classes='postal-code')
|
|
1362
|
+
|
|
1363
|
+
def render_sortingcode(self, h, x):
|
|
1364
|
+
return self.address_line_renderer(h, x, classes='postal-code')
|
|
1365
|
+
|
|
1366
|
+
# 9.15. <country>
|
|
1367
|
+
#
|
|
1368
|
+
# This element is rendered as a <div> element with CSS class "country-
|
|
1369
|
+
# name".
|
|
1370
|
+
#
|
|
1371
|
+
# <div class="country-name">England</div>
|
|
1372
|
+
def render_country(self, h, x):
|
|
1373
|
+
return self.address_line_renderer(h, x, classes='country-name')
|
|
1374
|
+
|
|
1375
|
+
# 9.16. <cref>
|
|
1376
|
+
#
|
|
1377
|
+
# This element is rendered as a <span> element with CSS class "cref".
|
|
1378
|
+
# Any anchor is copied to the "id" attribute. If there is a source
|
|
1379
|
+
# given, it is contained inside the "cref" <span> element with another
|
|
1380
|
+
# <span> element of class "crefSource".
|
|
1381
|
+
#
|
|
1382
|
+
# <span class="cref" id="crefAnchor">Just a brief comment
|
|
1383
|
+
# about something that we need to remember later.
|
|
1384
|
+
# <span class="crefSource">--life</span></span>
|
|
1385
|
+
def render_cref(self, h, x):
|
|
1386
|
+
disp = x.get('display') == 'true'
|
|
1387
|
+
if disp:
|
|
1388
|
+
span = add.span(h, x, classes='cref')
|
|
1389
|
+
for c in x.getchildren():
|
|
1390
|
+
self.render(span, c)
|
|
1391
|
+
source = x.get('source')
|
|
1392
|
+
if source:
|
|
1393
|
+
add.span(span, None, source, classes='crefSource')
|
|
1394
|
+
return span
|
|
1395
|
+
|
|
1396
|
+
# 9.17. <date>
|
|
1397
|
+
#
|
|
1398
|
+
# This element is rendered as the HTML <time> element. If the "year",
|
|
1399
|
+
# "month", or "day" attribute is included on the XML element, an
|
|
1400
|
+
# appropriate "datetime" element will be generated in HTML.
|
|
1401
|
+
#
|
|
1402
|
+
# If this date is a child of the document's <front> element, it gets
|
|
1403
|
+
# the CSS class "published".
|
|
1404
|
+
#
|
|
1405
|
+
# If this date is inside a <reference> element, it gets the CSS class
|
|
1406
|
+
# "refDate".
|
|
1407
|
+
#
|
|
1408
|
+
# <time datetime="2014-10" class="published">October 2014</time>
|
|
1409
|
+
def render_date(self, h, x):
|
|
1410
|
+
have_date = x.get('day') or x.get('month') or x.get('year')
|
|
1411
|
+
year, month, day = extract_date(x, self.options.date)
|
|
1412
|
+
p = x.getparent()
|
|
1413
|
+
if p==None or p.getparent().tag != 'reference':
|
|
1414
|
+
# don't touch the given date if we're rendering a reference
|
|
1415
|
+
year, month, day = augment_date(year, month, day, self.options.date)
|
|
1416
|
+
text = format_date(year, month, day, legacy=self.options.legacy_date_format)
|
|
1417
|
+
datetime = format_date_iso(year, month, day) if have_date else None
|
|
1418
|
+
if x.text and have_date:
|
|
1419
|
+
text = "%s (%s)" % (x.text, text)
|
|
1420
|
+
elif x.text:
|
|
1421
|
+
text = x.text
|
|
1422
|
+
else:
|
|
1423
|
+
# text = text
|
|
1424
|
+
pass
|
|
1425
|
+
if datetime:
|
|
1426
|
+
time = add.time(h, x, text, datetime=datetime)
|
|
1427
|
+
if x.getparent() == self.root.find('front'):
|
|
1428
|
+
time.set('class', 'published')
|
|
1429
|
+
elif list(x.iterancestors('reference')):
|
|
1430
|
+
time.set('class', 'refDate')
|
|
1431
|
+
elif text:
|
|
1432
|
+
time = add.span(h, x, text)
|
|
1433
|
+
else:
|
|
1434
|
+
time = ''
|
|
1435
|
+
return time
|
|
1436
|
+
|
|
1437
|
+
# 9.18. <dd>
|
|
1438
|
+
#
|
|
1439
|
+
# This element is directly rendered as its HTML counterpart.
|
|
1440
|
+
def render_dd(self, h, x):
|
|
1441
|
+
indent = x.getparent().get('indent') or '3'
|
|
1442
|
+
style = 'margin-left: %.1fem' % (int(indent)*0.5) if indent and indent!="0" else None
|
|
1443
|
+
dd = add.dd(h, x, style=style)
|
|
1444
|
+
for c in x.getchildren():
|
|
1445
|
+
self.render(dd, c)
|
|
1446
|
+
self.maybe_add_pilcrow(dd)
|
|
1447
|
+
# workaround for weasyprint's unwillingness to break between <dd> and
|
|
1448
|
+
# <dt>: add an extra <dd> that is very prone to page breaks. See CSS.
|
|
1449
|
+
add.dd(h, None, classes='break')
|
|
1450
|
+
return dd
|
|
1451
|
+
|
|
1452
|
+
# 9.19. <displayreference>
|
|
1453
|
+
#
|
|
1454
|
+
# This element does not affect the HTML output, but it is used in the
|
|
1455
|
+
# generation of the <reference>, <referencegroup>, <relref>, and <xref>
|
|
1456
|
+
# elements.
|
|
1457
|
+
render_displayreference = null_renderer
|
|
1458
|
+
|
|
1459
|
+
# 9.20. <dl>
|
|
1460
|
+
#
|
|
1461
|
+
# This element is directly rendered as its HTML counterpart.
|
|
1462
|
+
#
|
|
1463
|
+
# If the hanging attribute is "false", add the "dlParallel" class, else
|
|
1464
|
+
# add the "dlHanging" class.
|
|
1465
|
+
#
|
|
1466
|
+
# If the spacing attribute is "compact", add the "dlCompact" class.
|
|
1467
|
+
def render_dl(self, h, x):
|
|
1468
|
+
newline = x.get('newline')
|
|
1469
|
+
spacing = x.get('spacing')
|
|
1470
|
+
classes = []
|
|
1471
|
+
if newline == 'true':
|
|
1472
|
+
classes.append('dlNewline')
|
|
1473
|
+
elif newline == 'false':
|
|
1474
|
+
classes.append('dlParallel')
|
|
1475
|
+
if spacing == 'compact':
|
|
1476
|
+
classes.append('dlCompact')
|
|
1477
|
+
classes = ' '.join(classes)
|
|
1478
|
+
# workaround for weasyprint's unwillingness to break between <dd> and
|
|
1479
|
+
# <dt>: add an extra <dd> that is very prone to page breaks. See CSS.
|
|
1480
|
+
add.span(h, None, classes='break')
|
|
1481
|
+
dl = add.dl(h, x, classes=classes)
|
|
1482
|
+
for c in x.getchildren():
|
|
1483
|
+
self.render(dl, c)
|
|
1484
|
+
return dl
|
|
1485
|
+
|
|
1486
|
+
# 9.21. <dt>
|
|
1487
|
+
#
|
|
1488
|
+
# This element is directly rendered as its HTML counterpart.
|
|
1489
|
+
render_dt = default_renderer
|
|
1490
|
+
|
|
1491
|
+
#
|
|
1492
|
+
# 9.22. <em>
|
|
1493
|
+
#
|
|
1494
|
+
# This element is directly rendered as its HTML counterpart.
|
|
1495
|
+
render_em = default_renderer
|
|
1496
|
+
|
|
1497
|
+
# 9.23. <email>
|
|
1498
|
+
#
|
|
1499
|
+
# This element is rendered as an HTML <div> containing the string
|
|
1500
|
+
# "Email:" and an HTML <a> element with the "href" attribute set to the
|
|
1501
|
+
# equivalent "mailto:" URI, a CSS class of "email", and the contents
|
|
1502
|
+
# set to the email address. If this is the version of the address with
|
|
1503
|
+
# ASCII, the "ascii" attribute is preferred to the element text.
|
|
1504
|
+
#
|
|
1505
|
+
# <div>
|
|
1506
|
+
# <span>Email:</span>
|
|
1507
|
+
# <a class="email" href="mailto:joe@example.com">joe@example.com</a>
|
|
1508
|
+
# </div>
|
|
1509
|
+
def render_email(self, h, x):
|
|
1510
|
+
latin = True if h.get('class') == 'ascii' else ( False if h.get('class') == 'non-ascii' else None )
|
|
1511
|
+
if latin == False:
|
|
1512
|
+
return None
|
|
1513
|
+
value = x.text.strip() if x.text else ''
|
|
1514
|
+
if value:
|
|
1515
|
+
cls = 'email'
|
|
1516
|
+
prev = x.getprevious()
|
|
1517
|
+
if prev!=None and prev.tag==x.tag:
|
|
1518
|
+
div = h[-1]
|
|
1519
|
+
sib = div[-1]
|
|
1520
|
+
sib.tail = sib.tail+', ' if sib.tail else ', '
|
|
1521
|
+
div.append(build.a(value, href='mailto:%s'%value, classes=cls))
|
|
1522
|
+
else:
|
|
1523
|
+
div = add.div(h, None,
|
|
1524
|
+
build.span("Email:"), '\n',
|
|
1525
|
+
build.a(value, href='mailto:%s'%value, classes=cls),
|
|
1526
|
+
classes=cls,
|
|
1527
|
+
)
|
|
1528
|
+
return div
|
|
1529
|
+
|
|
1530
|
+
#
|
|
1531
|
+
# 9.24. <eref>
|
|
1532
|
+
#
|
|
1533
|
+
# This element is rendered as an HTML <a> element, with the "href"
|
|
1534
|
+
# attribute set to the value of the "target" attribute and the CSS
|
|
1535
|
+
# class of "eref".
|
|
1536
|
+
#
|
|
1537
|
+
# <a href="https://..." class="eref">the text</a>
|
|
1538
|
+
def render_eref(self, h, x):
|
|
1539
|
+
target = x.get('target')
|
|
1540
|
+
brackets = x.get('brackets', self.attribute_defaults[x.tag]['brackets'])
|
|
1541
|
+
if x.text:
|
|
1542
|
+
hh = add.a(h, x, href=target)
|
|
1543
|
+
else:
|
|
1544
|
+
if brackets == 'none':
|
|
1545
|
+
hh = add.span(h, x, build.a(target, href=target))
|
|
1546
|
+
elif brackets == 'angle':
|
|
1547
|
+
hh = add.span(h, x, '<', build.a(target, href=target), '>')
|
|
1548
|
+
else:
|
|
1549
|
+
self.warn(x, 'Unexpected attribute value in <eref>: brackets="%s"' % brackets)
|
|
1550
|
+
return hh
|
|
1551
|
+
|
|
1552
|
+
# 9.25. <figure>
|
|
1553
|
+
#
|
|
1554
|
+
# This element renders as the HTML <figure> element, containing the
|
|
1555
|
+
# artwork or sourcecode indicated and an HTML <figcaption> element.
|
|
1556
|
+
# The <figcaption> element will contain an <a> element around the
|
|
1557
|
+
# figure number. It will also contain another <a> element with CSS
|
|
1558
|
+
# class "selfRef" around the figure name, if a name was given.
|
|
1559
|
+
#
|
|
1560
|
+
# <figure id="f-1">
|
|
1561
|
+
# ...
|
|
1562
|
+
# <figcaption>
|
|
1563
|
+
# <a href="#f-1">Figure 1.</a>
|
|
1564
|
+
# <a href="#n-it-figures" id="n-it-figures" class="selfRef">
|
|
1565
|
+
# It figures
|
|
1566
|
+
# </a>
|
|
1567
|
+
# </figcaption>
|
|
1568
|
+
# </figure>
|
|
1569
|
+
def render_figure(self, h, x):
|
|
1570
|
+
name = x.find('name')
|
|
1571
|
+
name_slug = None
|
|
1572
|
+
if name != None:
|
|
1573
|
+
name_slug = name.get('slugifiedName')
|
|
1574
|
+
if name_slug:
|
|
1575
|
+
add.span(h, None, id=name_slug)
|
|
1576
|
+
figure = add.figure(h, x)
|
|
1577
|
+
for c in x.iterchildren('artset', 'artwork', 'sourcecode'):
|
|
1578
|
+
self.render(figure, c)
|
|
1579
|
+
pn = x.get('pn')
|
|
1580
|
+
caption = add.figcaption(figure, None)
|
|
1581
|
+
a = add.a(caption, None, pn.replace('-',' ',1).title(), href='#%s'%pn, classes='selfRef')
|
|
1582
|
+
if name != None and name_slug:
|
|
1583
|
+
a.tail = ':\n'
|
|
1584
|
+
a = add.a(caption, None, href='#%s'%name_slug, classes='selfRef')
|
|
1585
|
+
self.inline_text_renderer(a, name)
|
|
1586
|
+
return figure
|
|
1587
|
+
|
|
1588
|
+
# 9.26. <front>
|
|
1589
|
+
#
|
|
1590
|
+
# See "Document Information" (Section 6.5) for information on this
|
|
1591
|
+
# element.
|
|
1592
|
+
def render_front(self, h, x):
|
|
1593
|
+
if self.part == 'front':
|
|
1594
|
+
# 6.5. Document Information
|
|
1595
|
+
#
|
|
1596
|
+
# Information about the document as a whole will appear as the first
|
|
1597
|
+
# child of the HTML <body> element, embedded in an HTML <dl> element
|
|
1598
|
+
# with id="identifiers". The defined terms in the definition list are
|
|
1599
|
+
# "Workgroup:", "Series:", "Status:", "Published:", and "Author:" or
|
|
1600
|
+
# "Authors:" (as appropriate). For example:
|
|
1601
|
+
#
|
|
1602
|
+
# <dl id="identifiers">
|
|
1603
|
+
# <dt>Workgroup:</dt>
|
|
1604
|
+
# <dd class="workgroup">rfc-interest</dd>
|
|
1605
|
+
# <dt>Series:</dt>
|
|
1606
|
+
# <dd class="series">Internet-Draft</dd>
|
|
1607
|
+
# <dt>Status:</dt>
|
|
1608
|
+
# <dd class="status">Informational</dd>
|
|
1609
|
+
# <dt>Published:</dt>
|
|
1610
|
+
# <dd><time datetime="2014-10-25"
|
|
1611
|
+
# class="published">2014-10-25</time></dd>
|
|
1612
|
+
# <dt>Authors:</dt>
|
|
1613
|
+
# <dd class="authors">
|
|
1614
|
+
# <div class="author">
|
|
1615
|
+
# <span class="initial">J.</span>
|
|
1616
|
+
# <span class="surname">Hildebrand</span>
|
|
1617
|
+
# (<span class="organization">Cisco Systems, Inc.</span>)
|
|
1618
|
+
# <span class="editor">Ed.</span>
|
|
1619
|
+
# </div>
|
|
1620
|
+
# <div class="author">
|
|
1621
|
+
# <span class="initial">H.</span>
|
|
1622
|
+
# <span class="surname">Flanagan</span>
|
|
1623
|
+
# (<span class="organization">RFC Editor</span>)
|
|
1624
|
+
# </div>
|
|
1625
|
+
# </dd>
|
|
1626
|
+
# </dl>
|
|
1627
|
+
#
|
|
1628
|
+
|
|
1629
|
+
# Now, a text format RFC has the following information, optionals in
|
|
1630
|
+
# parentheses:
|
|
1631
|
+
#
|
|
1632
|
+
# If RFC:
|
|
1633
|
+
# * <Stream Name>
|
|
1634
|
+
# * Request for Comments: <Number>
|
|
1635
|
+
# * (STD|BCP|FYI: <Number>)
|
|
1636
|
+
# * (Obsoletes: <Number>[, <Number>]*)
|
|
1637
|
+
# * (Updates: <Number>[, <Number>]*)
|
|
1638
|
+
# * Category: <Category Name>
|
|
1639
|
+
# * ISSN: 2070-1721
|
|
1640
|
+
# else:
|
|
1641
|
+
# * <Workgroup name> or "Network Working Group"
|
|
1642
|
+
# * Internet-Draft
|
|
1643
|
+
# * (STD|BCP|FYI: <Number> (if approved))
|
|
1644
|
+
# * (Obsoletes: <Number>[, <Number>]*)
|
|
1645
|
+
# * (Updates: <Number>[, <Number>]*)
|
|
1646
|
+
# * Intended Status: <Category Name>
|
|
1647
|
+
# * Expires: <Date>
|
|
1648
|
+
|
|
1649
|
+
def entry(dl, name, *values):
|
|
1650
|
+
if filter(lambda x: x!=None, values):
|
|
1651
|
+
cls = slugify(name)
|
|
1652
|
+
dl.append( build.dt('%s:'%name, classes='label-%s'%cls))
|
|
1653
|
+
dl.append( build.dd(*values, classes=cls))
|
|
1654
|
+
# First page, top, external document information
|
|
1655
|
+
# This will be filled in by javascript from external JSON at rendering time
|
|
1656
|
+
h.append( build.div(classes='document-information', id="external-metadata" ))
|
|
1657
|
+
# First page, top, internal document information
|
|
1658
|
+
dl = build.dl(id='identifiers')
|
|
1659
|
+
h.append( build.div(dl, classes='document-information', id="internal-metadata" ))
|
|
1660
|
+
ipr = self.root.get('ipr')
|
|
1661
|
+
if ipr in ['', 'none']:
|
|
1662
|
+
# Empty IPR -- not an I*TF document; abbreviated top
|
|
1663
|
+
for wg in x.xpath('./workgroup'):
|
|
1664
|
+
if wg.text and wg.text.strip():
|
|
1665
|
+
entry(dl, 'Workgroup', wg.text.strip())
|
|
1666
|
+
found = True
|
|
1667
|
+
# Publication date
|
|
1668
|
+
entry(dl, 'Published', self.render_date(None, x.find('date')))
|
|
1669
|
+
else:
|
|
1670
|
+
if self.options.rfc:
|
|
1671
|
+
# Stream
|
|
1672
|
+
stream = self.root.get('submissionType')
|
|
1673
|
+
entry(dl, 'Stream', strings.stream_name[stream])
|
|
1674
|
+
# Series info
|
|
1675
|
+
for series in x.xpath('./seriesInfo'):
|
|
1676
|
+
self.render_seriesinfo(dl, series)
|
|
1677
|
+
for section in ['obsoletes', 'updates']:
|
|
1678
|
+
items = self.root.get(section)
|
|
1679
|
+
if items:
|
|
1680
|
+
alist = []
|
|
1681
|
+
for num in filter(None, items.split(',')):
|
|
1682
|
+
num = num.strip()
|
|
1683
|
+
if alist:
|
|
1684
|
+
alist[-1].tail = ', '
|
|
1685
|
+
a = build.a(num, href=os.path.join(self.options.rfc_base_url, 'rfc%s'%num), classes='eref')
|
|
1686
|
+
a.tail = ' '
|
|
1687
|
+
alist.append(a)
|
|
1688
|
+
entry(dl, section.title(), *alist)
|
|
1689
|
+
|
|
1690
|
+
category = self.root.get('category', '')
|
|
1691
|
+
if category:
|
|
1692
|
+
entry(dl, 'Category', strings.category_name[category])
|
|
1693
|
+
# Publication date
|
|
1694
|
+
entry(dl, 'Published', self.render_date(None, x.find('date')))
|
|
1695
|
+
# ISSN
|
|
1696
|
+
entry(dl, 'ISSN', '2070-1721')
|
|
1697
|
+
|
|
1698
|
+
else:
|
|
1699
|
+
# Workgroup
|
|
1700
|
+
found = False
|
|
1701
|
+
for wg in x.xpath('./workgroup'):
|
|
1702
|
+
if wg.text and wg.text.strip():
|
|
1703
|
+
entry(dl, 'Workgroup', wg.text.strip())
|
|
1704
|
+
found = True
|
|
1705
|
+
if not found:
|
|
1706
|
+
entry(dl, 'Workgroup', 'Network Working Group')
|
|
1707
|
+
# Internet-Draft
|
|
1708
|
+
for series in x.xpath('./seriesInfo'):
|
|
1709
|
+
entry(dl, series.get('name'), series.get('value'))
|
|
1710
|
+
# Obsoletes and Updates
|
|
1711
|
+
for section in ['obsoletes', 'updates']:
|
|
1712
|
+
items = self.root.get(section)
|
|
1713
|
+
if items:
|
|
1714
|
+
alist = []
|
|
1715
|
+
for num in filter(None, items.split(',')):
|
|
1716
|
+
num = num.strip()
|
|
1717
|
+
if alist:
|
|
1718
|
+
alist[-1].tail = ', '
|
|
1719
|
+
a = build.a(num, href=os.path.join(self.options.rfc_base_url, 'rfc%s'%num), classes='eref')
|
|
1720
|
+
alist.append(a)
|
|
1721
|
+
entry(dl, section.title(), *alist)
|
|
1722
|
+
a.tail = ' (if approved)'
|
|
1723
|
+
# Publication date
|
|
1724
|
+
entry(dl, 'Published', self.render_date(None, x.find('date')))
|
|
1725
|
+
# Intended category
|
|
1726
|
+
category = self.root.get('category', '')
|
|
1727
|
+
if category:
|
|
1728
|
+
entry(dl, 'Intended Status', strings.category_name[category])
|
|
1729
|
+
# Expiry date
|
|
1730
|
+
if self.root.get('ipr') != 'none':
|
|
1731
|
+
exp = get_expiry_date(self.root, self.date)
|
|
1732
|
+
expdate = build.date(year=str(exp.year), month=str(exp.month))
|
|
1733
|
+
if exp.day:
|
|
1734
|
+
expdate.set('day', str(exp.day))
|
|
1735
|
+
entry(dl, 'Expires', self.render_date(None, expdate))
|
|
1736
|
+
|
|
1737
|
+
authors = x.xpath('./author')
|
|
1738
|
+
dl.append( build.dt('Authors:' if len(authors)>1 else 'Author:', classes='label-authors' ))
|
|
1739
|
+
dd = add.dd(dl, None, classes='authors')
|
|
1740
|
+
for a in authors:
|
|
1741
|
+
self.render(dd, a)
|
|
1742
|
+
|
|
1743
|
+
for c in x.iterchildren('title', 'abstract', 'note', 'boilerplate', 'toc'):
|
|
1744
|
+
self.render(h, c)
|
|
1745
|
+
|
|
1746
|
+
elif self.part == 'references':
|
|
1747
|
+
self.default_renderer(h, x)
|
|
1748
|
+
else:
|
|
1749
|
+
self.err(x, "Did not expect to be asked to render <%s> while in <%s> (self.part: %s)" % (x.tag, x.getparent().tag, self.part))
|
|
1750
|
+
|
|
1751
|
+
|
|
1752
|
+
# 9.27. <iref>
|
|
1753
|
+
#
|
|
1754
|
+
# This element is rendered as an empty <> tag of class "iref", with an
|
|
1755
|
+
# "id" attribute consisting of the <iref> element's "irefid" attribute:
|
|
1756
|
+
#
|
|
1757
|
+
# <span class="iref" id="s-Paragraphs-first-1"/>
|
|
1758
|
+
def render_iref(self, h, x):
|
|
1759
|
+
span = add.span(None, x, classes='iref', id=x.get('pn'))
|
|
1760
|
+
if h.tag in ['table', ]:
|
|
1761
|
+
h.addprevious(span)
|
|
1762
|
+
else:
|
|
1763
|
+
h.append(span)
|
|
1764
|
+
return span
|
|
1765
|
+
|
|
1766
|
+
# 9.28. <keyword>
|
|
1767
|
+
#
|
|
1768
|
+
# Each <keyword> element renders its text into the <meta> keywords in
|
|
1769
|
+
# the document's header, separated by commas.
|
|
1770
|
+
#
|
|
1771
|
+
# <meta name="keywords" content="html,css,rfc">
|
|
1772
|
+
#
|
|
1773
|
+
# 9.29. <li>
|
|
1774
|
+
#
|
|
1775
|
+
# This element is rendered as its HTML counterpart. However, if there
|
|
1776
|
+
# is no contained element that has a pilcrow (Section 5.2) attached, a
|
|
1777
|
+
# pilcrow is added.
|
|
1778
|
+
#
|
|
1779
|
+
# <li id="s-2-7">Item <a href="#s-2-7" class="pilcrow">¶</a></li>
|
|
1780
|
+
def render_li_ul(self, h, x):
|
|
1781
|
+
indent = x.getparent().get('indent') or '3'
|
|
1782
|
+
style = None
|
|
1783
|
+
if indent and int(indent)>4:
|
|
1784
|
+
em=(int(indent)-4)*0.5
|
|
1785
|
+
style = 'margin-left: %.1fem;' % em
|
|
1786
|
+
li = add.li(h, x, classes=h.get('class'), style=style)
|
|
1787
|
+
for c in x.getchildren():
|
|
1788
|
+
self.render(li, c)
|
|
1789
|
+
self.maybe_add_pilcrow(li)
|
|
1790
|
+
return li
|
|
1791
|
+
|
|
1792
|
+
def render_li(self, h, x):
|
|
1793
|
+
if h.tag == 'ul':
|
|
1794
|
+
li = self.render_li_ul(h, x)
|
|
1795
|
+
elif h.tag == 'dl':
|
|
1796
|
+
li = self.render_li_dl(h, x)
|
|
1797
|
+
elif h.tag == 'ol':
|
|
1798
|
+
li = self.render_li_ol(h, x)
|
|
1799
|
+
else:
|
|
1800
|
+
self.err(x, "Did not expect to be asked to render <%s> while in <%s>" % (x.tag, h.tag))
|
|
1801
|
+
li = None
|
|
1802
|
+
return li
|
|
1803
|
+
|
|
1804
|
+
# 9.30. <link>
|
|
1805
|
+
#
|
|
1806
|
+
# This element is rendered as its HTML counterpart, in the HTML header.
|
|
1807
|
+
def render_link(self, h, x):
|
|
1808
|
+
link = add.link(h, x, href=x.get('href'), rel=x.get('rel'))
|
|
1809
|
+
return link
|
|
1810
|
+
|
|
1811
|
+
# 9.31. <middle>
|
|
1812
|
+
#
|
|
1813
|
+
# This element does not add any direct output to HTML.
|
|
1814
|
+
render_middle = skip_renderer
|
|
1815
|
+
|
|
1816
|
+
## Potential extension: <math>
|
|
1817
|
+
##
|
|
1818
|
+
## Same content as for instance <name>, but may contain unicode
|
|
1819
|
+
## characters of categories L*, P*, Sm, Sk or Zs. For categories L*, the script
|
|
1820
|
+
## must be either Common, Greek, or Hebrew.
|
|
1821
|
+
##
|
|
1822
|
+
## def render_math(self, s, x):
|
|
1823
|
+
## for t in x.itertext():
|
|
1824
|
+
## for c in t:
|
|
1825
|
+
## cat = unicode.category(c)
|
|
1826
|
+
## if cat.beginswith('L'):
|
|
1827
|
+
## scr = get_script(c)
|
|
1828
|
+
## if not scr in ['Common', 'Greek', 'Hebrew', ]:
|
|
1829
|
+
## self.err(x, ...)
|
|
1830
|
+
## div = add.div(h, x, classes="inline-math")
|
|
1831
|
+
## for c in x.getchildren():
|
|
1832
|
+
## self.render(div, c)
|
|
1833
|
+
##
|
|
1834
|
+
|
|
1835
|
+
# 9.32. <name>
|
|
1836
|
+
#
|
|
1837
|
+
# This element is never rendered directly; it is only rendered when
|
|
1838
|
+
# considering a parent element, such as <figure>, <references>,
|
|
1839
|
+
# <section>, or <table>.
|
|
1840
|
+
def render_name(self, s, x):
|
|
1841
|
+
p = x.getparent()
|
|
1842
|
+
if p.tag in [ 'note', 'section', 'references' ]:
|
|
1843
|
+
name_slug = x.get('slugifiedName') or None
|
|
1844
|
+
pn = p.get('pn')
|
|
1845
|
+
# determine whether we have an appendix
|
|
1846
|
+
_, num, _ = self.split_pn(pn)
|
|
1847
|
+
num += '.'
|
|
1848
|
+
level = min([6, self.level_of_section_num(num) + 1])
|
|
1849
|
+
tag = 'h%d' % level
|
|
1850
|
+
h = build(tag, id=name_slug)
|
|
1851
|
+
s.append(h)
|
|
1852
|
+
#
|
|
1853
|
+
numbered = p.get('numbered')=='true' or (self.check_refs_numbered() if p.tag == 'references' else False)
|
|
1854
|
+
if num and numbered:
|
|
1855
|
+
if self.is_appendix(pn) and self.is_top_level_section(num):
|
|
1856
|
+
num = 'Appendix %s' % num
|
|
1857
|
+
a_number = build.a(
|
|
1858
|
+
num.title(),
|
|
1859
|
+
' ',
|
|
1860
|
+
href='#%s' % id_for_pn(pn),
|
|
1861
|
+
classes='section-number selfRef',
|
|
1862
|
+
)
|
|
1863
|
+
h.append( a_number)
|
|
1864
|
+
if name_slug:
|
|
1865
|
+
a_title = build.a(href='#%s'%name_slug, classes='section-name selfRef')
|
|
1866
|
+
else:
|
|
1867
|
+
a_title = build.span()
|
|
1868
|
+
self.inline_text_renderer(a_title, x)
|
|
1869
|
+
h.append(a_title)
|
|
1870
|
+
return h
|
|
1871
|
+
elif p.tag in [ 'table', 'figure' ]:
|
|
1872
|
+
return None
|
|
1873
|
+
else:
|
|
1874
|
+
self.warn(x, "Did not expect to be asked to render <%s> while in <%s>" % (x.tag, x.getparent().tag))
|
|
1875
|
+
self.default_renderer(s, x)
|
|
1876
|
+
|
|
1877
|
+
|
|
1878
|
+
# 9.33. <note>
|
|
1879
|
+
#
|
|
1880
|
+
# This element is rendered like a <section> element, but without a
|
|
1881
|
+
# section number and with the CSS class of "note". If the
|
|
1882
|
+
# "removeInRFC" attribute is set to "yes", the generated <div> element
|
|
1883
|
+
# will also include the CSS class "rfcEditorRemove".
|
|
1884
|
+
#
|
|
1885
|
+
# <section id="s-note-1" class="note rfcEditorRemove">
|
|
1886
|
+
# <h2>
|
|
1887
|
+
# <a href="#n-editorial-note" class="selfRef">Editorial Note</a>
|
|
1888
|
+
# </h2>
|
|
1889
|
+
# <p id="s-note-1-1">
|
|
1890
|
+
# Discussion of this draft takes place...
|
|
1891
|
+
# <a href="#s-note-1-1" class="pilcrow">¶</a>
|
|
1892
|
+
# </p>
|
|
1893
|
+
# </section>
|
|
1894
|
+
def render_note(self, h, x):
|
|
1895
|
+
classes = 'note'
|
|
1896
|
+
if x.get('removeInRFC') == 'true':
|
|
1897
|
+
classes += ' rfcEditorRemove'
|
|
1898
|
+
section = add.section(h, x, classes=classes)
|
|
1899
|
+
for c in x.getchildren():
|
|
1900
|
+
self.render(section, c)
|
|
1901
|
+
return section
|
|
1902
|
+
|
|
1903
|
+
# 9.34. <ol>
|
|
1904
|
+
#
|
|
1905
|
+
# The output created from an <ol> element depends upon the "style"
|
|
1906
|
+
# attribute.
|
|
1907
|
+
#
|
|
1908
|
+
# If the "spacing" attribute has the value "compact", a CSS class of
|
|
1909
|
+
# "olCompact" will be added.
|
|
1910
|
+
#
|
|
1911
|
+
# The group attribute is not copied; the input XML should have start
|
|
1912
|
+
# values added by a prep tool for all grouped <ol> elements.
|
|
1913
|
+
def render_ol(self, h, x):
|
|
1914
|
+
type = x.get('type')
|
|
1915
|
+
if len(type) > 1 and '%' in type:
|
|
1916
|
+
add.span(h, None, classes='break')
|
|
1917
|
+
ol = add.dl(h, x, classes='olPercent')
|
|
1918
|
+
else:
|
|
1919
|
+
attrib = sdict(dict( (k,v) for (k,v) in x.attrib.items() if k in ['start', 'type', ] ))
|
|
1920
|
+
classes= ' '.join(filter(None, [ x.get('spacing'), 'type-%s' % attrib.get('type', '1') ]))
|
|
1921
|
+
ol = add.ol(h, x, classes=classes, **attrib)
|
|
1922
|
+
for c in x.getchildren():
|
|
1923
|
+
self.render(ol, c)
|
|
1924
|
+
return ol
|
|
1925
|
+
|
|
1926
|
+
# 9.34.1. Percent Styles
|
|
1927
|
+
#
|
|
1928
|
+
# If the style attribute includes the character "%", the output is a
|
|
1929
|
+
# <dl> tag with the class "olPercent". Each contained <li> element is
|
|
1930
|
+
# emitted as a <dt>/<dd> pair, with the generated label in the <dt> and
|
|
1931
|
+
# the contents of the <li> in the <dd>.
|
|
1932
|
+
#
|
|
1933
|
+
# <dl class="olPercent">
|
|
1934
|
+
# <dt>Requirement xviii:</dt>
|
|
1935
|
+
# <dd>Wheels on a big rig</dd>
|
|
1936
|
+
# </dl>
|
|
1937
|
+
def render_li_dl(self, h, x):
|
|
1938
|
+
label = x.get('derivedCounter')
|
|
1939
|
+
dt = add.dt(h, None, label)
|
|
1940
|
+
indent = x.getparent().get('indent') or '3'
|
|
1941
|
+
style = None
|
|
1942
|
+
if indent and indent.isdigit():
|
|
1943
|
+
style = 'margin-left: %.1fem' % (int(indent)*0.5) if indent and indent!="0" else None
|
|
1944
|
+
dd = add.dd(h, x, style=style)
|
|
1945
|
+
# workaround for weasyprint's unwillingness to break between <dd> and
|
|
1946
|
+
# <dt>: add an extra <dd> that is very prone to page breaks. See CSS.
|
|
1947
|
+
add.dd(h, None, classes='break')
|
|
1948
|
+
for c in x.getchildren():
|
|
1949
|
+
self.render(dd, c)
|
|
1950
|
+
self.maybe_add_pilcrow(dd)
|
|
1951
|
+
return dt, dd
|
|
1952
|
+
|
|
1953
|
+
# 9.34.2. Standard Styles
|
|
1954
|
+
#
|
|
1955
|
+
# For all other styles, an <ol> tag is emitted, with any "style"
|
|
1956
|
+
# attribute turned into the equivalent HTML attribute.
|
|
1957
|
+
#
|
|
1958
|
+
# <ol class="compact" type="I" start="18">
|
|
1959
|
+
# <li>Wheels on a big rig</li>
|
|
1960
|
+
# </ol>
|
|
1961
|
+
def render_li_ol(self, h, x):
|
|
1962
|
+
indent = x.getparent().get('indent') or '3'
|
|
1963
|
+
style = None
|
|
1964
|
+
if indent and indent.isdigit() and int(indent)>4:
|
|
1965
|
+
em=(int(indent)-4)*0.5
|
|
1966
|
+
style = 'padding-left: %.1fem;' % em
|
|
1967
|
+
li = add.li(h, x, style=style)
|
|
1968
|
+
for c in x.getchildren():
|
|
1969
|
+
self.render(li, c)
|
|
1970
|
+
self.maybe_add_pilcrow(li)
|
|
1971
|
+
return li
|
|
1972
|
+
|
|
1973
|
+
# 9.35. <organization>
|
|
1974
|
+
#
|
|
1975
|
+
# This element is rendered as an HTML <div> tag with CSS class "org".
|
|
1976
|
+
#
|
|
1977
|
+
# If the element contains the "ascii" attribute, the organization name
|
|
1978
|
+
# is rendered twice: once with the non-ASCII version wrapped in an HTML
|
|
1979
|
+
# <span> tag of class "non-ascii" and then as the ASCII version wrapped
|
|
1980
|
+
# in an HTML <span> tag of class "ascii" wrapped in parentheses.
|
|
1981
|
+
#
|
|
1982
|
+
# <div class="org">
|
|
1983
|
+
# <span class="non-ascii">Test Org</span>
|
|
1984
|
+
# (<span class="ascii">TEST ORG</span>)
|
|
1985
|
+
# </div>
|
|
1986
|
+
render_organization = null_renderer # handled in render_address
|
|
1987
|
+
|
|
1988
|
+
# 9.36. <phone>
|
|
1989
|
+
#
|
|
1990
|
+
# This element is rendered as an HTML <div> tag containing the string
|
|
1991
|
+
# "Phone:" (wrapped in a span), an HTML <a> tag with CSS class "tel"
|
|
1992
|
+
# containing the phone number (and an href with a corresponding "tel:"
|
|
1993
|
+
# URI), and an HTML <span> with CSS class "type" containing the string
|
|
1994
|
+
# "VOICE".
|
|
1995
|
+
#
|
|
1996
|
+
# <div>
|
|
1997
|
+
# <span>Phone:</span>
|
|
1998
|
+
# <a class="tel" href="tel:+1-720-555-1212">+1-720-555-1212</a>
|
|
1999
|
+
# <span class="type">VOICE</span>
|
|
2000
|
+
# </div>
|
|
2001
|
+
def render_phone(self, h, x):
|
|
2002
|
+
# The content of <span class="type">VOICE</span> seems to violate the
|
|
2003
|
+
# vcard types (they identify things like 'Home', 'Work', etc) and
|
|
2004
|
+
# will be skipped. The
|
|
2005
|
+
if not x.text:
|
|
2006
|
+
return None
|
|
2007
|
+
value = x.text.strip() if x.text else ''
|
|
2008
|
+
if value:
|
|
2009
|
+
cls = 'tel'
|
|
2010
|
+
div = add.div(h, None,
|
|
2011
|
+
build.span("Phone:"), '\n',
|
|
2012
|
+
build.a(value, href='tel:%s'%value, classes=cls),
|
|
2013
|
+
classes=cls,
|
|
2014
|
+
)
|
|
2015
|
+
return div
|
|
2016
|
+
|
|
2017
|
+
# 9.37. <postal>
|
|
2018
|
+
#
|
|
2019
|
+
# This element renders as an HTML <div> with CSS class "adr", unless it
|
|
2020
|
+
# contains one or more <postalLine> child elements; in which case, it
|
|
2021
|
+
# renders as an HTML <pre> element with CSS class "label".
|
|
2022
|
+
#
|
|
2023
|
+
# When there is no <postalLine> child, the following child elements are
|
|
2024
|
+
# rendered into the HTML:
|
|
2025
|
+
#
|
|
2026
|
+
# o Each <street> is rendered
|
|
2027
|
+
#
|
|
2028
|
+
# o A <div> that includes:
|
|
2029
|
+
#
|
|
2030
|
+
# * The rendering of all <city> elements
|
|
2031
|
+
#
|
|
2032
|
+
# * A comma and a space: ", "
|
|
2033
|
+
#
|
|
2034
|
+
# * The rendering of all <region> elements
|
|
2035
|
+
#
|
|
2036
|
+
# * Whitespace
|
|
2037
|
+
#
|
|
2038
|
+
# * The rendering of all <code> elements
|
|
2039
|
+
#
|
|
2040
|
+
# o The rendering of all <country> elements
|
|
2041
|
+
#
|
|
2042
|
+
# <div class="adr">
|
|
2043
|
+
# <div class="street-address">1 Main Street</div>
|
|
2044
|
+
# <div class="street-address">Suite 1</div>
|
|
2045
|
+
# <div>
|
|
2046
|
+
# <span class="city">Denver</span>,
|
|
2047
|
+
# <span class="region">CO</span>
|
|
2048
|
+
# <span class="postal-code">80212</span>
|
|
2049
|
+
# </div>
|
|
2050
|
+
# <div class="country-name">United States of America</div>
|
|
2051
|
+
# </div>
|
|
2052
|
+
##
|
|
2053
|
+
## Much of the description above is much too americentric, and also
|
|
2054
|
+
## conflicts with hCard. Examples from hCard will be used instead,
|
|
2055
|
+
## and addresses rendered in a format appropriate for their country.
|
|
2056
|
+
##
|
|
2057
|
+
## <span class="adr">
|
|
2058
|
+
## <span class="street-address">12 rue Danton</span>
|
|
2059
|
+
## <span class="postal-code">94270</span>
|
|
2060
|
+
## <span class="locality">Le Kremlin-Bicetre</span>
|
|
2061
|
+
## <span class="country-name">France</span>
|
|
2062
|
+
## </span>
|
|
2063
|
+
def render_postal(self, h, x):
|
|
2064
|
+
latin = h.get('class') == 'ascii'
|
|
2065
|
+
adr = get_normalized_address_info(self, x, latin=latin)
|
|
2066
|
+
if adr:
|
|
2067
|
+
if all(is_script(v, 'Latin') for v in adr.values() if v):
|
|
2068
|
+
latin = True
|
|
2069
|
+
align = 'left' if latin else get_bidi_alignment(adr)
|
|
2070
|
+
normalize = latin and x.get('asciiOrder') == 'normalized'
|
|
2071
|
+
for item in format_address(adr, latin=latin, normalize=normalize):
|
|
2072
|
+
item.set('class', align)
|
|
2073
|
+
h.append(item)
|
|
2074
|
+
else:
|
|
2075
|
+
# render elements in found order
|
|
2076
|
+
for c in x.getchildren():
|
|
2077
|
+
self.render(h, c)
|
|
2078
|
+
|
|
2079
|
+
# 9.38. <postalLine>
|
|
2080
|
+
#
|
|
2081
|
+
# This element renders as the text contained by the element, followed
|
|
2082
|
+
# by a newline. However, the last <postalLine> in a given <postal>
|
|
2083
|
+
# element should not be followed by a newline. For example:
|
|
2084
|
+
#
|
|
2085
|
+
# <postal>
|
|
2086
|
+
# <postalLine>In care of:</postalLine>
|
|
2087
|
+
# <postalLine>Computer Sciences Division</postalLine>
|
|
2088
|
+
# </postal>
|
|
2089
|
+
#
|
|
2090
|
+
# Would be rendered as:
|
|
2091
|
+
#
|
|
2092
|
+
# <pre class="label">In care of:
|
|
2093
|
+
# Computer Sciences Division</pre>
|
|
2094
|
+
def render_postalline(self, h, x):
|
|
2095
|
+
return self.address_line_renderer(h, x, classes='extended-address')
|
|
2096
|
+
|
|
2097
|
+
# 9.39. <refcontent>
|
|
2098
|
+
#
|
|
2099
|
+
# This element renders as an HTML <span> with CSS class "refContent".
|
|
2100
|
+
#
|
|
2101
|
+
# <span class="refContent">Self-published pamphlet</span>
|
|
2102
|
+
def render_refcontent(self, h, x):
|
|
2103
|
+
span = add.span(h, x, classes='refContent')
|
|
2104
|
+
for c in x.getchildren():
|
|
2105
|
+
self.render(span, c)
|
|
2106
|
+
return span
|
|
2107
|
+
|
|
2108
|
+
|
|
2109
|
+
# 9.40. <reference>
|
|
2110
|
+
#
|
|
2111
|
+
# If the parent of this element is not a <referencegroup>, this element
|
|
2112
|
+
# will render as a <dt> <dd> pair with the defined term being the
|
|
2113
|
+
# reference "anchor" attribute surrounded by square brackets and the
|
|
2114
|
+
# definition including the correct set of bibliographic information as
|
|
2115
|
+
# specified by [RFC7322]. The <dt> element will have an "id" attribute
|
|
2116
|
+
# of the reference anchor.
|
|
2117
|
+
#
|
|
2118
|
+
# <dl class="reference">
|
|
2119
|
+
# <dt id="RFC5646">[RFC5646]</dt>
|
|
2120
|
+
# <dd>
|
|
2121
|
+
# <span class="refAuthor">Phillips, A.</span>
|
|
2122
|
+
# <span>and</span>
|
|
2123
|
+
# <span class="refAuthor">M. Davis</span>
|
|
2124
|
+
# <span class="refTitle">"Tags for Identifying Languages"</span>,
|
|
2125
|
+
# ...
|
|
2126
|
+
# </dd>
|
|
2127
|
+
# </dl>
|
|
2128
|
+
#
|
|
2129
|
+
# If the child of a <referencegroup>, this element renders as a <div>
|
|
2130
|
+
# of class "refInstance" whose "id" attribute is the value of the
|
|
2131
|
+
# <source> element's "anchor" attribute.
|
|
2132
|
+
#
|
|
2133
|
+
# <div class="refInstance" id="RFC5730">
|
|
2134
|
+
# ...
|
|
2135
|
+
# </div>
|
|
2136
|
+
def render_reference(self, h, x):
|
|
2137
|
+
p = x.getparent()
|
|
2138
|
+
if p.tag == 'referencegroup':
|
|
2139
|
+
div = add.div(h, x, classes='refInstance')
|
|
2140
|
+
outer = div
|
|
2141
|
+
inner = div
|
|
2142
|
+
else:
|
|
2143
|
+
dt = add.dt(h, x, '[%s]'%x.get('derivedAnchor'))
|
|
2144
|
+
dd = add.dd(h, None)
|
|
2145
|
+
# workaround for weasyprint's unwillingness to break between <dd> and
|
|
2146
|
+
# <dt>: add an extra <dd> that is very prone to page breaks. See CSS.
|
|
2147
|
+
add.dd(h, None, classes='break')
|
|
2148
|
+
outer = dt, dd
|
|
2149
|
+
inner = dd
|
|
2150
|
+
# Deal with parts in the correct order
|
|
2151
|
+
for c in x.iterdescendants('author'):
|
|
2152
|
+
self.render(inner, c)
|
|
2153
|
+
for ctag in ('title', 'refcontent', 'stream', 'seriesInfo', 'date', ):
|
|
2154
|
+
for c in x.iterdescendants(ctag):
|
|
2155
|
+
if len(inner):
|
|
2156
|
+
inner[-1].tail = ', '
|
|
2157
|
+
self.render(inner, c)
|
|
2158
|
+
target = x.get('target')
|
|
2159
|
+
if len(inner):
|
|
2160
|
+
inner[-1].tail = ', '
|
|
2161
|
+
if target:
|
|
2162
|
+
inner.append( build.span('<', build.a(target, href=target), '>') )
|
|
2163
|
+
if len(inner):
|
|
2164
|
+
inner[-1].tail = '. '
|
|
2165
|
+
for ctag in ('annotation', ):
|
|
2166
|
+
for c in x.iterdescendants(ctag):
|
|
2167
|
+
self.render(inner, c)
|
|
2168
|
+
#
|
|
2169
|
+
return outer
|
|
2170
|
+
|
|
2171
|
+
# 9.41. <referencegroup>
|
|
2172
|
+
#
|
|
2173
|
+
# A <referencegroup> is translated into a <dt> <dd> pair, with the
|
|
2174
|
+
# defined term being the referencegroup "anchor" attribute surrounded
|
|
2175
|
+
# by square brackets, and the definition containing the translated
|
|
2176
|
+
# output of all of the child <reference> elements.
|
|
2177
|
+
#
|
|
2178
|
+
# <dt id="STD69">[STD69]</dt>
|
|
2179
|
+
# <dd>
|
|
2180
|
+
# <div class="refInstance" id="RFC5730">
|
|
2181
|
+
# <span class="refAuthor">Hollenbeck, S.</span>
|
|
2182
|
+
# ...
|
|
2183
|
+
# </div>
|
|
2184
|
+
# <div class="refInstance" id="RFC5731">
|
|
2185
|
+
# <span class="refAuthor">Hollenbeck, S.</span>
|
|
2186
|
+
# ...
|
|
2187
|
+
# </div>
|
|
2188
|
+
# ...
|
|
2189
|
+
# </dd>
|
|
2190
|
+
def render_referencegroup(self, h, x):
|
|
2191
|
+
dt = add.dt(h, x, '[%s]'%x.get('derivedAnchor'))
|
|
2192
|
+
dd = add.dd(h, None)
|
|
2193
|
+
target = x.get('target')
|
|
2194
|
+
subseries = False
|
|
2195
|
+
# workaround for weasyprint's unwillingness to break between <dd> and
|
|
2196
|
+
# <dt>: add an extra <dd> that is very prone to page breaks. See CSS.
|
|
2197
|
+
add.dd(h, None, classes='break')
|
|
2198
|
+
for series in x.xpath('.//seriesInfo'):
|
|
2199
|
+
if series.get('name') in SUBSERIES.keys():
|
|
2200
|
+
text = f"{SUBSERIES[series.get('name')]} {series.get('value')}"
|
|
2201
|
+
subdiv = build.div(text, classes='refInstance')
|
|
2202
|
+
if target:
|
|
2203
|
+
subdiv.text += ', '
|
|
2204
|
+
subdiv.append(build.span('<', build.a(target, href=target), '>'))
|
|
2205
|
+
subdiv[0].tail = '.'
|
|
2206
|
+
else:
|
|
2207
|
+
subdiv.text += '.'
|
|
2208
|
+
subdiv.append(build.br())
|
|
2209
|
+
subdiv.append(build.span(f"At the time of writing, this {series.get('name')} comprises the following:"))
|
|
2210
|
+
dd.append(subdiv)
|
|
2211
|
+
subseries = True
|
|
2212
|
+
break
|
|
2213
|
+
for c in x.getchildren():
|
|
2214
|
+
self.render(dd, c)
|
|
2215
|
+
if target and not subseries:
|
|
2216
|
+
dd.append( build.span('<', build.a(target, href=target), '>') )
|
|
2217
|
+
return dt, dd
|
|
2218
|
+
|
|
2219
|
+
# 9.42. <references>
|
|
2220
|
+
#
|
|
2221
|
+
# If there is at exactly one <references> element, a section is added
|
|
2222
|
+
# to the document, continuing with the next section number after the
|
|
2223
|
+
# last top-level <section> in <middle>. The <name> element of the
|
|
2224
|
+
# <references> element is used as the section name.
|
|
2225
|
+
#
|
|
2226
|
+
# <section id="n-my-references">
|
|
2227
|
+
# <h2 id="s-3">
|
|
2228
|
+
# <a href="#s-3" class="selfRef">3.</a>
|
|
2229
|
+
# <a href="#n-my-references class="selfRef">My References</a>
|
|
2230
|
+
# </h2>
|
|
2231
|
+
# ...
|
|
2232
|
+
# </section>
|
|
2233
|
+
#
|
|
2234
|
+
# If there is more than one <references> element, an HTML <section>
|
|
2235
|
+
# element is created to contain a subsection for each of the
|
|
2236
|
+
# <references>. The section number will be the next section number
|
|
2237
|
+
# after the last top-level <section> in <middle>. The name of this
|
|
2238
|
+
# section will be "References", and its "id" attribute will be
|
|
2239
|
+
# "n-references".
|
|
2240
|
+
#
|
|
2241
|
+
# <section id="n-references">
|
|
2242
|
+
# <h2 id="s-3">
|
|
2243
|
+
# <a href="#s-3" class="selfRef">3.</a>
|
|
2244
|
+
# <a href="#n-references" class="selfRef">References</a>
|
|
2245
|
+
# </h2>
|
|
2246
|
+
# <section id="n-informative-references">
|
|
2247
|
+
# <h3 id="s-3.1">
|
|
2248
|
+
# <a href="#s-3.1" class="selfRef">3.1.</a>
|
|
2249
|
+
# <a href="#n-informative-references" class="selfRef">
|
|
2250
|
+
# Informative References</a></h3>
|
|
2251
|
+
# <dl class="reference">...
|
|
2252
|
+
# </dl>
|
|
2253
|
+
# </section>
|
|
2254
|
+
# ...
|
|
2255
|
+
# </section>
|
|
2256
|
+
def render_references(self, h, x):
|
|
2257
|
+
self.part = x.tag
|
|
2258
|
+
section = add.section(h, x)
|
|
2259
|
+
hh = section
|
|
2260
|
+
for c in x.getchildren():
|
|
2261
|
+
if c.tag in ['reference', 'referencegroup'] and hh.tag != 'dl':
|
|
2262
|
+
hh = add.dl(hh, None, classes='references')
|
|
2263
|
+
self.render(hh, c)
|
|
2264
|
+
return section
|
|
2265
|
+
|
|
2266
|
+
#
|
|
2267
|
+
# 9.43. <region>
|
|
2268
|
+
#
|
|
2269
|
+
# This element is rendered as a <span> tag with CSS class "region".
|
|
2270
|
+
#
|
|
2271
|
+
# <span class="region">Colorado</span>
|
|
2272
|
+
def render_region(self, h, x):
|
|
2273
|
+
return self.address_line_renderer(h, x, classes='region')
|
|
2274
|
+
|
|
2275
|
+
#
|
|
2276
|
+
# 9.44. <relref>
|
|
2277
|
+
#
|
|
2278
|
+
# This element is rendered as an HTML <a> tag with CSS class "relref"
|
|
2279
|
+
# and "href" attribute of the "derivedLink" attribute of the element.
|
|
2280
|
+
# Different values of the "displayFormat" attribute cause the text
|
|
2281
|
+
# inside that HTML <a> tag to change and cause extra text to be
|
|
2282
|
+
# generated. Some values of the "displayFormat" attribute also cause
|
|
2283
|
+
# another HTML <a> tag to be rendered with CSS class "xref" and an
|
|
2284
|
+
# "href" of "#" and the "target" attribute (modified by any applicable
|
|
2285
|
+
# <displayreference> XML element) and text inside of the "target"
|
|
2286
|
+
# attribute (modified by any applicable <displayreference> XML
|
|
2287
|
+
# element). When used, this <a class='xref'> HTML tag is always
|
|
2288
|
+
# surrounded by square brackets, for example, "[<a class='xref'
|
|
2289
|
+
# href='#foo'>foo</a>]".
|
|
2290
|
+
|
|
2291
|
+
## Deprecated, removed by preptool
|
|
2292
|
+
|
|
2293
|
+
|
|
2294
|
+
# 9.44.1. displayFormat='of'
|
|
2295
|
+
#
|
|
2296
|
+
# The output is an <a class='relref'> HTML tag, with contents of
|
|
2297
|
+
# "Section " and the value of the "section" attribute. This is
|
|
2298
|
+
# followed by the word "of" (surrounded by whitespace). This is
|
|
2299
|
+
# followed by the <a class='xref'> HTML tag (surrounded by square
|
|
2300
|
+
# brackets).
|
|
2301
|
+
#
|
|
2302
|
+
# For example, with an input of:
|
|
2303
|
+
#
|
|
2304
|
+
# See <relref section="2.3" target="RFC9999" displayFormat="of"
|
|
2305
|
+
# derivedLink="http://www.rfc-editor.org/info/rfc9999#s-2.3"/>
|
|
2306
|
+
# for an overview.
|
|
2307
|
+
#
|
|
2308
|
+
# The HTML generated will be:
|
|
2309
|
+
#
|
|
2310
|
+
# See <a class="relref"
|
|
2311
|
+
# href="http://www.rfc-editor.org/info/rfc9999#s-2.3">Section
|
|
2312
|
+
# 2.3</a> of [<a class="xref" href="#RFC9999">RFC9999</a>]
|
|
2313
|
+
# for an overview.
|
|
2314
|
+
#
|
|
2315
|
+
# 9.44.2. displayFormat='comma'
|
|
2316
|
+
#
|
|
2317
|
+
# The output is an <a class='xref'> HTML tag (wrapped by square
|
|
2318
|
+
# brackets), followed by a comma (","), followed by whitespace,
|
|
2319
|
+
# followed by an <a class='relref'> HTML tag, with contents of
|
|
2320
|
+
# "Section " and the value of the "section" attribute.
|
|
2321
|
+
#
|
|
2322
|
+
# For example, with an input of:
|
|
2323
|
+
#
|
|
2324
|
+
# See <relref section="2.3" target="RFC9999" displayFormat="comma"
|
|
2325
|
+
# derivedLink="http://www.rfc-editor.org/info/rfc9999#s-2.3"/>,
|
|
2326
|
+
# for an overview.
|
|
2327
|
+
#
|
|
2328
|
+
# The HTML generated will be:
|
|
2329
|
+
#
|
|
2330
|
+
# See [<a class="xref" href="#RFC9999">RFC9999</a>], <a class="relref"
|
|
2331
|
+
# href="http://www.rfc-editor.org/info/rfc9999#s-2.3">Section 2.3</a>,
|
|
2332
|
+
# for an overview.
|
|
2333
|
+
#
|
|
2334
|
+
# 9.44.3. displayFormat='parens'
|
|
2335
|
+
#
|
|
2336
|
+
# The output is an <a> element with "href" attribute whose value is the
|
|
2337
|
+
# value of the "target" attribute prepended by "#", and whose content
|
|
2338
|
+
# is the value of the "target" attribute; the entire element is wrapped
|
|
2339
|
+
# in square brackets. This is followed by whitespace. This is
|
|
2340
|
+
# followed by an <a> element whose "href" attribute is the value of the
|
|
2341
|
+
# "derivedLink" attribute and whose content is the value of the
|
|
2342
|
+
# "derivedRemoteContent" attribute; the entire element is wrapped in
|
|
2343
|
+
# parentheses.
|
|
2344
|
+
#
|
|
2345
|
+
# For example, if Section 2.3 of RFC 9999 has the title "Protocol
|
|
2346
|
+
# Overview", for an input of:
|
|
2347
|
+
#
|
|
2348
|
+
# See <relref section="2.3" target="RFC9999" displayFormat="parens"
|
|
2349
|
+
# derivedLink="http://www.rfc-editor.org/info/rfc9999#s-2.3"
|
|
2350
|
+
# derivedRemoteContent="Section 2.3"/> for an overview.
|
|
2351
|
+
#
|
|
2352
|
+
# The HTML generated will be:
|
|
2353
|
+
#
|
|
2354
|
+
# See [<a class="relref" href="#RFC9999">RFC9999</a>]
|
|
2355
|
+
# (<a class="relref"
|
|
2356
|
+
# href="http://www.rfc-editor.org/info/rfc9999#s-2.3">Section
|
|
2357
|
+
# 2.3</a>) for an overview.
|
|
2358
|
+
#
|
|
2359
|
+
# 9.44.4. displayFormat='bare'
|
|
2360
|
+
#
|
|
2361
|
+
# The output is an <a> element whose "href" attribute is the value of
|
|
2362
|
+
# the "derivedLink" attribute and whose content is the value of the
|
|
2363
|
+
# "derivedRemoteContent" attribute.
|
|
2364
|
+
#
|
|
2365
|
+
# For this input:
|
|
2366
|
+
#
|
|
2367
|
+
# See <relref section="2.3" target="RFC9999" displayFormat="bare"
|
|
2368
|
+
# derivedLink="http://www.rfc-editor.org/info/rfc9999#s-2.3"
|
|
2369
|
+
# derivedRemoteContent="Section 2.3"/> and ...
|
|
2370
|
+
#
|
|
2371
|
+
# The HTML generated will be:
|
|
2372
|
+
#
|
|
2373
|
+
# See <a class="relref"
|
|
2374
|
+
# href="http://www.rfc-editor.org/info/rfc9999#s-2.3">Section
|
|
2375
|
+
# 2.3</a> and ...
|
|
2376
|
+
|
|
2377
|
+
# 9.45. <rfc>
|
|
2378
|
+
#
|
|
2379
|
+
# Various attributes of this element are represented in different parts
|
|
2380
|
+
# of the HTML document.
|
|
2381
|
+
|
|
2382
|
+
# 9.46. <section>
|
|
2383
|
+
#
|
|
2384
|
+
# This element is rendered as an HTML <section> element, containing an
|
|
2385
|
+
# appropriate level HTML heading element (<h2>-<h6>). That heading
|
|
2386
|
+
# element contains an <a> element around the part number (pn), if
|
|
2387
|
+
# applicable (for instance, <abstract> does not get a section number).
|
|
2388
|
+
# Another <a> element is included with the section's name.
|
|
2389
|
+
#
|
|
2390
|
+
# <section id="intro">
|
|
2391
|
+
# <h2 id="s-1">
|
|
2392
|
+
# <a href="#s-1" class="selfRef">1.</a>
|
|
2393
|
+
# <a href="#intro" class="selfRef">Introduction</a>
|
|
2394
|
+
# </h2>
|
|
2395
|
+
# <p id="s-1-1">Paragraph <a href="#s-1-1" class="pilcrow">¶</a>
|
|
2396
|
+
# </p>
|
|
2397
|
+
# </section>
|
|
2398
|
+
def render_section(self, h, x):
|
|
2399
|
+
section = add(x.tag, h, x)
|
|
2400
|
+
anchor = x.get('anchor')
|
|
2401
|
+
if anchor == 'toc':
|
|
2402
|
+
add.a(section, None, "\u25b2", href="#", onclick="scroll(0,0)", classes="toplink")
|
|
2403
|
+
for c in x.getchildren():
|
|
2404
|
+
self.render(section, c)
|
|
2405
|
+
return section
|
|
2406
|
+
|
|
2407
|
+
|
|
2408
|
+
# 9.47. <seriesInfo>
|
|
2409
|
+
#
|
|
2410
|
+
# This element is rendered in an HTML <span> element with CSS name
|
|
2411
|
+
# "seriesInfo".
|
|
2412
|
+
#
|
|
2413
|
+
# <span class="seriesInfo">RFC 5646</span>
|
|
2414
|
+
## This is different from what's shown in the sample documents, _and_
|
|
2415
|
+
## different from what's shown in Section 6.5. Following the sample doc
|
|
2416
|
+
## here.
|
|
2417
|
+
def render_seriesinfo(self, h, x):
|
|
2418
|
+
def entry(dl, name, value):
|
|
2419
|
+
cls = slugify(name)
|
|
2420
|
+
dl.append( build.dt('%s:'%name, classes='label-%s'%cls))
|
|
2421
|
+
dl.append( build.dd(value, classes=cls))
|
|
2422
|
+
#
|
|
2423
|
+
name = x.get('name')
|
|
2424
|
+
value = x.get('value')
|
|
2425
|
+
if self.part == 'front':
|
|
2426
|
+
if name == 'RFC':
|
|
2427
|
+
value = build.a(value, href=os.path.join(self.options.rfc_base_url, 'rfc%s'%value), classes='eref')
|
|
2428
|
+
elif name == 'DOI':
|
|
2429
|
+
value = build.a(value, href=os.path.join(self.options.doi_base_url, value), classes='eref')
|
|
2430
|
+
elif name == 'Internet-Draft':
|
|
2431
|
+
value = build.a(value, href=os.path.join(self.options.id_base_url, value), classes='eref')
|
|
2432
|
+
entry(h, name, value)
|
|
2433
|
+
return h
|
|
2434
|
+
elif self.part == 'references':
|
|
2435
|
+
if name == 'Internet-Draft':
|
|
2436
|
+
span = add.span(h, x, name, ', ', value, classes='seriesInfo')
|
|
2437
|
+
else:
|
|
2438
|
+
span = add.span(h, x, name, ' ', value, classes='seriesInfo')
|
|
2439
|
+
return span
|
|
2440
|
+
else:
|
|
2441
|
+
self.err(x, "Did not expect to be asked to render <%s> while in <%s>" % (x.tag, x.getparent().tag))
|
|
2442
|
+
|
|
2443
|
+
|
|
2444
|
+
# 9.48. <sourcecode>
|
|
2445
|
+
#
|
|
2446
|
+
# This element is rendered in an HTML <pre> element with a CSS class of
|
|
2447
|
+
# "sourcecode". Note that CDATA blocks do not work consistently in
|
|
2448
|
+
# HTML, so all <, >, and & must be escaped as <, >, and &,
|
|
2449
|
+
# respectively. If the input XML has a "type" attribute, another CSS
|
|
2450
|
+
# class of "lang-" and the type is added.
|
|
2451
|
+
#
|
|
2452
|
+
# If the sourcecode is not inside a <figure> element, a pilcrow
|
|
2453
|
+
# (Section 5.2) is included. Inside a <figure> element, the figure
|
|
2454
|
+
# title serves the purpose of the pilcrow.
|
|
2455
|
+
#
|
|
2456
|
+
# <pre class="sourcecode lang-c">
|
|
2457
|
+
# #include <stdio.h>
|
|
2458
|
+
#
|
|
2459
|
+
# int main(void)
|
|
2460
|
+
# {
|
|
2461
|
+
# printf("hello, world\n");
|
|
2462
|
+
# return 0;
|
|
2463
|
+
# }
|
|
2464
|
+
# </pre>
|
|
2465
|
+
def render_sourcecode(self, h, x):
|
|
2466
|
+
file = x.get('name')
|
|
2467
|
+
type = x.get('type')
|
|
2468
|
+
mark = x.get('markers') == 'true'
|
|
2469
|
+
classes = 'sourcecode'
|
|
2470
|
+
if type:
|
|
2471
|
+
classes += ' lang-%s' % type
|
|
2472
|
+
if (len(x.text.split('\n')) > 50):
|
|
2473
|
+
classes += ' breakable'
|
|
2474
|
+
div = add.div(h, x, classes=classes)
|
|
2475
|
+
div.text = None
|
|
2476
|
+
pre = add.pre(div, None, x.text)
|
|
2477
|
+
if mark:
|
|
2478
|
+
text = pre.text
|
|
2479
|
+
if file:
|
|
2480
|
+
text = (' file "%s"\n%s' % (file, text)) if text else '\n%s' % text
|
|
2481
|
+
text = "<CODE BEGINS>%s\n<CODE ENDS>" % text
|
|
2482
|
+
pre.text = text
|
|
2483
|
+
if x.getparent().tag != 'figure':
|
|
2484
|
+
self.maybe_add_pilcrow(div)
|
|
2485
|
+
return div
|
|
2486
|
+
|
|
2487
|
+
|
|
2488
|
+
def render_stream(self, h, x):
|
|
2489
|
+
return add.span(h, x, classes="stream")
|
|
2490
|
+
|
|
2491
|
+
# 9.49. <street>
|
|
2492
|
+
#
|
|
2493
|
+
# This element renders as an HTML <div> element with CSS class "street-
|
|
2494
|
+
# address".
|
|
2495
|
+
#
|
|
2496
|
+
# <div class="street-address">1899 Wynkoop St, Suite 600</div>
|
|
2497
|
+
def render_street(self, h, x):
|
|
2498
|
+
return self.address_line_renderer(h, x, classes='street-address')
|
|
2499
|
+
|
|
2500
|
+
def render_extaddr(self, h, x):
|
|
2501
|
+
return self.address_line_renderer(h, x, classes='extended-address')
|
|
2502
|
+
|
|
2503
|
+
def render_pobox(self, h, x):
|
|
2504
|
+
return self.address_line_renderer(h, x, classes='post-office-box')
|
|
2505
|
+
|
|
2506
|
+
|
|
2507
|
+
# 9.50. <strong>
|
|
2508
|
+
#
|
|
2509
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2510
|
+
render_strong = default_renderer
|
|
2511
|
+
|
|
2512
|
+
# 9.51. <sub>
|
|
2513
|
+
#
|
|
2514
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2515
|
+
render_sub = default_renderer
|
|
2516
|
+
|
|
2517
|
+
# 9.52. <sup>
|
|
2518
|
+
#
|
|
2519
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2520
|
+
render_sup = default_renderer
|
|
2521
|
+
|
|
2522
|
+
# 9.53. <t>
|
|
2523
|
+
#
|
|
2524
|
+
# This element is rendered as an HTML <p> element. A pilcrow
|
|
2525
|
+
# (Section 5.2) is included.
|
|
2526
|
+
#
|
|
2527
|
+
# <p id="s-1-1">A paragraph.
|
|
2528
|
+
# <a href="#s-1-1" class="pilcrow">¶</a></p>
|
|
2529
|
+
def render_t(self, h, x):
|
|
2530
|
+
indent = x.get('indent') or '0'
|
|
2531
|
+
style = 'margin-left: %.1fem' % (int(indent)*0.5) if indent and indent!="0" else None
|
|
2532
|
+
p = add.p(h, x, style=style)
|
|
2533
|
+
for c in x.getchildren():
|
|
2534
|
+
self.render(p, c)
|
|
2535
|
+
self.maybe_add_pilcrow(p)
|
|
2536
|
+
return p
|
|
2537
|
+
|
|
2538
|
+
#
|
|
2539
|
+
# 9.54. <table>
|
|
2540
|
+
#
|
|
2541
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2542
|
+
def render_table(self, h, x):
|
|
2543
|
+
name = x.find('name')
|
|
2544
|
+
name_slug = None
|
|
2545
|
+
if name != None:
|
|
2546
|
+
name_slug = name.get('slugifiedName')
|
|
2547
|
+
if name_slug:
|
|
2548
|
+
add.span(h, None, id=name_slug)
|
|
2549
|
+
align = x.get('align')
|
|
2550
|
+
table = add.table(h, x, classes=align)
|
|
2551
|
+
caption = add.caption(table, None)
|
|
2552
|
+
pn = x.get('pn')
|
|
2553
|
+
a = add.a(caption, None, pn.replace('-',' ',1).title(), href='#%s'%pn, classes='selfRef')
|
|
2554
|
+
if name != None and name_slug:
|
|
2555
|
+
a.tail = ':\n'
|
|
2556
|
+
a = add.a(caption, None, href='#%s'%name_slug, classes='selfRef')
|
|
2557
|
+
self.inline_text_renderer(a, name)
|
|
2558
|
+
for c in x.getchildren():
|
|
2559
|
+
self.render(table, c)
|
|
2560
|
+
return table
|
|
2561
|
+
|
|
2562
|
+
# 9.55. <tbody>
|
|
2563
|
+
#
|
|
2564
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2565
|
+
render_tbody = default_renderer
|
|
2566
|
+
|
|
2567
|
+
# 9.56. <td>
|
|
2568
|
+
#
|
|
2569
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2570
|
+
def render_td(self, h, x):
|
|
2571
|
+
classes = "text-%s" % x.get('align')
|
|
2572
|
+
hh = add(x.tag, h, x, classes=classes)
|
|
2573
|
+
hh.set('rowspan', x.get('rowspan', '1'))
|
|
2574
|
+
hh.set('colspan', x.get('colspan', '1'))
|
|
2575
|
+
for c in x.getchildren():
|
|
2576
|
+
self.render(hh, c)
|
|
2577
|
+
|
|
2578
|
+
# 9.57. <tfoot>
|
|
2579
|
+
#
|
|
2580
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2581
|
+
render_tfoot = default_renderer
|
|
2582
|
+
|
|
2583
|
+
# 9.58. <th>
|
|
2584
|
+
#
|
|
2585
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2586
|
+
def render_th(self, h, x):
|
|
2587
|
+
classes = "text-%s" % x.get('align')
|
|
2588
|
+
hh = add(x.tag, h, x, classes=classes)
|
|
2589
|
+
hh.set('rowspan', x.get('rowspan', '1'))
|
|
2590
|
+
hh.set('colspan', x.get('colspan', '1'))
|
|
2591
|
+
for c in x.getchildren():
|
|
2592
|
+
self.render(hh, c)
|
|
2593
|
+
|
|
2594
|
+
# 9.59. <thead>
|
|
2595
|
+
#
|
|
2596
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2597
|
+
render_thead = default_renderer
|
|
2598
|
+
|
|
2599
|
+
# 9.60. <title>
|
|
2600
|
+
#
|
|
2601
|
+
# The title of the document appears in a <title> element in the <head>
|
|
2602
|
+
# element, as described in Section 6.3.2.
|
|
2603
|
+
#
|
|
2604
|
+
# The title also appears in an <h1> element and follows directly after
|
|
2605
|
+
# the Document Information. The <h1> element has an "id" attribute
|
|
2606
|
+
# with value "title".
|
|
2607
|
+
#
|
|
2608
|
+
# <h1 id="title">HyperText Markup Language Request For
|
|
2609
|
+
# Comments Format</h1>
|
|
2610
|
+
#
|
|
2611
|
+
# Inside a reference, the title is rendered as an HTML <span> tag with
|
|
2612
|
+
# CSS class "refTitle". The text is surrounded by quotes inside the
|
|
2613
|
+
# <span>.
|
|
2614
|
+
#
|
|
2615
|
+
# <span class="refTitle">"Tags for Identifying Languages"</span>
|
|
2616
|
+
def render_title(self, h, x):
|
|
2617
|
+
pp = x.getparent().getparent()
|
|
2618
|
+
title = '\u2028'.join(x.itertext())
|
|
2619
|
+
if pp.get("quoteTitle") == 'true':
|
|
2620
|
+
title = '"%s"' % title
|
|
2621
|
+
ascii = x.get('ascii')
|
|
2622
|
+
if ascii and not is_script(title, 'Latin'):
|
|
2623
|
+
if pp.get("quoteTitle") == 'true':
|
|
2624
|
+
ascii = '"%s"' % ascii
|
|
2625
|
+
#
|
|
2626
|
+
if self.part == 'references':
|
|
2627
|
+
if title:
|
|
2628
|
+
span = wrap_ascii('span', clean_text(title), ascii, '', classes='refTitle')
|
|
2629
|
+
h.append(span)
|
|
2630
|
+
return span
|
|
2631
|
+
else:
|
|
2632
|
+
if self.options.rfc:
|
|
2633
|
+
rfc_number = self.root.get('number')
|
|
2634
|
+
h1 = build.h1("RFC %s" % rfc_number, id='rfcnum')
|
|
2635
|
+
h.append(h1)
|
|
2636
|
+
h1 = build.h1(title, id='title')
|
|
2637
|
+
h.append(h1)
|
|
2638
|
+
return h1
|
|
2639
|
+
|
|
2640
|
+
# <toc>
|
|
2641
|
+
render_toc = skip_renderer
|
|
2642
|
+
|
|
2643
|
+
# 9.61. <tr>
|
|
2644
|
+
#
|
|
2645
|
+
# This element is directly rendered as its HTML counterpart.
|
|
2646
|
+
render_tr = default_renderer
|
|
2647
|
+
|
|
2648
|
+
# 9.62. <tt>
|
|
2649
|
+
#
|
|
2650
|
+
# This element is rendered as an HTML <code> element.
|
|
2651
|
+
def render_tt(self, h, x):
|
|
2652
|
+
hh = add.code(h, x)
|
|
2653
|
+
for c in x.getchildren():
|
|
2654
|
+
self.render(hh, c)
|
|
2655
|
+
|
|
2656
|
+
# 9.63. <ul>
|
|
2657
|
+
#
|
|
2658
|
+
# This element is directly rendered as its HTML counterpart. If the
|
|
2659
|
+
# "spacing" attribute has the value "compact", a CSS class of
|
|
2660
|
+
# "ulCompact" will be added. If the "empty" attribute has the value
|
|
2661
|
+
# "true", a CSS class of "ulEmpty" will be added.
|
|
2662
|
+
def render_ul(self, h, x):
|
|
2663
|
+
ul = build.ul()
|
|
2664
|
+
p = x.getparent()
|
|
2665
|
+
panchor = p.get('anchor')
|
|
2666
|
+
classes = h.get('class', '')
|
|
2667
|
+
classes = re.sub(r'\bulEmpty\b ?', '', classes).rstrip() # Don't inherit empty attribute
|
|
2668
|
+
if panchor in ['toc', ]:
|
|
2669
|
+
hh = wrap(ul, 'nav', **{'class': panchor})
|
|
2670
|
+
classes += ' '+panchor if classes else panchor
|
|
2671
|
+
else:
|
|
2672
|
+
hh = ul
|
|
2673
|
+
h.append(hh)
|
|
2674
|
+
classes = ' '.join( list(
|
|
2675
|
+
set( classes.split() ) |
|
|
2676
|
+
set( filter(None, [
|
|
2677
|
+
'ulEmpty' if x.get('empty')=='true' else None,
|
|
2678
|
+
'ulBare' if x.get('bare')=='true' and x.get('empty')=='true' else None,
|
|
2679
|
+
x.get('spacing'), ]))))
|
|
2680
|
+
if classes:
|
|
2681
|
+
ul.set('class', classes)
|
|
2682
|
+
for c in x.getchildren():
|
|
2683
|
+
self.render(ul, c)
|
|
2684
|
+
return ul
|
|
2685
|
+
|
|
2686
|
+
# RFC 7997
|
|
2687
|
+
# 3.4. Body of the Document
|
|
2688
|
+
#
|
|
2689
|
+
# When the mention of non-ASCII characters is required for correct
|
|
2690
|
+
# protocol operation and understanding, the characters' Unicode
|
|
2691
|
+
# character name or code point MUST be included in the text.
|
|
2692
|
+
#
|
|
2693
|
+
# o Non-ASCII characters will require identifying the Unicode code
|
|
2694
|
+
# point.
|
|
2695
|
+
#
|
|
2696
|
+
# o Use of the actual UTF-8 character (e.g., Δ) is encouraged so
|
|
2697
|
+
# that a reader can more easily see what the character is, if their
|
|
2698
|
+
# device can render the text.
|
|
2699
|
+
#
|
|
2700
|
+
# o The use of the Unicode character names like "INCREMENT" in
|
|
2701
|
+
# addition to the use of Unicode code points is also encouraged.
|
|
2702
|
+
# When used, Unicode character names should be in all capital
|
|
2703
|
+
# letters.
|
|
2704
|
+
#
|
|
2705
|
+
# Examples:
|
|
2706
|
+
#
|
|
2707
|
+
# OLD [RFC7564]:
|
|
2708
|
+
#
|
|
2709
|
+
# However, the problem is made more serious by introducing the full
|
|
2710
|
+
# range of Unicode code points into protocol strings. For example,
|
|
2711
|
+
# the characters U+13DA U+13A2 U+13B5 U+13AC U+13A2 U+13AC U+13D2 from
|
|
2712
|
+
# the Cherokee block look similar to the ASCII characters "STPETER" as
|
|
2713
|
+
# they might appear when presented using a "creative" font family.
|
|
2714
|
+
#
|
|
2715
|
+
# NEW/ALLOWED:
|
|
2716
|
+
#
|
|
2717
|
+
# However, the problem is made more serious by introducing the full
|
|
2718
|
+
# range of Unicode code points into protocol strings. For example,
|
|
2719
|
+
# the characters U+13DA U+13A2 U+13B5 U+13AC U+13A2 U+13AC U+13D2
|
|
2720
|
+
# (ᏚᎢᎵᎬᎢᎬᏒ) from the Cherokee block look similar to the ASCII
|
|
2721
|
+
# characters "STPETER" as they might appear when presented using a
|
|
2722
|
+
# "creative" font family.
|
|
2723
|
+
#
|
|
2724
|
+
# ALSO ACCEPTABLE:
|
|
2725
|
+
#
|
|
2726
|
+
# However, the problem is made more serious by introducing the full
|
|
2727
|
+
# range of Unicode code points into protocol strings. For example,
|
|
2728
|
+
# the characters "ᏚᎢᎵᎬᎢᎬᏒ" (U+13DA U+13A2 U+13B5 U+13AC U+13A2
|
|
2729
|
+
# U+13AC U+13D2) from the Cherokee block look similar to the ASCII
|
|
2730
|
+
# characters "STPETER" as they might appear when presented using a
|
|
2731
|
+
# "creative" font family.
|
|
2732
|
+
#
|
|
2733
|
+
# Example of proper identification of Unicode characters in an RFC:
|
|
2734
|
+
#
|
|
2735
|
+
# Flanagan Expires October 27, 2016 [Page 6]
|
|
2736
|
+
#
|
|
2737
|
+
#
|
|
2738
|
+
# Internet-Draft non-ASCII in RFCs April 2016
|
|
2739
|
+
#
|
|
2740
|
+
#
|
|
2741
|
+
# Acceptable:
|
|
2742
|
+
#
|
|
2743
|
+
# o Temperature changes in the Temperature Control Protocol are
|
|
2744
|
+
# indicated by the U+2206 character.
|
|
2745
|
+
#
|
|
2746
|
+
# Preferred:
|
|
2747
|
+
#
|
|
2748
|
+
# 1. Temperature changes in the Temperature Control Protocol are
|
|
2749
|
+
# indicated by the U+2206 character ("Δ").
|
|
2750
|
+
#
|
|
2751
|
+
# 2. Temperature changes in the Temperature Control Protocol are
|
|
2752
|
+
# indicated by the U+2206 character (INCREMENT).
|
|
2753
|
+
#
|
|
2754
|
+
# 3. Temperature changes in the Temperature Control Protocol are
|
|
2755
|
+
# indicated by the U+2206 character ("Δ", INCREMENT).
|
|
2756
|
+
#
|
|
2757
|
+
# 4. Temperature changes in the Temperature Control Protocol are
|
|
2758
|
+
# indicated by the U+2206 character (INCREMENT, "Δ").
|
|
2759
|
+
#
|
|
2760
|
+
# 5. Temperature changes in the Temperature Control Protocol are
|
|
2761
|
+
# indicated by the "Delta" character "Δ" (U+2206).
|
|
2762
|
+
#
|
|
2763
|
+
# 6. Temperature changes in the Temperature Control Protocol are
|
|
2764
|
+
# indicated by the character "Δ" (INCREMENT, U+2206).
|
|
2765
|
+
def render_u(self, h, x):
|
|
2766
|
+
try:
|
|
2767
|
+
text = expand_unicode_element(x)
|
|
2768
|
+
except (RuntimeError, ValueError) as e:
|
|
2769
|
+
self.err(x, e)
|
|
2770
|
+
text = ''
|
|
2771
|
+
anchor = x.get('anchor')
|
|
2772
|
+
xref = self.root.find('.//xref[@target="%s"]'%anchor) if anchor else None
|
|
2773
|
+
if xref != None:
|
|
2774
|
+
# render only literal here
|
|
2775
|
+
text = x.text
|
|
2776
|
+
span = add.span(h, None, text, classes="unicode", id=anchor)
|
|
2777
|
+
span.tail = x.tail
|
|
2778
|
+
return span
|
|
2779
|
+
|
|
2780
|
+
# 9.64. <uri>
|
|
2781
|
+
#
|
|
2782
|
+
# This element is rendered as an HTML <div> containing the string
|
|
2783
|
+
# "URI:" and an HTML <a> element with the "href" attribute set to the
|
|
2784
|
+
# linked URI, CSS class of "url" (note that the value is "url", not
|
|
2785
|
+
# "uri" as one might expect), and the contents set to the linked URI.
|
|
2786
|
+
#
|
|
2787
|
+
# <div>URI:
|
|
2788
|
+
# <a href="http://www.example.com"
|
|
2789
|
+
# class="url">http://www.example.com</a>
|
|
2790
|
+
# </div>
|
|
2791
|
+
def render_uri(self, h, x):
|
|
2792
|
+
if not x.text:
|
|
2793
|
+
return None
|
|
2794
|
+
value = x.text.strip() if x.text else ''
|
|
2795
|
+
if value:
|
|
2796
|
+
cls = 'url'
|
|
2797
|
+
div = add.div(h, None,
|
|
2798
|
+
build.span("URI:"), '\n',
|
|
2799
|
+
build.a(value, href=value, classes=cls),
|
|
2800
|
+
classes=cls,
|
|
2801
|
+
)
|
|
2802
|
+
return div
|
|
2803
|
+
|
|
2804
|
+
# 9.65. <workgroup>
|
|
2805
|
+
#
|
|
2806
|
+
# This element does not add any direct output to HTML.
|
|
2807
|
+
render_workgroup = null_renderer # handled in render_rfc, when rendering the page top for drafts
|
|
2808
|
+
|
|
2809
|
+
# 9.66. <xref>
|
|
2810
|
+
#
|
|
2811
|
+
# This element is rendered as an HTML <a> element containing an
|
|
2812
|
+
# appropriate local link as the "href" attribute. The value of the
|
|
2813
|
+
# "href" attribute is taken from the "target" attribute, prepended by
|
|
2814
|
+
# "#". The <a> element generated will have class "xref". The contents
|
|
2815
|
+
# of the <a> element are the value of the "derivedContent" attribute.
|
|
2816
|
+
# If the "format" attribute has the value "default", and the "target"
|
|
2817
|
+
# attribute points to a <reference> or <referencegroup> element, then
|
|
2818
|
+
# the generated <a> element is surrounded by square brackets in the
|
|
2819
|
+
# output.
|
|
2820
|
+
#
|
|
2821
|
+
# <a class="xref" href="#target">Table 2</a>
|
|
2822
|
+
#
|
|
2823
|
+
# or
|
|
2824
|
+
#
|
|
2825
|
+
# [<a class="xref" href="#RFC1234">RFC1234</a>]
|
|
2826
|
+
#
|
|
2827
|
+
# Regarding HTML class assignment:
|
|
2828
|
+
# Most links here will get the "xref" class.
|
|
2829
|
+
# Relative links to sections of citations get "relref", but not "xref".
|
|
2830
|
+
# Links to citations/references (like "[RFC2119]") get the "cite" class.
|
|
2831
|
+
# Links to sections (or appendices) of this document get the "internal" class.
|
|
2832
|
+
# Links with default text (like "Section 1.3") get the "auto" class.
|
|
2833
|
+
# Links with format="none" get the "plain" class.
|
|
2834
|
+
def render_xref(self, h, x):
|
|
2835
|
+
# possible attributes:
|
|
2836
|
+
target = x.get('target')
|
|
2837
|
+
#pageno = x.get('pageno')
|
|
2838
|
+
format = x.get('format')
|
|
2839
|
+
section = x.get('section')
|
|
2840
|
+
relative= x.get('relative')
|
|
2841
|
+
#sformat = x.get('sectionFormat')
|
|
2842
|
+
reftext = x.get('derivedContent', '')
|
|
2843
|
+
in_name = len(list(x.iterancestors('name'))) > 0
|
|
2844
|
+
content = ''.join(x.itertext()).strip()
|
|
2845
|
+
if reftext is None:
|
|
2846
|
+
self.die(x, "Found an <%s> without derivedContent: %s" % (x.tag, lxml.etree.tostring(x),))
|
|
2847
|
+
if not (section or relative):
|
|
2848
|
+
if self.is_appendix(target):
|
|
2849
|
+
# link to xref using section fragment instead of ID
|
|
2850
|
+
target = id_for_pn(target)
|
|
2851
|
+
# plain xref
|
|
2852
|
+
if in_name:
|
|
2853
|
+
if format == 'none':
|
|
2854
|
+
reftext = content
|
|
2855
|
+
hh = build.span(reftext, classes='xref cite')
|
|
2856
|
+
elif target in self.refname_mapping and format != 'title':
|
|
2857
|
+
if content:
|
|
2858
|
+
hh = build.span(content, ' ', '[', reftext, ']', classes='xref cite')
|
|
2859
|
+
else:
|
|
2860
|
+
hh = build.span('[', reftext, ']', classes='xref cite')
|
|
2861
|
+
else:
|
|
2862
|
+
if content:
|
|
2863
|
+
hh = build.span(content, ' ', '(', reftext, ')', classes='xref cite')
|
|
2864
|
+
else:
|
|
2865
|
+
hh = build.span(reftext, classes='xref cite')
|
|
2866
|
+
else:
|
|
2867
|
+
srefclass = 'xref internal'
|
|
2868
|
+
if not content and format != 'title':
|
|
2869
|
+
srefclass = srefclass + ' auto'
|
|
2870
|
+
if reftext:
|
|
2871
|
+
if format == 'none':
|
|
2872
|
+
aa = build.a(href='#%s'%target, classes='xref plain')
|
|
2873
|
+
self.inline_text_renderer(aa, x)
|
|
2874
|
+
aa.tail = None
|
|
2875
|
+
hh = aa
|
|
2876
|
+
else:
|
|
2877
|
+
if target in self.refname_mapping and format != 'title':
|
|
2878
|
+
a = build.a(reftext, href='#%s'%target, classes='xref cite')
|
|
2879
|
+
if content:
|
|
2880
|
+
aa = build.a(href='#%s'%target, classes=srefclass)
|
|
2881
|
+
self.inline_text_renderer(aa, x)
|
|
2882
|
+
aa.tail = None
|
|
2883
|
+
hh = build.span(aa, ' [', a, ']')
|
|
2884
|
+
else:
|
|
2885
|
+
hh = build.span('[', a, ']')
|
|
2886
|
+
else:
|
|
2887
|
+
if content:
|
|
2888
|
+
aa = build.a(href='#%s'%target, classes=srefclass)
|
|
2889
|
+
self.inline_text_renderer(aa, x)
|
|
2890
|
+
aa.tail = None
|
|
2891
|
+
if format != 'title':
|
|
2892
|
+
srefclass = srefclass + ' auto'
|
|
2893
|
+
a = build.a(reftext, href='#%s'%target, classes=srefclass)
|
|
2894
|
+
hh = build.span(aa, ' (', a, ')')
|
|
2895
|
+
else:
|
|
2896
|
+
a = build.a(reftext, href='#%s'%target, classes=srefclass)
|
|
2897
|
+
hh = a
|
|
2898
|
+
else:
|
|
2899
|
+
a = build.a(href='#%s'%target, classes=srefclass)
|
|
2900
|
+
self.inline_text_renderer(a, x)
|
|
2901
|
+
hh = a
|
|
2902
|
+
hh.tail = x.tail
|
|
2903
|
+
h.append(hh)
|
|
2904
|
+
return hh
|
|
2905
|
+
else:
|
|
2906
|
+
label = 'Section' if section[0].isdigit() else 'Appendix' if re.search(r'^[A-Z](\.|$)', section) else 'Part'
|
|
2907
|
+
link = x.get('derivedLink')
|
|
2908
|
+
format = x.get('sectionFormat')
|
|
2909
|
+
exptext = f'{x.text.lstrip()}' if x.text else ''
|
|
2910
|
+
expchildren = list(x.getchildren())
|
|
2911
|
+
separator = ' ' if exptext != '' or len(expchildren) > 0 else ''
|
|
2912
|
+
# 9.44.1. displayFormat='of'
|
|
2913
|
+
#
|
|
2914
|
+
# The output is an <a class='relref'> HTML tag, with contents of
|
|
2915
|
+
# "Section " and the value of the "section" attribute. This is
|
|
2916
|
+
# followed by the word "of" (surrounded by whitespace). This is
|
|
2917
|
+
# followed by the <a class='xref'> HTML tag (surrounded by square
|
|
2918
|
+
# brackets).
|
|
2919
|
+
#
|
|
2920
|
+
# For example, with an input of:
|
|
2921
|
+
#
|
|
2922
|
+
# See <relref section="2.3" target="RFC9999" displayFormat="of"
|
|
2923
|
+
# derivedLink="http://www.rfc-editor.org/info/rfc9999#s-2.3"/>
|
|
2924
|
+
# for an overview.
|
|
2925
|
+
#
|
|
2926
|
+
# The HTML generated will be:
|
|
2927
|
+
#
|
|
2928
|
+
# See <a class="relref"
|
|
2929
|
+
# href="http://www.rfc-editor.org/info/rfc9999#s-2.3">Section
|
|
2930
|
+
# 2.3</a> of [<a class="xref" href="#RFC9999">RFC9999</a>]
|
|
2931
|
+
# for an overview.
|
|
2932
|
+
if format == 'of':
|
|
2933
|
+
span = add.span(h, None,
|
|
2934
|
+
build.a('%s %s'%(label, section), href=link, classes='relref'),
|
|
2935
|
+
' of ',
|
|
2936
|
+
exptext,
|
|
2937
|
+
*expchildren,
|
|
2938
|
+
separator,
|
|
2939
|
+
'[',
|
|
2940
|
+
build.a(reftext, href='#%s'%target, classes='xref cite'),
|
|
2941
|
+
']',
|
|
2942
|
+
)
|
|
2943
|
+
span.tail = x.tail
|
|
2944
|
+
return span
|
|
2945
|
+
|
|
2946
|
+
# 9.44.2. displayFormat='comma'
|
|
2947
|
+
#
|
|
2948
|
+
# The output is an <a class='xref'> HTML tag (wrapped by square
|
|
2949
|
+
# brackets), followed by a comma (","), followed by whitespace,
|
|
2950
|
+
# followed by an <a class='relref'> HTML tag, with contents of
|
|
2951
|
+
# "Section " and the value of the "section" attribute.
|
|
2952
|
+
#
|
|
2953
|
+
# For example, with an input of:
|
|
2954
|
+
#
|
|
2955
|
+
# See <relref section="2.3" target="RFC9999" displayFormat="comma"
|
|
2956
|
+
# derivedLink="http://www.rfc-editor.org/info/rfc9999#s-2.3"/>,
|
|
2957
|
+
# for an overview.
|
|
2958
|
+
#
|
|
2959
|
+
# The HTML generated will be:
|
|
2960
|
+
#
|
|
2961
|
+
# See [<a class="xref" href="#RFC9999">RFC9999</a>], <a class="relref"
|
|
2962
|
+
# href="http://www.rfc-editor.org/info/rfc9999#s-2.3">Section 2.3</a>,
|
|
2963
|
+
# for an overview.
|
|
2964
|
+
elif format == 'comma':
|
|
2965
|
+
span = add.span(h, None,
|
|
2966
|
+
exptext,
|
|
2967
|
+
*expchildren,
|
|
2968
|
+
separator,
|
|
2969
|
+
'[',
|
|
2970
|
+
build.a(reftext, href='#%s'%target, classes='xref cite'),
|
|
2971
|
+
'], ',
|
|
2972
|
+
build.a('%s %s'%(label, section), href=link, classes='relref'),
|
|
2973
|
+
)
|
|
2974
|
+
span.tail = x.tail
|
|
2975
|
+
return span
|
|
2976
|
+
|
|
2977
|
+
|
|
2978
|
+
# 9.44.3. displayFormat='parens'
|
|
2979
|
+
#
|
|
2980
|
+
# The output is an <a> element with "href" attribute whose value is the
|
|
2981
|
+
# value of the "target" attribute prepended by "#", and whose content
|
|
2982
|
+
# is the value of the "target" attribute; the entire element is wrapped
|
|
2983
|
+
# in square brackets. This is followed by whitespace. This is
|
|
2984
|
+
# followed by an <a> element whose "href" attribute is the value of the
|
|
2985
|
+
# "derivedLink" attribute and whose content is the value of the
|
|
2986
|
+
# "derivedRemoteContent" attribute; the entire element is wrapped in
|
|
2987
|
+
# parentheses.
|
|
2988
|
+
#
|
|
2989
|
+
# For example, if Section 2.3 of RFC 9999 has the title "Protocol
|
|
2990
|
+
# Overview", for an input of:
|
|
2991
|
+
#
|
|
2992
|
+
# See <relref section="2.3" target="RFC9999" displayFormat="parens"
|
|
2993
|
+
# derivedLink="http://www.rfc-editor.org/info/rfc9999#s-2.3"
|
|
2994
|
+
# derivedRemoteContent="Section 2.3"/> for an overview.
|
|
2995
|
+
#
|
|
2996
|
+
# The HTML generated will be:
|
|
2997
|
+
#
|
|
2998
|
+
# See [<a class="relref" href="#RFC9999">RFC9999</a>]
|
|
2999
|
+
# (<a class="relref"
|
|
3000
|
+
# href="http://www.rfc-editor.org/info/rfc9999#s-2.3">Section
|
|
3001
|
+
# 2.3</a>) for an overview.
|
|
3002
|
+
elif format == 'parens':
|
|
3003
|
+
span = add.span(h, None,
|
|
3004
|
+
exptext,
|
|
3005
|
+
*expchildren,
|
|
3006
|
+
separator,
|
|
3007
|
+
'[',
|
|
3008
|
+
build.a(reftext, href='#%s'%target, classes='xref cite'),
|
|
3009
|
+
'] (',
|
|
3010
|
+
build.a('%s %s'%(label, section), href=link, classes='relref'),
|
|
3011
|
+
')',
|
|
3012
|
+
)
|
|
3013
|
+
span.tail = x.tail
|
|
3014
|
+
return span
|
|
3015
|
+
|
|
3016
|
+
#
|
|
3017
|
+
# 9.44.4. displayFormat='bare'
|
|
3018
|
+
#
|
|
3019
|
+
# The output is an <a> element whose "href" attribute is the value of
|
|
3020
|
+
# the "derivedLink" attribute and whose content is the value of the
|
|
3021
|
+
# "derivedRemoteContent" attribute.
|
|
3022
|
+
#
|
|
3023
|
+
# For this input:
|
|
3024
|
+
#
|
|
3025
|
+
# See <relref section="2.3" target="RFC9999" displayFormat="bare"
|
|
3026
|
+
# derivedLink="http://www.rfc-editor.org/info/rfc9999#s-2.3"
|
|
3027
|
+
# derivedRemoteContent="Section 2.3"/> and ...
|
|
3028
|
+
#
|
|
3029
|
+
# The HTML generated will be:
|
|
3030
|
+
#
|
|
3031
|
+
# See <a class="relref"
|
|
3032
|
+
# href="http://www.rfc-editor.org/info/rfc9999#s-2.3">Section
|
|
3033
|
+
# 2.3</a> and ...
|
|
3034
|
+
elif format == 'bare':
|
|
3035
|
+
a = build.a(section, href=link, classes='relref')
|
|
3036
|
+
if content:
|
|
3037
|
+
hh = build.span(
|
|
3038
|
+
a,
|
|
3039
|
+
' (',
|
|
3040
|
+
build.a(exptext, *expchildren, href=link, classes='relref'),
|
|
3041
|
+
')',
|
|
3042
|
+
)
|
|
3043
|
+
else:
|
|
3044
|
+
hh = a
|
|
3045
|
+
hh.tail = x.tail
|
|
3046
|
+
h.append(hh)
|
|
3047
|
+
return hh
|
|
3048
|
+
else:
|
|
3049
|
+
self.err(x, 'Unexpected value combination: section: %s relative: %s format: %s' %(section, relative, format))
|
|
3050
|
+
|
|
3051
|
+
|
|
3052
|
+
# --------------------------------------------------------------------------
|
|
3053
|
+
# Post processing
|
|
3054
|
+
def post_process(self, h):
|
|
3055
|
+
for x in h.iter():
|
|
3056
|
+
x = self.sort_classes(x)
|
|
3057
|
+
if x.text and x.text.strip() and '\u2028' in x.text:
|
|
3058
|
+
parts = x.text.split('\u2028')
|
|
3059
|
+
x.text = parts[0]
|
|
3060
|
+
i = 0
|
|
3061
|
+
for t in parts[1:]:
|
|
3062
|
+
br = build.br()
|
|
3063
|
+
br.tail = t
|
|
3064
|
+
x.insert(i, br)
|
|
3065
|
+
i += 1
|
|
3066
|
+
if x.tail and x.tail.strip() and '\u2028' in x.tail:
|
|
3067
|
+
p = x.getparent()
|
|
3068
|
+
i = p.index(x)+1
|
|
3069
|
+
parts = x.tail.split('\u2028')
|
|
3070
|
+
x.tail = parts[0]
|
|
3071
|
+
for t in parts[1:]:
|
|
3072
|
+
br = build.br()
|
|
3073
|
+
br.tail = t
|
|
3074
|
+
p.insert(i, br)
|
|
3075
|
+
i += 1
|
|
3076
|
+
return h
|
|
3077
|
+
|
|
3078
|
+
|
|
3079
|
+
# --------------------------------------------------------------------------
|
|
3080
|
+
# Sort class values
|
|
3081
|
+
def sort_classes(self, element):
|
|
3082
|
+
classes = element.get('class', None)
|
|
3083
|
+
if classes:
|
|
3084
|
+
classes_set = classes.split(' ')
|
|
3085
|
+
if len(classes_set) > 1:
|
|
3086
|
+
classes_set.sort()
|
|
3087
|
+
classes = ' '.join(classes_set)
|
|
3088
|
+
element.set('class', classes)
|
|
3089
|
+
return element
|
|
3090
|
+
|
|
3091
|
+
def get_font_family(self, font_family):
|
|
3092
|
+
if font_family is None or font_family == 'serif':
|
|
3093
|
+
return 'Noto Serif'
|
|
3094
|
+
elif font_family == 'sans-serif':
|
|
3095
|
+
return 'Noto Sans'
|
|
3096
|
+
elif font_family == 'monospace':
|
|
3097
|
+
return 'Roboto Mono'
|
|
3098
|
+
else:
|
|
3099
|
+
return 'inherit'
|
|
3100
|
+
|
|
3101
|
+
|
|
3102
|
+
def set_font_family(self, svg):
|
|
3103
|
+
for child in svg:
|
|
3104
|
+
if child is not None:
|
|
3105
|
+
|
|
3106
|
+
font_family = child.get('font-family')
|
|
3107
|
+
if font_family:
|
|
3108
|
+
child.set('font-family', self.get_font_family(font_family))
|
|
3109
|
+
self.set_font_family(child)
|
|
3110
|
+
return svg
|