Coverage for /builds/ahmed.baizid.0/gtk-doc/gtkdoc/mkhtml2.py: 16%
1025 statements
« prev ^ index » next coverage.py v6.5.0, created at 2024-11-05 19:25 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2024-11-05 19:25 +0000
1#!/usr/bin/env python3
2# -*- python; coding: utf-8 -*-
3#
4# gtk-doc - GTK DocBook documentation generator.
5# Copyright (C) 2018 Stefan Sauer
6#
7# This program is free software; you can redistribute it and/or modify
8# it under the terms of the GNU General Public License as published by
9# the Free Software Foundation; either version 2 of the License, or
10# (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU General Public License for more details.
16#
17# You should have received a copy of the GNU General Public License
18# along with this program; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20#
22"""Generate html from docbook
24The tool loads the main xml document (<module>-docs.xml) and chunks it
25like the xsl-stylesheets would do. For that it resolves all the xml-includes.
26Each chunk is converted to html using python functions.
28In contrast to our previous approach of running gtkdoc-mkhtml + gtkdoc-fixxref,
29this tools will replace both without relying on external tools such as xsltproc
30and source-highlight.
32Please note, that we're not aiming for complete docbook-xml support. All tags
33used in the generated xml are of course handled. More tags used in handwritten
34xml can be easily supported, but for some combinations of tags we prefer
35simplicity.
37TODO:
38- tag converters:
39 - 'section'/'simplesect' - the first we convert as a chunk, the nested ones we
40 need to convert as 'sect{2,3,4,...}, we can track depth in 'ctx'
41 - inside 'footnote' one can have many tags, we only handle 'para'/'simpara'
42 - inside 'glossentry' we're only handling 'glossterm' and 'glossdef'
43 - convert_{figure,table} need counters.
44- check each docbook tag if it can contain #PCDATA, if not don't check for
45 xml.text/xml.tail and add a comment (# no PCDATA allowed here)
46- find a better way to print context for warnings
47 - we use 'xml.sourceline', but this all does not help a lot due to xi:include
48- consolidate title handling:
49 - always use the titles-dict
50 - convert_title(): uses titles.get(tid)['title']
51 - convert_xref(): uses titles[tid]['tag'], ['title'] and ['xml']
52 - create_devhelp2_refsect2_keyword(): uses titles[tid]['title']
53 - there only store what we have (xml, tag, ...)
54 - when chunking generate 'id's and add entries to titles-dict
55 - add accessors for title and raw_title that lazily get them
56 - see if any of the other ~10 places that call convert_title() could use this
57 cache
58- performance
59 - consider some perf-warnings flag
60 - see 'No "id" attribute on'
61 - xinclude processing in libxml2 is slow
62 - if we disable it, we get '{http://www.w3.org/2003/XInclude}include' tags
63 and we could try handling them ourself, in some cases those are subtrees
64 that we extract for chunking anyway
66DIFFERENCES:
67- titles
68 - we add the chunk label to the title in toc, on the page and in nav tooltips
69 - docbook xsl only sometimes adds the label to the titles and when it does it
70 adds name chunk type too (e.g. 'Part I.' instead of 'I.')
71- navigation
72 - we always add an up-link except on the first page
73- footer
74 - we're nov omitting the footer
75- tocs
76 - we always add "Table of Contents' before a toc
77 - docbook does that for some pages, it is configurable
79OPTIONAL:
80- minify html: https://pypi.python.org/pypi/htmlmin/
82Requirements:
83sudo pip3 install anytree lxml pygments
85Example invocation:
86cd tests/bugs/docs/
87mkdir -p db2html
88cd dbhtml
89../../../../gtkdoc-mkhtml2 tester ../tester-docs.xml
90cd ..
91xdg-open db2html/index.html
92meld html db2html
94Benchmarking:
95cd tests/bugs/docs/;
96rm html-build.stamp; time make html-build.stamp
97"""
99import argparse
100import logging
101import os
102import shutil
103import sys
105from copy import deepcopy
106from glob import glob
107from lxml import etree
108from timeit import default_timer as timer
110from . import config, highlight, fixxref
113class ChunkParams(object):
114 def __init__(self, prefix, parent=None, min_idx=0):
115 self.prefix = prefix
116 self.parent = parent
117 self.min_idx = min_idx
118 self.idx = 1
121DONT_CHUNK = float('inf')
122# docbook-xsl defines the chunk tags here.
123# http://www.sagehill.net/docbookxsl/Chunking.html#GeneratedFilenames
124# https://github.com/oreillymedia/HTMLBook/blob/master/htmlbook-xsl/chunk.xsl#L33
125# If not defined, we can just create an example without an 'id' attr and see
126# docbook xsl does.
127#
128# For toc levels see http://www.sagehill.net/docbookxsl/TOCcontrol.html
129# TODO: this list has also a flag that controls whether we add the
130# 'Table of Contents' heading in convert_chunk_with_toc()
131CHUNK_PARAMS = {
132 'appendix': ChunkParams('app', 'book'),
133 'book': ChunkParams('bk'),
134 'chapter': ChunkParams('ch', 'book'),
135 'glossary': ChunkParams('go', 'book'),
136 'index': ChunkParams('ix', 'book'),
137 'part': ChunkParams('pt', 'book'),
138 'preface': ChunkParams('pr', 'book'),
139 'refentry': ChunkParams('re', 'book'),
140 'reference': ChunkParams('rn', 'book'),
141 'sect1': ChunkParams('s', 'chapter', 1),
142 'section': ChunkParams('s', 'chapter', 1),
143 'sect2': ChunkParams('s', 'sect1', DONT_CHUNK),
144 'sect3': ChunkParams('s', 'sect2', DONT_CHUNK),
145 'sect4': ChunkParams('s', 'sect3', DONT_CHUNK),
146 'sect5': ChunkParams('s', 'sect4', DONT_CHUNK),
147}
148# TAGS we don't support:
149# 'article', 'bibliography', 'colophon', 'set', 'setindex'
151TITLE_XPATHS = {
152 '_': (etree.XPath('./title'), None),
153 'book': (etree.XPath('./bookinfo/title'), None),
154 'refentry': (
155 etree.XPath('./refmeta/refentrytitle'),
156 etree.XPath('./refnamediv/refpurpose')
157 ),
158}
160ID_XPATH = etree.XPath('//*[@id]')
162GLOSSENTRY_XPATH = etree.XPath('//glossentry')
163glossary = {}
165footnote_idx = 1
167# nested dict with subkeys:
168# title: textual title
169# tag: chunk tag
170# xml: title xml node
171titles = {}
173# files to copy
174assets = set()
177def encode_entities(text):
178 return text.replace('&', '&').replace('<', '<').replace('>', '>')
181def raw_text(xml):
182 return etree.tostring(xml, method="text", encoding=str).strip()
185def gen_chunk_name(node, chunk_params):
186 """Generate a chunk file name
188 This is either based on the id or on the position in the doc. In the latter
189 case it uses a prefix from CHUNK_PARAMS and a sequence number for each chunk
190 type.
191 """
192 idval = node.attrib.get('id')
193 if idval is not None:
194 return idval
196 name = ('%s%02d' % (chunk_params.prefix, chunk_params.idx))
197 chunk_params.idx += 1
199 # handle parents to make names of nested tags like in docbook
200 # - we only need to prepend the parent if there are > 1 of them in the
201 # xml. None, the parents we have are not sufficient, e.g. 'index' can
202 # be in 'book' or 'part' or ... Maybe we can track the chunk_parents
203 # when we chunk explicitly and on each level maintain the 'idx'
204 # while chunk_params.parent:
205 # parent = chunk_params.parent
206 # if parent not in CHUNK_PARAMS:
207 # break;
208 # chunk_params = CHUNK_PARAMS[parent]
209 # name = ('%s%02d' % (chunk_params.prefix, chunk_params.idx)) + name
211 logging.info('Gen chunk name: "%s"', name)
212 return name
215def get_chunk_titles(module, node, tree_node):
216 tag = node.tag
217 (title, subtitle) = TITLE_XPATHS.get(tag, TITLE_XPATHS['_'])
219 ctx = {
220 'module': module,
221 'files': [],
222 'node': tree_node,
223 }
224 result = {
225 'title': None,
226 'title_tag': None,
227 'subtitle': None,
228 'subtitle_tag': None
229 }
230 res = title(node)
231 if res:
232 # handle chunk label for tocs
233 label = node.attrib.get('label')
234 if label:
235 label += '. '
236 else:
237 label = ''
239 xml = res[0]
240 # TODO: consider to eval 'title'/'raw_title' lazily
241 result['title'] = label + ''.join(convert_title(ctx, xml))
242 result['raw_title'] = encode_entities(raw_text(xml))
243 if xml.tag != 'title':
244 result['title_tag'] = xml.tag
245 else:
246 result['title_tag'] = tag
248 if subtitle:
249 res = subtitle(node)
250 if res:
251 xml = res[0]
252 result['subtitle'] = ''.join(convert_title(ctx, xml))
253 result['subtitle_tag'] = xml.tag
254 return result
257class PreOrderIter:
258 def __init__(self, node):
259 self.__node = node
261 def __iter__(self):
262 yield self.__node
264 for child in self.__node.descendants:
265 yield child
268class Node:
269 def __init__(self, name, parent=None, **kwargs):
270 self.name = name
271 self.__root = None
272 self.__attrs = kwargs
273 self.children = []
275 assert parent is None or isinstance(parent, Node)
276 self.parent = parent
277 if parent is not None:
278 self.__root = parent.root
279 parent.children.append(self)
281 @property
282 def root(self):
283 return self.__root or self
285 @property
286 def descendants(self):
287 ret = []
289 for child in self.children:
290 ret.append(child)
292 for other in child.descendants:
293 ret.append(other)
295 return ret
297 def __iter__(self):
298 for child in self.children:
299 yield child
301 def __getattr__(self, name):
302 try:
303 return self.__attrs[name]
304 except KeyError as e:
305 raise AttributeError(str(e))
308def chunk(xml_node, module, depth=0, idx=0, parent=None):
309 """Chunk the tree.
311 The first time, we're called with parent=None and in that case we return
312 the new_node as the root of the tree. For each tree-node we generate a
313 filename and process the children.
314 """
315 tag = xml_node.tag
316 chunk_params = CHUNK_PARAMS.get(tag)
317 if chunk_params:
318 title_args = get_chunk_titles(module, xml_node, parent)
319 chunk_name = gen_chunk_name(xml_node, chunk_params)
321 # check idx to handle 'sect1'/'section' special casing and title-only
322 # segments
323 if idx >= chunk_params.min_idx:
324 logging.info('chunk tag: "%s"[%d]', tag, idx)
325 if parent:
326 # remove the xml-node from the parent
327 sub_tree = etree.ElementTree(deepcopy(xml_node)).getroot()
328 xml_node.getparent().remove(xml_node)
329 xml_node = sub_tree
331 parent = Node(tag, parent=parent, xml=xml_node, depth=depth,
332 idx=idx,
333 filename=chunk_name + '.html', anchor=None,
334 **title_args)
335 else:
336 parent = Node(tag, parent=parent, xml=xml_node, depth=depth,
337 idx=idx,
338 filename=parent.filename, anchor='#' + chunk_name,
339 **title_args)
341 depth += 1
342 idx = 0
343 for child in xml_node:
344 chunk(child, module, depth, idx, parent)
345 if child.tag in CHUNK_PARAMS:
346 idx += 1
348 return parent
351def add_id_links_and_titles(files, links):
352 for node in files:
353 chunk_name = node.filename[:-5]
354 chunk_base = node.filename + '#'
355 for elem in ID_XPATH(node.xml):
356 attr = elem.attrib['id']
357 if attr == chunk_name:
358 links[attr] = node.filename
359 else:
360 links[attr] = chunk_base + attr
362 title = TITLE_XPATHS.get(elem.tag, TITLE_XPATHS['_'])[0]
363 res = title(elem)
364 if res:
365 xml = res[0]
366 # TODO: consider to eval 'title' lazily
367 titles[attr] = {
368 'title': encode_entities(raw_text(xml)),
369 'xml': xml,
370 'tag': elem.tag,
371 }
374def build_glossary(files):
375 for node in files:
376 if node.xml.tag != 'glossary':
377 continue
378 for term in GLOSSENTRY_XPATH(node.xml):
379 # TODO: there can be all kind of things in a glossary. This only supports
380 # what we commonly use, glossterm is mandatory
381 key_node = term.find('glossterm')
382 val_node = term.find('glossdef')
383 if key_node is not None and val_node is not None:
384 glossary[raw_text(key_node)] = raw_text(val_node)
385 else:
386 debug = []
387 if key_node is None:
388 debug.append('missing key')
389 if val_node is None:
390 debug.append('missing val')
391 logging.warning('Broken glossentry "%s": %s',
392 term.attrib['id'], ','.join(debug))
395# conversion helpers
398def convert_inner(ctx, xml, result):
399 for child in xml:
400 result.extend(convert_tags.get(child.tag, convert__unknown)(ctx, child))
403def convert_ignore(ctx, xml):
404 result = []
405 convert_inner(ctx, xml, result)
406 return result
409def convert_skip(ctx, xml):
410 return []
413def append_idref(attrib, result):
414 idval = attrib.get('id')
415 if idval is not None:
416 result.append('<a name="%s"></a>' % idval)
419def append_text(ctx, text, result):
420 if text and ('no-strip' in ctx or text.strip()):
421 result.append(encode_entities(text))
424missing_tags = {}
427def convert__unknown(ctx, xml):
428 # don't recurse on subchunks
429 if xml.tag in CHUNK_PARAMS:
430 return []
431 if isinstance(xml, etree._Comment):
432 return ['<!-- ' + xml.text + '-->\n']
433 else:
434 # warn only once
435 if xml.tag not in missing_tags:
436 logging.warning('Add tag converter for "%s"', xml.tag)
437 missing_tags[xml.tag] = True
438 result = ['<!-- ' + xml.tag + '-->\n']
439 convert_inner(ctx, xml, result)
440 result.append('<!-- /' + xml.tag + '-->\n')
441 return result
444def convert_mediaobject_children(ctx, xml, result):
445 # look for textobject/phrase
446 alt_text = ''
447 textobject = xml.find('textobject')
448 if textobject is not None:
449 phrase = textobject.findtext('phrase')
450 if phrase:
451 alt_text = ' alt="%s"' % phrase
453 # look for imageobject/imagedata
454 imageobject = xml.find('imageobject')
455 if imageobject is not None:
456 imagedata = imageobject.find('imagedata')
457 if imagedata is not None:
458 # TODO(ensonic): warn on missing fileref attr?
459 fileref = imagedata.attrib.get('fileref', '')
460 if fileref:
461 assets.add(fileref)
462 result.append('<img src="%s"%s>' % (fileref, alt_text))
465def convert_sect(ctx, xml, h_tag, inner_func=convert_inner):
466 result = ['<div class="%s">\n' % xml.tag]
467 title_tag = xml.find('title')
468 if title_tag is not None:
469 append_idref(xml.attrib, result)
470 result.append('<%s>%s</%s>' % (
471 h_tag, ''.join(convert_title(ctx, title_tag)), h_tag))
472 append_text(ctx, xml.text, result)
473 inner_func(ctx, xml, result)
474 result.append('</div>')
475 append_text(ctx, xml.tail, result)
476 return result
479def xml_get_title(ctx, xml):
480 title_tag = xml.find('title')
481 if title_tag is not None:
482 return ''.join(convert_title(ctx, title_tag))
483 else:
484 logging.warning('%s: Expected title tag under "%s %s"', xml.sourceline, xml.tag, str(xml.attrib))
485 return ''
488# docbook tags
491def convert_abstract(ctx, xml):
492 result = ["""<div class="abstract">
493 <p class="title"><b>Abstract</b></p>"""]
494 append_text(ctx, xml.text, result)
495 convert_inner(ctx, xml, result)
496 result.append('</div>')
497 append_text(ctx, xml.tail, result)
498 return result
501def convert_acronym(ctx, xml):
502 key = xml.text
503 title = glossary.get(key, '')
504 # TODO: print a sensible warning if missing
505 result = ['<acronym title="%s"><span class="acronym">%s</span></acronym>' % (title, key)]
506 if xml.tail:
507 result.append(xml.tail)
508 return result
511def convert_anchor(ctx, xml):
512 return ['<a name="%s"></a>' % xml.attrib['id']]
515def convert_bookinfo(ctx, xml):
516 result = ['<div class="titlepage">']
517 convert_inner(ctx, xml, result)
518 result.append("""<hr>
519</div>""")
520 if xml.tail:
521 result.append(xml.tail)
522 return result
525def convert_blockquote(ctx, xml):
526 result = ['<div class="blockquote">\n<blockquote class="blockquote">']
527 append_text(ctx, xml.text, result)
528 convert_inner(ctx, xml, result)
529 result.append('</blockquote>\n</div>')
530 append_text(ctx, xml.tail, result)
531 return result
534def convert_code(ctx, xml):
535 result = ['<code class="%s">' % xml.tag]
536 append_text(ctx, xml.text, result)
537 convert_inner(ctx, xml, result)
538 result.append('</code>')
539 append_text(ctx, xml.tail, result)
540 return result
543def convert_colspec(ctx, xml):
544 result = ['<col']
545 colname = xml.attrib.get('colname')
546 if colname is not None:
547 result.append(' class="%s"' % colname)
548 colwidth = xml.attrib.get('colwidth')
549 if colwidth is not None:
550 result.append(' width="%s"' % colwidth)
551 result.append('>\n')
552 # is in tgroup and there can be no 'text'
553 return result
556def convert_command(ctx, xml):
557 result = ['<strong class="userinput"><code>']
558 append_text(ctx, xml.text, result)
559 convert_inner(ctx, xml, result)
560 result.append('</code></strong>')
561 append_text(ctx, xml.tail, result)
562 return result
565def convert_corpauthor(ctx, xml):
566 result = ['<div><h3 class="corpauthor">\n']
567 append_text(ctx, xml.text, result)
568 convert_inner(ctx, xml, result)
569 result.append('</h3></div>\n')
570 append_text(ctx, xml.tail, result)
571 return result
574def convert_div(ctx, xml):
575 result = ['<div class="%s">\n' % xml.tag]
576 append_text(ctx, xml.text, result)
577 convert_inner(ctx, xml, result)
578 result.append('</div>')
579 append_text(ctx, xml.tail, result)
580 return result
583def convert_emphasis(ctx, xml):
584 role = xml.attrib.get('role')
585 if role is not None:
586 result = ['<span class="%s">' % role]
587 end = '</span>'
588 else:
589 result = ['<span class="emphasis"><em>']
590 end = '</em></span>'
591 append_text(ctx, xml.text, result)
592 convert_inner(ctx, xml, result)
593 result.append(end)
594 append_text(ctx, xml.tail, result)
595 return result
598def convert_em(ctx, xml):
599 result = ['<em class="%s">' % xml.tag]
600 append_text(ctx, xml.text, result)
601 convert_inner(ctx, xml, result)
602 result.append('</em>')
603 append_text(ctx, xml.tail, result)
604 return result
607def convert_em_code(ctx, xml):
608 result = ['<em class="%s"><code>' % xml.tag]
609 append_idref(xml.attrib, result)
610 append_text(ctx, xml.text, result)
611 convert_inner(ctx, xml, result)
612 result.append('</code></em>')
613 append_text(ctx, xml.tail, result)
614 return result
617def convert_entry(ctx, xml):
618 entry_type = ctx['table.entry']
619 result = ['<' + entry_type]
620 role = xml.attrib.get('role')
621 if role is not None:
622 result.append(' class="%s"' % role)
623 morerows = xml.attrib.get('morerows')
624 if morerows is not None:
625 result.append(' rowspan="%s"' % (1 + int(morerows)))
626 result.append('>')
627 append_text(ctx, xml.text, result)
628 convert_inner(ctx, xml, result)
629 result.append('</' + entry_type + '>')
630 append_text(ctx, xml.tail, result)
631 return result
634def convert_figure(ctx, xml):
635 result = ['<div class="figure">\n']
636 append_idref(xml.attrib, result)
637 title_tag = xml.find('title')
638 if title_tag is not None:
639 # TODO(ensonic): Add a 'Figure X. ' prefix, needs a figure counter
640 result.append('<p><b>%s</b></p>' % ''.join(convert_title(ctx, title_tag)))
641 result.append('<div class="figure-contents">')
642 # TODO(ensonic): title can become alt on inner 'graphic' element
643 convert_inner(ctx, xml, result)
644 result.append('</div></div><br class="figure-break"/>')
645 append_text(ctx, xml.tail, result)
646 return result
649def convert_footnote(ctx, xml):
650 footnotes = ctx.get('footnotes', [])
651 # footnotes idx is not per page, but per doc
652 global footnote_idx
653 idx = footnote_idx
654 footnote_idx += 1
656 # need a pair of ids for each footnote (docbook generates different ids)
657 this_id = 'footnote-%d' % idx
658 that_id = 'ftn.' + this_id
660 inner = ['<div id="%s" class="footnote">' % that_id]
661 inner.append('<p><a href="#%s" class="para"><sup class="para">[%d] </sup></a>' % (
662 this_id, idx))
663 # TODO(ensonic): this can contain all kind of tags, if we convert them we'll
664 # get double nested paras :/.
665 # convert_inner(ctx, xml, inner)
666 para = xml.find('para')
667 if para is None:
668 para = xml.find('simpara')
669 if para is not None:
670 inner.append(para.text)
671 else:
672 logging.warning('%s: Unhandled footnote content: %s', xml.sourceline, raw_text(xml))
673 inner.append('</p></div>')
674 footnotes.append(inner)
675 ctx['footnotes'] = footnotes
676 return ['<a href="#%s" class="footnote" name="%s"><sup class="footnote">[%s]</sup></a>' % (
677 that_id, this_id, idx)]
680def convert_formalpara(ctx, xml):
681 result = None
682 title_tag = xml.find('title')
683 result = ['<p><b>%s</b>' % ''.join(convert_title(ctx, title_tag))]
684 para_tag = xml.find('para')
685 append_text(ctx, para_tag.text, result)
686 convert_inner(ctx, para_tag, result)
687 append_text(ctx, para_tag.tail, result)
688 result.append('</p>')
689 append_text(ctx, xml.tail, result)
690 return result
693def convert_glossdef(ctx, xml):
694 result = ['<dd class="glossdef">']
695 convert_inner(ctx, xml, result)
696 result.append('</dd>\n')
697 return result
700def convert_glossdiv(ctx, xml):
701 title_tag = xml.find('title')
702 title = title_tag.text
703 xml.remove(title_tag)
704 result = [
705 '<a name="gls%s"></a><h3 class="title">%s</h3>' % (title, title)
706 ]
707 convert_inner(ctx, xml, result)
708 return result
711def convert_glossentry(ctx, xml):
712 result = []
713 convert_inner(ctx, xml, result)
714 return result
717def convert_glossterm(ctx, xml):
718 glossid = ''
719 text = ''
720 anchor = xml.find('anchor')
721 if anchor is not None:
722 glossid = anchor.attrib.get('id', '')
723 text += anchor.tail or ''
724 text += xml.text or ''
725 if glossid == '':
726 glossid = 'glossterm-' + text
727 return [
728 '<dt><span class="glossterm"><a name="%s"></a>%s</span></dt>' % (
729 glossid, text)
730 ]
733def convert_graphic(ctx, xml):
734 # TODO(ensonic): warn on missing fileref attr?
735 fileref = xml.attrib.get('fileref', '')
736 if fileref:
737 assets.add(fileref)
738 return ['<div><img src="%s"></div>' % fileref]
741def convert_indexdiv(ctx, xml):
742 title_tag = xml.find('title')
743 title = title_tag.text
744 xml.remove(title_tag)
745 result = [
746 '<a name="idx%s"></a><h3 class="title">%s</h3>' % (title, title)
747 ]
748 convert_inner(ctx, xml, result)
749 return result
752def convert_informaltable(ctx, xml):
753 result = ['<div class="informaltable"><table class="informaltable"']
754 if xml.attrib.get('pgwide') == '1':
755 result.append(' width="100%"')
756 if xml.attrib.get('frame') == 'none':
757 result.append(' border="0"')
758 result.append('>\n')
759 convert_inner(ctx, xml, result)
760 result.append('</table></div>')
761 if xml.tail:
762 result.append(xml.tail)
763 return result
766def convert_inlinegraphic(ctx, xml):
767 # TODO(ensonic): warn on missing fileref attr?
768 fileref = xml.attrib.get('fileref', '')
769 if fileref:
770 assets.add(fileref)
771 return ['<img src="%s">' % fileref]
774def convert_inlinemediaobject(ctx, xml):
775 result = ['<span class="inlinemediaobject">']
776 # no PCDATA allowed here
777 convert_mediaobject_children(ctx, xml, result)
778 result.append('</span>')
779 append_text(ctx, xml.tail, result)
780 return result
783def convert_itemizedlist(ctx, xml):
784 result = ['<div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; ">']
785 convert_inner(ctx, xml, result)
786 result.append('</ul></div>')
787 if xml.tail:
788 result.append(xml.tail)
789 return result
792def convert_link(ctx, xml):
793 linkend = xml.attrib['linkend']
794 result = []
795 if linkend:
796 link_text = []
797 append_text(ctx, xml.text, link_text)
798 convert_inner(ctx, xml, link_text)
799 text = ''.join(link_text)
801 (tid, href) = fixxref.GetXRef(linkend)
802 if href:
803 title_attr = ''
804 title = titles.get(tid)
805 if title:
806 title_attr = ' title="%s"' % title['title']
808 href = fixxref.MakeRelativeXRef(ctx['module'], href)
809 result = ['<a href="%s"%s>%s</a>' % (href, title_attr, text)]
810 else:
811 # TODO: filename is for the output and xml.sourceline is on the masterdoc ...
812 fixxref.ReportBadXRef(ctx['node'].filename, 0, linkend, text)
813 result = [text]
814 else:
815 append_text(ctx, xml.text, result)
816 convert_inner(ctx, xml, result)
817 append_text(ctx, xml.tail, result)
818 return result
821def convert_listitem(ctx, xml):
822 result = ['<li class="listitem">']
823 convert_inner(ctx, xml, result)
824 result.append('</li>')
825 # no PCDATA allowed here, is in itemizedlist
826 return result
829def convert_literallayout(ctx, xml):
830 result = ['<div class="literallayout"><p><br>\n']
831 append_text(ctx, xml.text, result)
832 convert_inner(ctx, xml, result)
833 result.append('</p></div>')
834 append_text(ctx, xml.tail, result)
835 return result
838def convert_mediaobject(ctx, xml):
839 result = ['<div class="mediaobject">\n']
840 # no PCDATA allowed here
841 convert_mediaobject_children(ctx, xml, result)
842 result.append('</div>')
843 append_text(ctx, xml.tail, result)
844 return result
847def convert_orderedlist(ctx, xml):
848 result = ['<div class="orderedlist"><ol class="orderedlist" type="1">']
849 convert_inner(ctx, xml, result)
850 result.append('</ol></div>')
851 append_text(ctx, xml.tail, result)
852 return result
855def convert_para(ctx, xml):
856 result = []
857 role = xml.attrib.get('role')
858 if role is not None:
859 result.append('<p class="%s">' % role)
860 else:
861 result.append('<p>')
862 append_idref(xml.attrib, result)
863 append_text(ctx, xml.text, result)
864 convert_inner(ctx, xml, result)
865 result.append('</p>')
866 append_text(ctx, xml.tail, result)
867 return result
870def convert_para_like(ctx, xml):
871 result = []
872 append_idref(xml.attrib, result)
873 result.append('<p class="%s">' % xml.tag)
874 append_text(ctx, xml.text, result)
875 convert_inner(ctx, xml, result)
876 result.append('</p>')
877 append_text(ctx, xml.tail, result)
878 return result
881def convert_phrase(ctx, xml):
882 result = ['<span']
883 role = xml.attrib.get('role')
884 if role is not None:
885 result.append(' class="%s">' % role)
886 else:
887 result.append('>')
888 append_text(ctx, xml.text, result)
889 convert_inner(ctx, xml, result)
890 result.append('</span>')
891 append_text(ctx, xml.tail, result)
892 return result
895def convert_primaryie(ctx, xml):
896 result = ['<dt>\n']
897 convert_inner(ctx, xml, result)
898 result.append('\n</dt>\n<dd></dd>\n')
899 return result
902def convert_pre(ctx, xml):
903 # Since we're inside <pre> don't skip newlines
904 ctx['no-strip'] = True
905 result = ['<pre class="%s">' % xml.tag]
906 append_text(ctx, xml.text, result)
907 convert_inner(ctx, xml, result)
908 result.append('</pre>')
909 del ctx['no-strip']
910 append_text(ctx, xml.tail, result)
911 return result
914def convert_programlisting(ctx, xml):
915 result = []
916 if xml.attrib.get('role', '') == 'example':
917 if xml.text:
918 lang = xml.attrib.get('language', ctx['src-lang']).lower()
919 highlighted = highlight.highlight_code(xml.text, lang)
920 if highlighted:
921 # we do own line-numbering
922 line_count = highlighted.count('\n')
923 source_lines = '\n'.join([str(i) for i in range(1, line_count + 1)])
924 result.append("""<table class="listing_frame" border="0" cellpadding="0" cellspacing="0">
925 <tbody>
926 <tr>
927 <td class="listing_lines" align="right"><pre>%s</pre></td>
928 <td class="listing_code"><pre class="programlisting">%s</pre></td>
929 </tr>
930 </tbody>
931</table>
932""" % (source_lines, highlighted))
933 else:
934 logging.warn('No pygments lexer for language="%s"', lang)
935 result.append('<pre class="programlisting">')
936 result.append(xml.text)
937 result.append('</pre>')
938 else:
939 result.append('<pre class="programlisting">')
940 append_text(ctx, xml.text, result)
941 convert_inner(ctx, xml, result)
942 result.append('</pre>')
943 append_text(ctx, xml.tail, result)
944 return result
947def convert_quote(ctx, xml):
948 result = ['<span class="quote">"<span class="quote">']
949 append_text(ctx, xml.text, result)
950 convert_inner(ctx, xml, result)
951 result.append('</span>"</span>')
952 append_text(ctx, xml.tail, result)
953 return result
956def convert_refsect1(ctx, xml):
957 # Add a divider between two consequitive refsect2
958 def convert_inner(ctx, xml, result):
959 prev = None
960 for child in xml:
961 if child.tag == 'refsect2' and prev is not None and prev.tag == child.tag:
962 result.append('<hr>\n')
963 result.extend(convert_tags.get(child.tag, convert__unknown)(ctx, child))
964 prev = child
965 return convert_sect(ctx, xml, 'h2', convert_inner)
968def convert_refsect2(ctx, xml):
969 return convert_sect(ctx, xml, 'h3')
972def convert_refsect3(ctx, xml):
973 return convert_sect(ctx, xml, 'h4')
976def convert_row(ctx, xml):
977 result = ['<tr>\n']
978 convert_inner(ctx, xml, result)
979 result.append('</tr>\n')
980 return result
983def convert_sbr(ctx, xml):
984 return ['<br>']
987def convert_sect1_tag(ctx, xml):
988 return convert_sect(ctx, xml, 'h2')
991def convert_sect2(ctx, xml):
992 return convert_sect(ctx, xml, 'h3')
995def convert_sect3(ctx, xml):
996 return convert_sect(ctx, xml, 'h4')
999def convert_simpara(ctx, xml):
1000 result = ['<p>']
1001 append_text(ctx, xml.text, result)
1002 convert_inner(ctx, xml, result)
1003 result.append('</p>')
1004 append_text(ctx, xml.tail, result)
1005 return result
1008def convert_span(ctx, xml):
1009 result = ['<span class="%s">' % xml.tag]
1010 append_text(ctx, xml.text, result)
1011 convert_inner(ctx, xml, result)
1012 result.append('</span>')
1013 append_text(ctx, xml.tail, result)
1014 return result
1017def convert_table(ctx, xml):
1018 result = ['<div class="table">']
1019 append_idref(xml.attrib, result)
1020 title_tag = xml.find('title')
1021 if title_tag is not None:
1022 result.append('<p class="title"><b>')
1023 # TODO(ensonic): Add a 'Table X. ' prefix, needs a table counter
1024 result.extend(convert_title(ctx, title_tag))
1025 result.append('</b></p>')
1026 result.append('<div class="table-contents"><table class="table" summary="g_object_new" border="1">')
1028 convert_inner(ctx, xml, result)
1030 result.append('</table></div></div>')
1031 append_text(ctx, xml.tail, result)
1032 return result
1035def convert_tag(ctx, xml):
1036 classval = xml.attrib.get('class')
1037 if classval is not None:
1038 result = ['<code class="sgmltag-%s">' % classval]
1039 else:
1040 result = ['<code>']
1041 append_text(ctx, xml.text, result)
1042 result.append('</code>')
1043 append_text(ctx, xml.tail, result)
1044 return result
1047def convert_tbody(ctx, xml):
1048 result = ['<tbody>']
1049 ctx['table.entry'] = 'td'
1050 convert_inner(ctx, xml, result)
1051 result.append('</tbody>')
1052 # is in tgroup and there can be no 'text'
1053 return result
1056def convert_tgroup(ctx, xml):
1057 # tgroup does not expand to anything, but the nested colspecs need to
1058 # be put into a colgroup
1059 cols = xml.findall('colspec')
1060 result = []
1061 if cols:
1062 result.append('<colgroup>\n')
1063 for col in cols:
1064 result.extend(convert_colspec(ctx, col))
1065 xml.remove(col)
1066 result.append('</colgroup>\n')
1067 convert_inner(ctx, xml, result)
1068 # is in informaltable and there can be no 'text'
1069 return result
1072def convert_thead(ctx, xml):
1073 result = ['<thead>']
1074 ctx['table.entry'] = 'th'
1075 convert_inner(ctx, xml, result)
1076 result.append('</thead>')
1077 # is in tgroup and there can be no 'text'
1078 return result
1081def convert_title(ctx, xml):
1082 # This is always explicitly called from some context
1083 result = []
1084 append_text(ctx, xml.text, result)
1085 convert_inner(ctx, xml, result)
1086 append_text(ctx, xml.tail, result)
1087 return result
1090def convert_ulink(ctx, xml):
1091 if xml.text:
1092 result = ['<a class="%s" href="%s">%s</a>' % (xml.tag, xml.attrib['url'], xml.text)]
1093 else:
1094 url = xml.attrib['url']
1095 result = ['<a class="%s" href="%s">%s</a>' % (xml.tag, url, url)]
1096 append_text(ctx, xml.tail, result)
1097 return result
1100def convert_userinput(ctx, xml):
1101 result = ['<span class="command"><strong>']
1102 append_text(ctx, xml.text, result)
1103 convert_inner(ctx, xml, result)
1104 result.append('</strong></span>')
1105 append_text(ctx, xml.tail, result)
1106 return result
1109def convert_variablelist(ctx, xml):
1110 result = ["""<div class="variablelist"><table border="0" class="variablelist">
1111<colgroup>
1112<col align="left" valign="top">
1113<col>
1114</colgroup>
1115<tbody>"""]
1116 convert_inner(ctx, xml, result)
1117 result.append("""</tbody>
1118</table></div>""")
1119 return result
1122def convert_varlistentry(ctx, xml):
1123 result = ['<tr>']
1125 result.append('<td><p>')
1126 term = xml.find('term')
1127 result.extend(convert_span(ctx, term))
1128 result.append('</p></td>')
1130 result.append('<td>')
1131 listitem = xml.find('listitem')
1132 convert_inner(ctx, listitem, result)
1133 result.append('</td>')
1135 result.append('<tr>')
1136 return result
1139def convert_xref(ctx, xml):
1140 result = []
1141 linkend = xml.attrib['linkend']
1142 (tid, href) = fixxref.GetXRef(linkend)
1143 try:
1144 title = titles[tid]
1145 # all sectN need to become 'section
1146 tag = title['tag']
1147 tag = {
1148 'sect1': 'section',
1149 'sect2': 'section',
1150 'sect3': 'section',
1151 'sect4': 'section',
1152 'sect5': 'section',
1153 }.get(tag, tag)
1154 result = [
1155 '<a class="xref" href="%s" title="%s">the %s called “%s”</a>' %
1156 (href, title['title'], tag, ''.join(convert_title(ctx, title['xml'])))
1157 ]
1158 except KeyError:
1159 logging.warning('invalid linkend "%s"', tid)
1161 append_text(ctx, xml.tail, result)
1162 return result
1165# TODO(ensonic): turn into class with converters as functions and ctx as self
1166convert_tags = {
1167 'abstract': convert_abstract,
1168 'acronym': convert_acronym,
1169 'anchor': convert_anchor,
1170 'application': convert_span,
1171 'bookinfo': convert_bookinfo,
1172 'blockquote': convert_blockquote,
1173 'classname': convert_code,
1174 'caption': convert_div,
1175 'code': convert_code,
1176 'colspec': convert_colspec,
1177 'constant': convert_code,
1178 'command': convert_command,
1179 'corpauthor': convert_corpauthor,
1180 'emphasis': convert_emphasis,
1181 'entry': convert_entry,
1182 'envar': convert_code,
1183 'footnote': convert_footnote,
1184 'figure': convert_figure,
1185 'filename': convert_code,
1186 'firstterm': convert_em,
1187 'formalpara': convert_formalpara,
1188 'function': convert_code,
1189 'glossdef': convert_glossdef,
1190 'glossdiv': convert_glossdiv,
1191 'glossentry': convert_glossentry,
1192 'glossterm': convert_glossterm,
1193 'graphic': convert_graphic,
1194 'indexdiv': convert_indexdiv,
1195 'indexentry': convert_ignore,
1196 'indexterm': convert_skip,
1197 'informalexample': convert_div,
1198 'informaltable': convert_informaltable,
1199 'inlinegraphic': convert_inlinegraphic,
1200 'inlinemediaobject': convert_inlinemediaobject,
1201 'interfacename': convert_code,
1202 'itemizedlist': convert_itemizedlist,
1203 'legalnotice': convert_div,
1204 'link': convert_link,
1205 'listitem': convert_listitem,
1206 'literal': convert_code,
1207 'literallayout': convert_literallayout,
1208 'mediaobject': convert_mediaobject,
1209 'note': convert_div,
1210 'option': convert_code,
1211 'orderedlist': convert_orderedlist,
1212 'para': convert_para,
1213 'partintro': convert_div,
1214 'parameter': convert_em_code,
1215 'phrase': convert_phrase,
1216 'primaryie': convert_primaryie,
1217 'programlisting': convert_programlisting,
1218 'quote': convert_quote,
1219 'releaseinfo': convert_para_like,
1220 'refsect1': convert_refsect1,
1221 'refsect2': convert_refsect2,
1222 'refsect3': convert_refsect3,
1223 'replaceable': convert_em_code,
1224 'returnvalue': convert_span,
1225 'row': convert_row,
1226 'sbr': convert_sbr,
1227 'screen': convert_pre,
1228 'section': convert_sect2, # FIXME: need tracking of nesting
1229 'sect1': convert_sect1_tag,
1230 'sect2': convert_sect2,
1231 'sect3': convert_sect3,
1232 'simpara': convert_simpara,
1233 'simplesect': convert_sect2, # FIXME: need tracking of nesting
1234 'structfield': convert_em_code,
1235 'structname': convert_span,
1236 'synopsis': convert_pre,
1237 'symbol': convert_span,
1238 'table': convert_table,
1239 'tag': convert_tag,
1240 'tbody': convert_tbody,
1241 'term': convert_span,
1242 'tgroup': convert_tgroup,
1243 'thead': convert_thead,
1244 'title': convert_skip,
1245 'type': convert_span,
1246 'ulink': convert_ulink,
1247 'userinput': convert_userinput,
1248 'varname': convert_code,
1249 'variablelist': convert_variablelist,
1250 'varlistentry': convert_varlistentry,
1251 'warning': convert_div,
1252 'xref': convert_xref,
1253}
1255# conversion helpers
1257HTML_HEADER = """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
1258<html>
1259<head>
1260<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1261<title>%s</title>
1262%s<link rel="stylesheet" href="style.css" type="text/css">
1263</head>
1264<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
1265"""
1268def generate_head_links(ctx):
1269 n = ctx['nav_home']
1270 result = [
1271 '<link rel="home" href="%s" title="%s">\n' % (n.filename, n.raw_title)
1272 ]
1274 n = ctx.get('nav_up')
1275 if n is not None:
1276 result.append('<link rel="up" href="%s" title="%s">\n' % (n.filename, n.raw_title))
1278 n = ctx.get('nav_prev')
1279 if n is not None:
1280 result.append('<link rel="prev" href="%s" title="%s">\n' % (n.filename, n.raw_title))
1282 n = ctx.get('nav_next')
1283 if n is not None:
1284 result.append('<link rel="next" href="%s" title="%s">\n' % (n.filename, n.raw_title))
1286 return ''.join(result)
1289def generate_nav_links(ctx):
1290 n = ctx['nav_home']
1291 result = [
1292 '<td><a accesskey="h" href="%s"><img src="home.png" width="16" height="16" border="0" alt="Home"></a></td>' % n.filename
1293 ]
1295 n = ctx.get('nav_up')
1296 if n is not None:
1297 result.append(
1298 '<td><a accesskey="u" href="%s"><img src="up.png" width="16" height="16" border="0" alt="Up"></a></td>' % n.filename)
1299 else:
1300 result.append('<td><img src="up-insensitive.png" width="16" height="16" border="0"></td>')
1302 n = ctx.get('nav_prev')
1303 if n is not None:
1304 result.append(
1305 '<td><a accesskey="p" href="%s"><img src="left.png" width="16" height="16" border="0" alt="Prev"></a></td>' % n.filename)
1306 else:
1307 result.append('<td><img src="left-insensitive.png" width="16" height="16" border="0"></td>')
1309 n = ctx.get('nav_next')
1310 if n is not None:
1311 result.append(
1312 '<td><a accesskey="n" href="%s"><img src="right.png" width="16" height="16" border="0" alt="Next"></a></td>' % n.filename)
1313 else:
1314 result.append('<td><img src="right-insensitive.png" width="16" height="16" border="0"></td>')
1316 return ''.join(result)
1319def generate_toc(ctx, node):
1320 result = []
1321 for c in node.children:
1322 # TODO: urlencode the filename: urllib.parse.quote_plus()
1323 link = c.filename
1324 if c.anchor:
1325 link += c.anchor
1326 result.append('<dt><span class="%s"><a href="%s">%s</a></span>\n' % (
1327 c.title_tag, link, c.title))
1328 if c.subtitle:
1329 result.append('<span class="%s"> — %s</span>' % (c.subtitle_tag, c.subtitle))
1330 result.append('</dt>\n')
1331 if c.children:
1332 result.append('<dd><dl>')
1333 result.extend(generate_toc(ctx, c))
1334 result.append('</dl></dd>')
1335 return result
1338def generate_basic_nav(ctx):
1339 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1340 <tr valign="middle">
1341 <td width="100%%" align="left" class="shortcuts"></td>
1342 %s
1343 </tr>
1344</table>
1345 """ % generate_nav_links(ctx)
1348def generate_alpha_nav(ctx, divs, prefix, span_id):
1349 ix_nav = []
1350 for s in divs:
1351 title = xml_get_title(ctx, s)
1352 ix_nav.append('<a class="shortcut" href="#%s%s">%s</a>' % (prefix, title, title))
1354 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1355 <tr valign="middle">
1356 <td width="100%%" align="left" class="shortcuts">
1357 <span id="nav_%s">
1358 %s
1359 </span>
1360 </td>
1361 %s
1362 </tr>
1363</table>
1364 """ % (span_id, '\n<span class="dim">|</span>\n'.join(ix_nav), generate_nav_links(ctx))
1367def generate_refentry_nav(ctx, refsect1s, result):
1368 result.append("""<table class="navigation" id="top" width="100%" cellpadding="2" cellspacing="5">
1369 <tr valign="middle">
1370 <td width="100%" align="left" class="shortcuts">
1371 <a href="#" class="shortcut">Top</a>""")
1373 for s in refsect1s:
1374 # don't list TOC sections (role="xxx_proto")
1375 if s.attrib.get('role', '').endswith("_proto"):
1376 continue
1377 # skip section without 'id' attrs
1378 ref_id = s.attrib.get('id')
1379 if ref_id is None:
1380 continue
1382 # skip foreign sections
1383 if '.' not in ref_id:
1384 continue
1386 title = xml_get_title(ctx, s)
1387 span_id = ref_id.split('.')[1].replace('-', '_')
1389 result.append("""
1390 <span id="nav_%s">
1391 <span class="dim">|</span>
1392 <a href="#%s" class="shortcut">%s</a>
1393 </span>
1394 """ % (span_id, ref_id, title))
1395 result.append("""
1396 </td>
1397 %s
1398 </tr>
1399</table>
1400""" % generate_nav_links(ctx))
1403def generate_footer(ctx):
1404 footnotes = ctx.get('footnotes')
1405 if footnotes is None:
1406 return []
1408 result = ["""<div class="footnotes">\n
1409<br><hr style="width:100; text-align:left;margin-left: 0">
1410"""]
1411 for f in footnotes:
1412 result.extend(f)
1413 result.append('</div>\n')
1414 return result
1417def get_id_path(node):
1418 """ Generate the 'id'.
1419 We need to walk up the xml-tree and check the positions for each sibling.
1420 When reaching the top of the tree we collect remaining index entries from
1421 the chunked-tree.
1422 """
1423 ix = []
1424 xml = node.xml
1425 parent = xml.getparent()
1426 while parent is not None:
1427 children = parent.getchildren()
1428 ix.insert(0, str(children.index(xml) + 1))
1429 xml = parent
1430 parent = xml.getparent()
1431 while node is not None:
1432 ix.insert(0, str(node.idx + 1))
1433 node = node.parent
1435 return ix
1438def get_id(node):
1439 xml = node.xml
1440 node_id = xml.attrib.get('id', None)
1441 if node_id:
1442 return node_id
1444 # TODO: this is moot if nothing links to it, we could also consider to omit
1445 # the <a name="$id"></a> tag.
1446 logging.info('%d: No "id" attribute on "%s", generating one',
1447 xml.sourceline, xml.tag)
1448 ix = get_id_path(node)
1449 # logging.warning('%s: id indexes: %s', node.filename, str(ix))
1450 return 'id-' + '.'.join(ix)
1453def convert_chunk_with_toc(ctx, div_class, title_tag):
1454 node = ctx['node']
1455 result = [
1456 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
1457 generate_basic_nav(ctx),
1458 '<div class="%s">' % div_class,
1459 ]
1460 if node.title:
1461 result.append("""
1462<div class="titlepage">
1463<%s class="title"><a name="%s"></a>%s</%s>
1464</div>""" % (
1465 title_tag, get_id(node), node.title, title_tag))
1467 toc = generate_toc(ctx, node)
1468 if toc:
1469 # TODO: not all docbook page types use this extra heading
1470 result.append("""<p><b>Table of Contents</b></p>
1471 <div class="toc">
1472 <dl class="toc">
1473 """)
1474 result.extend(toc)
1475 result.append("""</dl>
1476 </div>
1477 """)
1478 convert_inner(ctx, node.xml, result)
1479 result.extend(generate_footer(ctx))
1480 result.append("""</div>
1481</body>
1482</html>""")
1483 return result
1486# docbook chunks
1489def convert_book(ctx):
1490 node = ctx['node']
1491 result = [
1492 HTML_HEADER % (node.title, generate_head_links(ctx)),
1493 """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="0">
1494 <tr><th valign="middle"><p class="title">%s</p></th></tr>
1495</table>
1496<div class="book">
1497""" % node.title
1498 ]
1499 bookinfo = node.xml.findall('bookinfo')[0]
1500 result.extend(convert_bookinfo(ctx, bookinfo))
1501 result.append("""<div class="toc">
1502 <dl class="toc">
1503""")
1504 result.extend(generate_toc(ctx, node.root))
1505 result.append("""</dl>
1506</div>
1507""")
1508 result.extend(generate_footer(ctx))
1509 result.append("""</div>
1510</body>
1511</html>""")
1512 return result
1515def convert_chapter(ctx):
1516 return convert_chunk_with_toc(ctx, 'chapter', 'h2')
1519def convert_glossary(ctx):
1520 node = ctx['node']
1521 glossdivs = node.xml.findall('glossdiv')
1523 result = [
1524 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
1525 generate_alpha_nav(ctx, glossdivs, 'gls', 'glossary'),
1526 """<div class="glossary">
1527<div class="titlepage"><h%1d class="title">
1528<a name="%s"></a>%s</h%1d>
1529</div>""" % (node.depth, get_id(node), node.title, node.depth)
1530 ]
1531 for i in glossdivs:
1532 result.extend(convert_glossdiv(ctx, i))
1533 result.extend(generate_footer(ctx))
1534 result.append("""</div>
1535</body>
1536</html>""")
1537 return result
1540def convert_index(ctx):
1541 node = ctx['node']
1542 # Get all indexdivs under indexdiv
1543 indexdivs = []
1544 indexdiv = node.xml.find('indexdiv')
1545 if indexdiv is not None:
1546 indexdivs = indexdiv.findall('indexdiv')
1548 result = [
1549 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
1550 generate_alpha_nav(ctx, indexdivs, 'idx', 'index'),
1551 """<div class="index">
1552<div class="titlepage"><h%1d class="title">
1553<a name="%s"></a>%s</h%1d>
1554</div>""" % (node.depth, get_id(node), node.title, node.depth)
1555 ]
1556 for i in indexdivs:
1557 result.extend(convert_indexdiv(ctx, i))
1558 result.extend(generate_footer(ctx))
1559 result.append("""</div>
1560</body>
1561</html>""")
1562 return result
1565def convert_part(ctx):
1566 return convert_chunk_with_toc(ctx, 'part', 'h1')
1569def convert_preface(ctx):
1570 node = ctx['node']
1571 result = [
1572 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
1573 generate_basic_nav(ctx),
1574 '<div class="preface">'
1575 ]
1576 if node.title:
1577 result.append("""
1578<div class="titlepage">
1579<h2 class="title"><a name="%s"></a>%s</h2>
1580</div>""" % (get_id(node), node.title))
1581 convert_inner(ctx, node.xml, result)
1582 result.extend(generate_footer(ctx))
1583 result.append("""</div>
1584</body>
1585</html>""")
1586 return result
1589def convert_reference(ctx):
1590 return convert_chunk_with_toc(ctx, 'reference', 'h1')
1593def convert_refentry(ctx):
1594 node = ctx['node']
1595 node_id = get_id(node)
1596 refsect1s = node.xml.findall('refsect1')
1598 gallery = ''
1599 refmeta = node.xml.find('refmeta')
1600 if refmeta is not None:
1601 refmiscinfo = refmeta.find('refmiscinfo')
1602 if refmiscinfo is not None:
1603 inlinegraphic = refmiscinfo.find('inlinegraphic')
1604 if inlinegraphic is not None:
1605 gallery = ''.join(convert_inlinegraphic(ctx, inlinegraphic))
1607 result = [
1608 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx))
1609 ]
1610 generate_refentry_nav(ctx, refsect1s, result)
1611 result.append("""
1612<div class="refentry">
1613<a name="%s"></a>
1614<div class="refnamediv">
1615 <table width="100%%"><tr>
1616 <td valign="top">
1617 <h2><span class="refentrytitle"><a name="%s.top_of_page"></a>%s</span></h2>
1618 <p>%s — %s</p>
1619 </td>
1620 <td class="gallery_image" valign="top" align="right">%s</td>
1621 </tr></table>
1622</div>
1623""" % (node_id, node_id, node.title, node.title, node.subtitle, gallery))
1625 for s in refsect1s:
1626 result.extend(convert_refsect1(ctx, s))
1627 result.extend(generate_footer(ctx))
1628 result.append("""</div>
1629</body>
1630</html>""")
1631 return result
1634def convert_section(ctx):
1635 return convert_chunk_with_toc(ctx, 'section', 'h2')
1638def convert_sect1(ctx):
1639 return convert_chunk_with_toc(ctx, 'sect1', 'h2')
1642# TODO(ensonic): turn into class with converters as functions and ctx as self
1643convert_chunks = {
1644 'book': convert_book,
1645 'chapter': convert_chapter,
1646 'glossary': convert_glossary,
1647 'index': convert_index,
1648 'part': convert_part,
1649 'preface': convert_preface,
1650 'reference': convert_reference,
1651 'refentry': convert_refentry,
1652 'section': convert_section,
1653 'sect1': convert_sect1,
1654}
1657def generate_nav_nodes(files, node):
1658 nav = {
1659 'nav_home': node.root,
1660 }
1661 # nav params: up, prev, next
1662 if node.parent:
1663 nav['nav_up'] = node.parent
1664 ix = files.index(node)
1665 if ix > 0:
1666 nav['nav_prev'] = files[ix - 1]
1667 if ix < len(files) - 1:
1668 nav['nav_next'] = files[ix + 1]
1669 return nav
1672def convert_content(module, files, node, src_lang):
1673 converter = convert_chunks.get(node.name)
1674 if converter is None:
1675 logging.warning('Add chunk converter for "%s"', node.name)
1676 return []
1678 ctx = {
1679 'module': module,
1680 'files': files,
1681 'node': node,
1682 'src-lang': src_lang,
1683 }
1684 ctx.update(generate_nav_nodes(files, node))
1685 return converter(ctx)
1688def convert(out_dir, module, files, node, src_lang):
1689 """Convert the docbook chunks to a html file.
1691 Args:
1692 out_dir: already created output dir
1693 files: list of nodes in the tree in pre-order
1694 node: current tree node
1695 """
1697 logging.info('Writing: %s', node.filename)
1698 with open(os.path.join(out_dir, node.filename), 'wt',
1699 newline='\n', encoding='utf-8') as html:
1700 for line in convert_content(module, files, node, src_lang):
1701 html.write(line)
1704def create_devhelp2_toc(node):
1705 result = []
1706 for c in node.children:
1707 if c.children:
1708 result.append('<sub name="%s" link="%s">\n' % (c.raw_title, c.filename))
1709 result.extend(create_devhelp2_toc(c))
1710 result.append('</sub>\n')
1711 else:
1712 result.append('<sub name="%s" link="%s"/>\n' % (c.raw_title, c.filename))
1713 return result
1716def create_devhelp2_condition_attribs(node):
1717 condition = node.attrib.get('condition')
1718 if condition is not None:
1719 # condition -> since, deprecated, ... (separated with '|')
1720 cond = condition.replace('"', '"').split('|')
1721 keywords = []
1722 for c in cond:
1723 if ':' in c:
1724 keywords.append('{}="{}"'.format(*c.split(':', 1)))
1725 else:
1726 # deprecated can have no description
1727 keywords.append('{}="{}"'.format(c, ''))
1728 return ' ' + ' '.join(keywords)
1729 else:
1730 return ''
1733def create_devhelp2_refsect2_keyword(node, base_link):
1734 node_id = node.attrib['id']
1735 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1736 node.attrib['role'], titles[node_id]['title'], base_link + node_id,
1737 create_devhelp2_condition_attribs(node))
1740def create_devhelp2_refsect3_keyword(node, base_link, title, name):
1741 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1742 node.attrib['role'], title, base_link + name,
1743 create_devhelp2_condition_attribs(node))
1746def create_devhelp2_content(module, xml, files):
1747 title = ''
1748 online_attr = ''
1749 bookinfo_nodes = xml.xpath('/book/bookinfo')
1750 if len(bookinfo_nodes):
1751 bookinfo = bookinfo_nodes[0]
1752 title = bookinfo.xpath('./title/text()')[0]
1753 online_url = bookinfo.xpath('./releaseinfo/ulink[@role="online-location"]/@url')
1754 if len(online_url):
1755 online_attr = ' online="' + online_url[0] + '"'
1756 # TODO: support author too (see devhelp2.xsl), it is hardly used though
1757 # locate *.devhelp2 | xargs grep -Hn --color ' author="[^"]'
1758 # TODO: fixxref uses '--src-lang' to set the language, we have this in options too
1759 result = [
1760 """<?xml version="1.0" encoding="utf-8" standalone="no"?>
1761<book xmlns="http://www.devhelp.net/book" title="%s" link="index.html" author="" name="%s" version="2" language="c"%s>
1762 <chapters>
1763""" % (title, module, online_attr)
1764 ]
1765 # toc
1766 result.extend(create_devhelp2_toc(files[0].root))
1767 result.append(""" </chapters>
1768 <functions>
1769""")
1770 # keywords from all refsect2 and refsect3
1771 refsect2 = etree.XPath('//refsect2[@role]')
1772 refsect3_enum = etree.XPath('refsect3[@role="enum_members"]/informaltable/tgroup/tbody/row[@role="constant"]')
1773 refsect3_enum_details = etree.XPath('entry[@role="enum_member_name"]/para')
1774 refsect3_struct = etree.XPath('refsect3[@role="struct_members"]/informaltable/tgroup/tbody/row[@role="member"]')
1775 refsect3_struct_details = etree.XPath('entry[@role="struct_member_name"]/para/structfield')
1776 for node in files:
1777 base_link = node.filename + '#'
1778 refsect2_nodes = refsect2(node.xml)
1779 for refsect2_node in refsect2_nodes:
1780 result.append(create_devhelp2_refsect2_keyword(refsect2_node, base_link))
1781 refsect3_nodes = refsect3_enum(refsect2_node)
1782 for refsect3_node in refsect3_nodes:
1783 details_node = refsect3_enum_details(refsect3_node)[0]
1784 name = details_node.attrib['id']
1785 result.append(create_devhelp2_refsect3_keyword(refsect3_node, base_link, details_node.text, name))
1786 refsect3_nodes = refsect3_struct(refsect2_node)
1787 for refsect3_node in refsect3_nodes:
1788 details_node = refsect3_struct_details(refsect3_node)[0]
1789 name = details_node.attrib['id']
1790 result.append(create_devhelp2_refsect3_keyword(refsect3_node, base_link, name, name))
1792 result.append(""" </functions>
1793</book>
1794""")
1795 return result
1798def create_devhelp2(out_dir, module, xml, files):
1799 with open(os.path.join(out_dir, module + '.devhelp2'), 'wt',
1800 newline='\n', encoding='utf-8') as idx:
1801 for line in create_devhelp2_content(module, xml, files):
1802 idx.write(line)
1805class FakeDTDResolver(etree.Resolver):
1806 """Don't load the docbookx.dtd since we disable the validation anyway.
1808 libxsml2 does not cache DTDs. If we produce a docbook file with 100 chunks
1809 loading such a doc with xincluding will load and parse the docbook DTD 100
1810 times. This cases tons of memory allocations and is slow.
1811 """
1813 def resolve(self, url, id, context):
1814 if not url.endswith('.dtd'):
1815 return None
1816 # http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd
1817 return self.resolve_string('', context)
1820def main(module, index_file, out_dir, uninstalled, src_lang, paths):
1822 # == Loading phase ==
1823 # the next 3 steps could be done in parallel
1825 # 1) load the docuemnt
1826 _t = timer()
1827 parser = etree.XMLParser(dtd_validation=False, collect_ids=False)
1828 parser.resolvers.add(FakeDTDResolver())
1829 tree = etree.parse(index_file, parser)
1830 logging.warning("1a: %7.3lf: load doc", timer() - _t)
1831 _t = timer()
1832 tree.xinclude()
1833 logging.warning("1b: %7.3lf: xinclude doc", timer() - _t)
1835 # 2) copy datafiles
1836 _t = timer()
1837 # TODO: handle additional images
1838 (gtkdocdir, styledir) = config.get_dirs(uninstalled)
1839 # copy navigation images and stylesheets to html directory ...
1840 css_file = os.path.join(styledir, 'style.css')
1841 for f in glob(os.path.join(styledir, '*.png')) + [css_file]:
1842 shutil.copy(f, out_dir)
1843 highlight.append_style_defs(os.path.join(out_dir, 'style.css'))
1844 logging.warning("2: %7.3lf: copy datafiles", timer() - _t)
1846 # 3) load xref targets
1847 _t = timer()
1848 # TODO: migrate options from fixxref
1849 # TODO: ideally explicitly specify the files we need, this will save us the
1850 # globbing and we'll load less files.
1851 fixxref.LoadIndicies(out_dir, '/usr/share/gtk-doc/html', [])
1852 logging.warning("3: %7.3lf: load xrefs", timer() - _t)
1854 # == Processing phase ==
1856 # 4) recursively walk the tree and chunk it into a python tree so that we
1857 # can generate navigation and link tags.
1858 _t = timer()
1859 files = chunk(tree.getroot(), module)
1860 files = [f for f in PreOrderIter(files) if f.anchor is None]
1861 logging.warning("4: %7.3lf: chunk doc", timer() - _t)
1863 # 5) extract tables:
1864 _t = timer()
1865 # TODO: can be done in parallel
1866 # - find all 'id' attribs and add them to the link map
1867 # - .. get their titles and store them into the titles map
1868 add_id_links_and_titles(files, fixxref.Links)
1869 # - build glossary dict
1870 build_glossary(files)
1871 logging.warning("5: %7.3lf: extract tables", timer() - _t)
1873 # == Output phase ==
1874 # the next two step could be done in parllel
1876 # 6) create a xxx.devhelp2 file
1877 _t = timer()
1878 create_devhelp2(out_dir, module, tree.getroot(), files)
1879 logging.warning("6: %7.3lf: create devhelp2", timer() - _t)
1881 # 7) iterate the tree and output files
1882 _t = timer()
1883 # TODO: can be done in parallel, figure out why this is not faster
1884 # from multiprocessing.pool import Pool
1885 # with Pool(4) as p:
1886 # p.apply_async(convert, args=(out_dir, module, files))
1887 # from multiprocessing.pool import ThreadPool
1888 # with ThreadPool(4) as p:
1889 # p.apply_async(convert, args=(out_dir, module, files))
1890 for node in files:
1891 convert(out_dir, module, files, node, src_lang)
1892 logging.warning("7: %7.3lf: create html", timer() - _t)
1894 # 8) copy assets over
1895 _t = timer()
1896 paths = set(paths + [os.getcwd()])
1897 for a in assets:
1898 logging.info('trying %s in %s', a, str(paths))
1899 copied = False
1900 for p in paths:
1901 try:
1902 shutil.copy(os.path.join(p, a), out_dir)
1903 copied = True
1904 except FileNotFoundError:
1905 pass
1906 if not copied:
1907 logging.warning('file %s not found in path (did you add --path?)', a)
1908 logging.warning("8: %7.3lf: copy assets", timer() - _t)
1911def run(options):
1912 logging.info('options: %s', str(options.__dict__))
1913 module = options.args[0]
1914 document = options.args[1]
1916 output_dir = options.output_dir or os.getcwd()
1917 if options.output_dir and not os.path.isdir(output_dir):
1918 os.mkdir(output_dir)
1920 # TODO: pass options.extra_dir
1921 sys.exit(main(module, document, output_dir, options.uninstalled, options.src_lang,
1922 options.path))
1925def new_args_parser():
1926 parser = argparse.ArgumentParser(
1927 description=f"gtkdoc-mkhtml version {config.version} - generate documentation in html format"
1928 )
1929 parser.add_argument("--version", action="version", version=config.version)
1930 parser.add_argument(
1931 "--output-dir", default=".", help="The directory where the results are stored"
1932 )
1933 parser.add_argument(
1934 "--path", default=[], action="append", help="Extra source directories"
1935 )
1936 parser.add_argument(
1937 "--extra-dir",
1938 default=[],
1939 action="append",
1940 help="Directories to recursively scan for indices (*.devhelp2)"
1941 "in addition to HTML_DIR",
1942 )
1943 parser.add_argument(
1944 "--src-lang",
1945 default="c",
1946 help="Programing language used for syntax highlighting. This"
1947 "can be any language pygments supports.",
1948 )
1949 parser.add_argument("args", nargs="*", help="MODULE DRIVER_FILE")
1950 # TODO: only for testing, replace with env-var
1951 parser.add_argument("--uninstalled", action="store_true", default=False, help="???")
1952 return parser