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.
Files changed (71) hide show
  1. xml2rfc/__init__.py +68 -0
  2. xml2rfc/boilerplate_id_guidelines.py +28 -0
  3. xml2rfc/boilerplate_rfc_7841.py +575 -0
  4. xml2rfc/boilerplate_tlp.py +258 -0
  5. xml2rfc/data/SVG-1.2-RFC.rnc +1666 -0
  6. xml2rfc/data/SVG-1.2-RFC.rng +7460 -0
  7. xml2rfc/data/Scripts.txt +2693 -0
  8. xml2rfc/data/metadata.js +218 -0
  9. xml2rfc/data/metadata.min.js +1 -0
  10. xml2rfc/data/reference.rnc +955 -0
  11. xml2rfc/data/reference.rng +2158 -0
  12. xml2rfc/data/referencegroup.rnc +16 -0
  13. xml2rfc/data/referencegroup.rng +2180 -0
  14. xml2rfc/data/rfc7991.rnc +955 -0
  15. xml2rfc/data/rfc7991.rng +2143 -0
  16. xml2rfc/data/v2.rnc +309 -0
  17. xml2rfc/data/v2.rng +720 -0
  18. xml2rfc/data/v3.rnc +1144 -0
  19. xml2rfc/data/v3.rng +2538 -0
  20. xml2rfc/data/v3compat.dtd +630 -0
  21. xml2rfc/data/v3compat.rnc +985 -0
  22. xml2rfc/data/xml2rfc-v3-emacs-nxml.rnc +1083 -0
  23. xml2rfc/data/xml2rfc.css +1171 -0
  24. xml2rfc/data/xml2rfc.js +7 -0
  25. xml2rfc/debug.py +221 -0
  26. xml2rfc/log.py +54 -0
  27. xml2rfc/parser.py +815 -0
  28. xml2rfc/run.py +801 -0
  29. xml2rfc/scripts.py +52 -0
  30. xml2rfc/strings.py +26 -0
  31. xml2rfc/templates/address_card.html +21 -0
  32. xml2rfc/templates/base.html +415 -0
  33. xml2rfc/templates/doc.xml +367 -0
  34. xml2rfc/templates/doc.yaml +2138 -0
  35. xml2rfc/templates/rfc2629-other.ent +61 -0
  36. xml2rfc/templates/rfc2629-xhtml.ent +170 -0
  37. xml2rfc/templates/rfc2629.dtd +328 -0
  38. xml2rfc/uniscripts/__init__.py +97 -0
  39. xml2rfc/uniscripts/unidata.py +306 -0
  40. xml2rfc/uniscripts/update/make_unidata.py +94 -0
  41. xml2rfc/util/__init__.py +7 -0
  42. xml2rfc/util/date.py +94 -0
  43. xml2rfc/util/fonts.py +53 -0
  44. xml2rfc/util/name.py +229 -0
  45. xml2rfc/util/num.py +119 -0
  46. xml2rfc/util/postal.py +285 -0
  47. xml2rfc/util/unicode.py +441 -0
  48. xml2rfc/utils.py +672 -0
  49. xml2rfc/walkpdf.py +113 -0
  50. xml2rfc/writers/__init__.py +25 -0
  51. xml2rfc/writers/base.py +2248 -0
  52. xml2rfc/writers/bib.py +43 -0
  53. xml2rfc/writers/doc.py +354 -0
  54. xml2rfc/writers/expand.py +51 -0
  55. xml2rfc/writers/expanded_xml.py +39 -0
  56. xml2rfc/writers/html.py +3110 -0
  57. xml2rfc/writers/legacy_html.py +860 -0
  58. xml2rfc/writers/nroff.py +345 -0
  59. xml2rfc/writers/paginated_txt.py +432 -0
  60. xml2rfc/writers/pdf.py +230 -0
  61. xml2rfc/writers/preptool.py +2428 -0
  62. xml2rfc/writers/raw_txt.py +1453 -0
  63. xml2rfc/writers/text.py +5000 -0
  64. xml2rfc/writers/unprep.py +187 -0
  65. xml2rfc/writers/v2v3.py +1150 -0
  66. xml2rfc-3.26.0.dist-info/LICENSE +29 -0
  67. xml2rfc-3.26.0.dist-info/METADATA +178 -0
  68. xml2rfc-3.26.0.dist-info/RECORD +71 -0
  69. xml2rfc-3.26.0.dist-info/WHEEL +5 -0
  70. xml2rfc-3.26.0.dist-info/entry_points.txt +2 -0
  71. xml2rfc-3.26.0.dist-info/top_level.txt +1 -0
@@ -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">&para;</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>,&nbsp;
753
+ ## <span class="region">CO</span>&nbsp;
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 &lt;, &gt;, and
821
+ # &amp;, 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
+ # &lt; hello, world &gt;
831
+ # --------------
832
+ # \ ^__^
833
+ # \ (oo)\_______
834
+ # (__)\ )\/\
835
+ # ||----w |
836
+ # || ||
837
+ # </pre>
838
+ # <a class="pilcrow" href="#s-1-2">&para;</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">&para;</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">&para;</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">&para;</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 "&mdash;" 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">&para;</a>
1295
+ # </p>
1296
+ # <cite>&mdash; <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">&para;</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">&para;</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">&para;</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">&para;</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 &lt;, &gt;, and &amp;,
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 &lt;stdio.h&gt;
2458
+ #
2459
+ # int main(void)
2460
+ # {
2461
+ # printf(&quot;hello, world\n&quot;);
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">&para;</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