mkhtml2: handle special casing for sect1/section
[gtk-doc.git] / gtkdoc / mkhtml2.py
blob5b33273aede005acce2010d3c5eeb06b7aecb5b7
1 #!/usr/bin/env python3
2 # -*- python; coding: utf-8 -*-
4 # gtk-doc - GTK DocBook documentation generator.
5 # Copyright (C) 2018 Stefan Sauer
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.
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.
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.
22 """Generate html from docbook
24 The tool loads the main xml document (<module>-docs.xml) and chunks it
25 like the xsl-stylesheets would do. For that it resolves all the xml-includes.
26 Each chunk is converted to html using python functions.
28 In contrast to our previous approach of running gtkdoc-mkhtml + gtkdoc-fixxref,
29 this tools will replace both without relying on external tools such as xsltproc
30 and source-highlight.
32 Please note, that we're not aiming for complete docbook-xml support. All tags
33 used in the generated xml are of course handled. More tags used in handwritten
34 xml can be easilly supported, but for some combinations of tags we prefer
35 simplicity.
37 TODO:
38 - tag converters:
39 - inside 'footnote' one can have many tags, we only handle 'para'/'simpara'
40 - inside 'inlinemediaobject'/'mediaobject' a 'textobject' becomes the 'alt'
41 attr on the <img> tag of the 'imageobject'
42 - glossary/index: depending on the parents, the headings as h1/h2
43 - maybe track depth when chunking
44 - the part titles have a generated prefix, such as 'Part I:'
45 - replace get_title with a result.extend(convert_title(ctx, title_tag))
46 - see convert_table()
47 - check each docbook tag if it can contain #PCDATA, if not don't check for
48 xml.text
49 - consider some perf-warnings flag
50 - see 'No "id" attribute on'
52 OPTIONAL:
53 - minify html: https://pypi.python.org/pypi/htmlmin/
55 Requirements:
56 sudo pip3 install anytree lxml pygments
58 Example invocation:
59 cd tests/bugs/docs/
60 ../../../gtkdoc-mkhtml2 tester tester-docs.xml
61 xdg-open db2html/index.html
62 meld html db2html
64 Benchmarking:
65 cd tests/bugs/docs/;
66 rm html-build.stamp; time make html-build.stamp
67 """
69 import argparse
70 import errno
71 import logging
72 import os
73 import shutil
74 import sys
76 from anytree import Node, PreOrderIter
77 from copy import deepcopy
78 from glob import glob
79 from lxml import etree
80 from pygments import highlight
81 from pygments.lexers import CLexer
82 from pygments.formatters import HtmlFormatter
84 from . import config, fixxref
86 # pygments setup
87 # lazily constructed lexer cache
88 LEXERS = {
89 'c': CLexer()
91 HTML_FORMATTER = HtmlFormatter(nowrap=True)
93 # http://www.sagehill.net/docbookxsl/Chunking.html
94 CHUNK_TAGS = [
95 'appendix',
96 'article',
97 'bibliography', # in article or book
98 'book',
99 'chapter',
100 'colophon',
101 'glossary', # in article or book
102 'index', # in article or book
103 'part',
104 'preface',
105 'refentry',
106 'reference',
107 'sect1', # except first
108 'section', # if equivalent to sect1
109 'set',
110 'setindex',
114 class ChunkParams(object):
115 def __init__(self, prefix, parent=None, min_idx=0):
116 self.prefix = prefix
117 self.parent = parent
118 self.min_idx = min_idx
121 # TODO: look up the abbrevs and hierarchy for other tags
122 # http://www.sagehill.net/docbookxsl/Chunking.html#GeneratedFilenames
123 # 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 CHUNK_PARAMS = {
128 'appendix': ChunkParams('app', 'book'),
129 'book': ChunkParams('bk'),
130 'chapter': ChunkParams('ch', 'book'),
131 'index': ChunkParams('ix', 'book'),
132 'part': ChunkParams('pt', 'book'),
133 'preface': ChunkParams('pr', 'book'),
134 'reference': ChunkParams('rn', 'book'),
135 'sect1': ChunkParams('s', 'chapter', 1),
136 'section': ChunkParams('s', 'chapter', 1),
139 TITLE_XPATHS = {
140 '_': (etree.XPath('./title'), None),
141 'book': (etree.XPath('./bookinfo/title'), None),
142 'refentry': (
143 etree.XPath('./refmeta/refentrytitle'),
144 etree.XPath('./refnamediv/refpurpose')
148 ID_XPATH = etree.XPath('//@id')
150 GLOSSENTRY_XPATH = etree.XPath('//glossentry')
151 glossary = {}
153 footnote_idx = 1
156 def get_chunk_min_idx(tag):
157 if tag not in CHUNK_PARAMS:
158 return 0
160 return CHUNK_PARAMS[tag].min_idx
163 def gen_chunk_name(node, idx):
164 if 'id' in node.attrib:
165 return node.attrib['id']
167 tag = node.tag
168 if tag not in CHUNK_PARAMS:
169 CHUNK_PARAMS[tag] = ChunkParams(node.tag[:2])
170 logging.warning('Add CHUNK_PARAMS for "%s"', tag)
172 naming = CHUNK_PARAMS[tag]
173 name = ('%s%02d' % (naming.prefix, idx))
174 # handle parents to make names of nested tags unique
175 # TODO: we only need to prepend the parent if there are > 1 of them in the
176 # xml
177 # while naming.parent:
178 # parent = naming.parent
179 # if parent not in CHUNK_PARAMS:
180 # break;
181 # naming = CHUNK_PARAMS[parent]
182 # name = ('%s%02d' % (naming.prefix, idx)) + name
183 logging.info('Gen chunk name: "%s"', name)
184 return name
187 def get_chunk_titles(node):
188 tag = node.tag
189 if tag not in TITLE_XPATHS:
190 # Use defaults
191 (title, subtitle) = TITLE_XPATHS['_']
192 else:
193 (title, subtitle) = TITLE_XPATHS[tag]
195 xml = title(node)[0]
196 result = {
197 'title': xml.text
199 if xml.tag != 'title':
200 result['title_tag'] = xml.tag
201 else:
202 result['title_tag'] = tag
204 if subtitle:
205 xml = subtitle(node)[0]
206 result['subtitle'] = xml.text
207 result['subtitle_tag'] = xml.tag
208 else:
209 result['subtitle'] = None
210 result['subtitle_tag'] = None
211 return result
214 def chunk(xml_node, idx=0, parent=None):
215 """Chunk the tree.
217 The first time, we're called with parent=None and in that case we return
218 the new_node as the root of the tree
220 tag = xml_node.tag
221 # also check idx to handle 'sect1'/'section' special casing
222 if tag in CHUNK_TAGS and idx >= get_chunk_min_idx(tag):
223 logging.info('chunk tag: "%s"[%d]', tag, idx)
224 if parent:
225 # remove the xml-node from the parent
226 sub_tree = etree.ElementTree(deepcopy(xml_node)).getroot()
227 xml_node.getparent().remove(xml_node)
228 xml_node = sub_tree
230 title_args = get_chunk_titles(xml_node)
231 chunk_name = gen_chunk_name(xml_node, (idx + 1))
232 parent = Node(tag, parent=parent, xml=xml_node,
233 filename=chunk_name + '.html', **title_args)
235 idx = 0
236 for child in xml_node:
237 new_parent = chunk(child, idx, parent)
238 if child.tag in CHUNK_TAGS:
239 idx += 1
241 return parent
244 def add_id_links(files, links):
245 for node in files:
246 chunk_name = node.filename[:-5]
247 chunk_base = node.filename + '#'
248 for attr in ID_XPATH(node.xml):
249 if attr == chunk_name:
250 links[attr] = node.filename
251 else:
252 links[attr] = chunk_base + attr
255 def build_glossary(files):
256 for node in files:
257 if node.xml.tag != 'glossary':
258 continue
259 for term in GLOSSENTRY_XPATH(node.xml):
260 # TODO: there can be all kind of things in a glossary. This only supports
261 # what we commonly use
262 key = etree.tostring(term.find('glossterm'), method="text", encoding=str).strip()
263 value = etree.tostring(term.find('glossdef'), method="text", encoding=str).strip()
264 glossary[key] = value
265 # logging.debug('glosentry: %s:%s', key, value)
268 # conversion helpers
271 def convert_inner(ctx, xml, result):
272 for child in xml:
273 result.extend(convert_tags.get(child.tag, convert__unknown)(ctx, child))
276 def convert_ignore(ctx, xml):
277 result = []
278 convert_inner(ctx, xml, result)
279 return result
282 def convert_skip(ctx, xml):
283 return ['']
286 def append_text(text, result):
287 if text and text.strip():
288 result.append(text.replace('<', '&lt;').replace('>', '&gt;'))
291 missing_tags = {}
294 def convert__unknown(ctx, xml):
295 # don't recurse on subchunks
296 if xml.tag in CHUNK_TAGS:
297 return []
298 if isinstance(xml, etree._Comment):
299 return ['<!-- ' + xml.text + '-->\n']
300 else:
301 # warn only once
302 if xml.tag not in missing_tags:
303 logging.warning('Add tag converter for "%s"', xml.tag)
304 missing_tags[xml.tag] = True
305 result = ['<!-- ' + xml.tag + '-->\n']
306 convert_inner(ctx, xml, result)
307 result.append('<!-- /' + xml.tag + '-->\n')
308 return result
311 def convert_sect(ctx, xml, h_tag, inner_func=convert_inner):
312 result = ['<div class="%s">\n' % xml.tag]
313 title = xml.find('title')
314 if title is not None:
315 if 'id' in xml.attrib:
316 result.append('<a name="%s"></a>' % xml.attrib['id'])
317 result.append('<%s>%s</%s>' % (h_tag, title.text, h_tag))
318 xml.remove(title)
319 append_text(xml.text, result)
320 inner_func(ctx, xml, result)
321 result.append('</div>')
322 append_text(xml.tail, result)
323 return result
326 def xml_get_title(xml):
327 title = xml.find('title')
328 if title is not None:
329 return title.text
330 else:
331 # TODO(ensonic): any way to get the file (inlcudes) too?
332 logging.warning('%s: Expected title tag under "%s %s"', xml.sourceline, xml.tag, str(xml.attrib))
333 return ''
336 # docbook tags
339 def convert_abstract(ctx, xml):
340 result = ["""<div class="abstract">
341 <p class="title"><b>Abstract</b></p>"""]
342 append_text(xml.text, result)
343 convert_inner(ctx, xml, result)
344 result.append('</div>')
345 append_text(xml.tail, result)
346 return result
349 def convert_acronym(ctx, xml):
350 key = xml.text
351 title = glossary.get(key, '')
352 # TODO: print a sensible warning if missing
353 result = ['<acronym title="%s"><span class="acronym">%s</span></acronym>' % (title, key)]
354 if xml.tail:
355 result.append(xml.tail)
356 return result
359 def convert_anchor(ctx, xml):
360 return ['<a name="%s"></a>' % xml.attrib['id']]
363 def convert_bookinfo(ctx, xml):
364 result = ['<div class="titlepage">']
365 convert_inner(ctx, xml, result)
366 result.append("""<hr>
367 </div>""")
368 if xml.tail:
369 result.append(xml.tail)
370 return result
373 def convert_blockquote(ctx, xml):
374 result = ['<div class="blockquote">\n<blockquote class="blockquote">']
375 append_text(xml.text, result)
376 convert_inner(ctx, xml, result)
377 result.append('</blockquote>\n</div>')
378 append_text(xml.tail, result)
379 return result
382 def convert_code(ctx, xml):
383 result = ['<code class="%s">' % xml.tag]
384 append_text(xml.text, result)
385 convert_inner(ctx, xml, result)
386 result.append('</code>')
387 append_text(xml.tail, result)
388 return result
391 def convert_colspec(ctx, xml):
392 result = ['<col']
393 a = xml.attrib
394 if 'colname' in a:
395 result.append(' class="%s"' % a['colname'])
396 if 'colwidth' in a:
397 result.append(' width="%s"' % a['colwidth'])
398 result.append('>\n')
399 # is in tgroup and there can be no 'text'
400 return result
403 def convert_command(ctx, xml):
404 result = ['<strong class="userinput"><code>']
405 append_text(xml.text, result)
406 convert_inner(ctx, xml, result)
407 result.append('</code></strong>')
408 append_text(xml.tail, result)
409 return result
412 def convert_corpauthor(ctx, xml):
413 result = ['<div><h3 class="corpauthor">\n']
414 append_text(xml.text, result)
415 convert_inner(ctx, xml, result)
416 result.append('</h3></div>\n')
417 append_text(xml.tail, result)
418 return result
421 def convert_div(ctx, xml):
422 result = ['<div class="%s">\n' % xml.tag]
423 append_text(xml.text, result)
424 convert_inner(ctx, xml, result)
425 result.append('</div>')
426 append_text(xml.tail, result)
427 return result
430 def convert_em_class(ctx, xml):
431 result = ['<em class="%s"><code>' % xml.tag]
432 append_text(xml.text, result)
433 convert_inner(ctx, xml, result)
434 result.append('</code></em>')
435 append_text(xml.tail, result)
436 return result
439 def convert_entry(ctx, xml):
440 entry_type = ctx['table.entry']
441 result = ['<' + entry_type]
442 if 'role' in xml.attrib:
443 result.append(' class="%s"' % xml.attrib['role'])
444 if 'morerows' in xml.attrib:
445 result.append(' rowspan="%s"' % (1 + int(xml.attrib['morerows'])))
446 result.append('>')
447 append_text(xml.text, result)
448 convert_inner(ctx, xml, result)
449 result.append('</' + entry_type + '>')
450 append_text(xml.tail, result)
451 return result
454 def convert_footnote(ctx, xml):
455 footnotes = ctx.get('footnotes', [])
456 # footnotes idx is not per page, but per doc
457 global footnote_idx
458 idx = footnote_idx
459 footnote_idx += 1
461 # need a pair of ids for each footnote (docbook generates different ids)
462 this_id = 'footnote-%d' % idx
463 that_id = 'ftn.' + this_id
465 inner = ['<div id="%s" class="footnote">' % that_id]
466 inner.append('<p><a href="#%s" class="para"><sup class="para">[%d] </sup></a>' % (
467 this_id, idx))
468 # TODO(ensonic): this can contain all kind of tags, if we convert them we'll
469 # get double nested paras :/.
470 # convert_inner(ctx, xml, inner)
471 para = xml.find('para')
472 if para is None:
473 para = xml.find('simpara')
474 if para is not None:
475 inner.append(para.text)
476 else:
477 logging.warning('%s: Unhandled footnote content: %s', xml.sourceline,
478 etree.tostring(xml, method="text", encoding=str).strip())
479 inner.append('</p></div>')
480 footnotes.append(inner)
481 ctx['footnotes'] = footnotes
482 return ['<a href="#%s" class="footnote" name="%s"><sup class="footnote">[%s]</sup></a>' % (
483 that_id, this_id, idx)]
486 def convert_formalpara(ctx, xml):
487 result = None
488 title_tag = xml.find('title')
489 result = ['<p><b>%s</b>' % title_tag.text]
490 para_tag = xml.find('para')
491 append_text(para_tag.text, result)
492 convert_inner(ctx, para_tag, result)
493 append_text(para_tag.tail, result)
494 result.append('</p>')
495 append_text(xml.tail, result)
496 return result
499 def convert_glossdef(ctx, xml):
500 result = ['<dd class="glossdef">']
501 convert_inner(ctx, xml, result)
502 result.append('</dd>\n')
503 return result
506 def convert_glossdiv(ctx, xml):
507 title_tag = xml.find('title')
508 title = title_tag.text
509 xml.remove(title_tag)
510 result = [
511 '<a name="gls%s"></a><h3 class="title">%s</h3>' % (title, title)
513 convert_inner(ctx, xml, result)
514 return result
517 def convert_glossentry(ctx, xml):
518 result = []
519 convert_inner(ctx, xml, result)
520 return result
523 def convert_glossterm(ctx, xml):
524 glossid = ''
525 text = ''
526 anchor = xml.find('anchor')
527 if anchor is not None:
528 glossid = anchor.attrib.get('id', '')
529 text += anchor.tail or ''
530 text += xml.text or ''
531 if glossid == '':
532 glossid = 'glossterm-' + text
533 return [
534 '<dt><span class="glossterm"><a name="%s"></a>%s</span></dt>' % (
535 glossid, text)
539 def convert_imageobject(ctx, xml):
540 imagedata = xml.find('imagedata')
541 if imagedata is not None:
542 # TODO(ensonic): warn on missing fileref attr?
543 return ['<img src="%s">' % imagedata.attrib.get('fileref', '')]
544 else:
545 return []
548 def convert_indexdiv(ctx, xml):
549 title_tag = xml.find('title')
550 title = title_tag.text
551 xml.remove(title_tag)
552 result = [
553 '<a name="idx%s"></a><h3 class="title">%s</h3>' % (title, title)
555 convert_inner(ctx, xml, result)
556 return result
559 def convert_informaltable(ctx, xml):
560 result = ['<div class="informaltable"><table class="informaltable"']
561 a = xml.attrib
562 if 'pgwide' in a and a['pgwide'] == '1':
563 result.append(' width="100%"')
564 if 'frame' in a and a['frame'] == 'none':
565 result.append(' border="0"')
566 result.append('>\n')
567 convert_inner(ctx, xml, result)
568 result.append('</table></div>')
569 if xml.tail:
570 result.append(xml.tail)
571 return result
574 def convert_itemizedlist(ctx, xml):
575 result = ['<div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; ">']
576 convert_inner(ctx, xml, result)
577 result.append('</ul></div>')
578 if xml.tail:
579 result.append(xml.tail)
580 return result
583 def convert_link(ctx, xml):
584 linkend = xml.attrib['linkend']
585 if linkend in fixxref.NoLinks:
586 linkend = None
587 result = []
588 if linkend:
589 link_text = []
590 convert_inner(ctx, xml, link_text)
591 append_text(xml.text, link_text)
592 # TODO: fixxref does some weird checks in xml.text
593 result = [fixxref.MakeXRef(ctx['module'], '', 0, linkend, ''.join(link_text))]
594 append_text(xml.tail, result)
595 return result
598 def convert_listitem(ctx, xml):
599 result = ['<li class="listitem">']
600 convert_inner(ctx, xml, result)
601 result.append('</li>')
602 # is in itemizedlist and there can be no 'text'
603 return result
606 def convert_literallayout(ctx, xml):
607 result = ['<div class="literallayout"><p><br>\n']
608 append_text(xml.text, result)
609 convert_inner(ctx, xml, result)
610 result.append('</p></div>')
611 append_text(xml.tail, result)
612 return result
615 def convert_orderedlist(ctx, xml):
616 result = ['<div class="orderedlistlist"><ol class="orderedlistlist" type="1">']
617 convert_inner(ctx, xml, result)
618 result.append('</ol></div>')
619 append_text(xml.tail, result)
620 return result
623 def convert_para(ctx, xml):
624 result = []
625 if 'id' in xml.attrib:
626 result.append('<a name="%s"></a>' % xml.attrib['id'])
627 result.append('<p>')
628 append_text(xml.text, result)
629 convert_inner(ctx, xml, result)
630 result.append('</p>')
631 append_text(xml.tail, result)
632 return result
635 def convert_para_like(ctx, xml):
636 result = []
637 if 'id' in xml.attrib:
638 result.append('<a name="%s"></a>' % xml.attrib['id'])
639 result.append('<p class="%s">' % xml.tag)
640 append_text(xml.text, result)
641 convert_inner(ctx, xml, result)
642 result.append('</p>')
643 append_text(xml.tail, result)
644 return result
647 def convert_phrase(ctx, xml):
648 result = ['<span']
649 if 'role' in xml.attrib:
650 result.append(' class="%s">' % xml.attrib['role'])
651 else:
652 result.append('>')
653 append_text(xml.text, result)
654 convert_inner(ctx, xml, result)
655 result.append('</span>')
656 append_text(xml.tail, result)
657 return result
660 def convert_primaryie(ctx, xml):
661 result = ['<dt>\n']
662 convert_inner(ctx, xml, result)
663 result.append('\n</dt>\n<dd></dd>\n')
664 return result
667 def convert_pre(ctx, xml):
668 result = ['<pre class="%s">\n' % xml.tag]
669 append_text(xml.text, result)
670 convert_inner(ctx, xml, result)
671 result.append('</pre>')
672 append_text(xml.tail, result)
673 return result
676 def convert_programlisting(ctx, xml):
677 result = []
678 if xml.attrib.get('role', '') == 'example':
679 if xml.text:
680 lang = xml.attrib.get('language', 'c').lower()
681 if lang not in LEXERS:
682 LEXERS[lang] = get_lexer_by_name(lang)
683 lexer = LEXERS.get(lang, None)
684 if lexer:
685 highlighted = highlight(xml.text, lexer, HTML_FORMATTER)
687 # we do own line-numbering
688 line_count = highlighted.count('\n')
689 source_lines = '\n'.join([str(i) for i in range(1, line_count + 1)])
690 result.append("""<table class="listing_frame" border="0" cellpadding="0" cellspacing="0">
691 <tbody>
692 <tr>
693 <td class="listing_lines" align="right"><pre>%s</pre></td>
694 <td class="listing_code"><pre class="programlisting">%s</pre></td>
695 </tr>
696 </tbody>
697 </table>
698 """ % (source_lines, highlighted))
699 else:
700 logging.warn('No pygments lexer for language="%s"', lang)
701 result.append('<pre class="programlisting">')
702 result.append(xml.text)
703 result.append('</pre>')
704 else:
705 result.append('<pre class="programlisting">')
706 append_text(xml.text, result)
707 convert_inner(ctx, xml, result)
708 result.append('</pre>')
709 append_text(xml.tail, result)
710 return result
713 def convert_quote(ctx, xml):
714 result = ['<span class="quote">"<span class="quote">']
715 append_text(xml.text, result)
716 convert_inner(ctx, xml, result)
717 result.append('</span>"</span>')
718 append_text(xml.tail, result)
719 return result
722 def convert_refsect1(ctx, xml):
723 # Add a divider between two consequitive refsect2
724 def convert_inner(ctx, xml, result):
725 prev = None
726 for child in xml:
727 if child.tag == 'refsect2' and prev is not None and prev.tag == child.tag:
728 result.append('<hr>\n')
729 result.extend(convert_tags.get(child.tag, convert__unknown)(ctx, child))
730 prev = child
731 return convert_sect(ctx, xml, 'h2', convert_inner)
734 def convert_refsect2(ctx, xml):
735 return convert_sect(ctx, xml, 'h3')
738 def convert_refsect3(ctx, xml):
739 return convert_sect(ctx, xml, 'h4')
742 def convert_row(ctx, xml):
743 result = ['<tr>\n']
744 convert_inner(ctx, xml, result)
745 result.append('</tr>\n')
746 return result
749 def convert_sect1_tag(ctx, xml):
750 return convert_sect(ctx, xml, 'h2')
753 def convert_sect2(ctx, xml):
754 return convert_sect(ctx, xml, 'h3')
757 def convert_sect3(ctx, xml):
758 return convert_sect(ctx, xml, 'h4')
761 def convert_simpara(ctx, xml):
762 result = ['<p>']
763 append_text(xml.text, result)
764 result.append('</p>')
765 append_text(xml.tail, result)
766 return result
769 def convert_span(ctx, xml):
770 result = ['<span class="%s">' % xml.tag]
771 append_text(xml.text, result)
772 convert_inner(ctx, xml, result)
773 result.append('</span>')
774 append_text(xml.tail, result)
775 return result
778 def convert_table(ctx, xml):
779 result = ['<div class="table">']
780 if 'id' in xml.attrib:
781 result.append('<a name="%s"></a>' % xml.attrib['id'])
782 title_tag = xml.find('title')
783 if title_tag is not None:
784 result.append('<p class="title"><b>')
785 # TODO(ensonic): Add a 'Table X. ' prefix, needs a table counter
786 result.extend(convert_title(ctx, title_tag))
787 result.append('</b></p>')
788 xml.remove(title_tag)
789 result.append('<div class="table-contents"><table class="table" summary="g_object_new" border="1">')
791 convert_inner(ctx, xml, result)
793 result.append('</table></div></div>')
794 append_text(xml.tail, result)
795 return result
798 def convert_tbody(ctx, xml):
799 result = ['<tbody>']
800 ctx['table.entry'] = 'td'
801 convert_inner(ctx, xml, result)
802 result.append('</tbody>')
803 # is in tgroup and there can be no 'text'
804 return result
807 def convert_tgroup(ctx, xml):
808 # tgroup does not expand to anything, but the nested colspecs need to
809 # be put into a colgroup
810 cols = xml.findall('colspec')
811 result = []
812 if cols:
813 result.append('<colgroup>\n')
814 for col in cols:
815 result.extend(convert_colspec(ctx, col))
816 xml.remove(col)
817 result.append('</colgroup>\n')
818 convert_inner(ctx, xml, result)
819 # is in informaltable and there can be no 'text'
820 return result
823 def convert_thead(ctx, xml):
824 result = ['<thead>']
825 ctx['table.entry'] = 'th'
826 convert_inner(ctx, xml, result)
827 result.append('</thead>')
828 # is in tgroup and there can be no 'text'
829 return result
832 def convert_title(ctx, xml):
833 # This is always called from some context
834 result = []
835 append_text(xml.text, result)
836 convert_inner(ctx, xml, result)
837 append_text(xml.tail, result)
838 return result
841 def convert_ulink(ctx, xml):
842 result = ['<a class="%s" href="%s">%s</a>' % (xml.tag, xml.attrib['url'], xml.text)]
843 if xml.tail:
844 result.append(xml.tail)
845 return result
848 def convert_userinput(ctx, xml):
849 result = ['<span class="command"><strong>']
850 append_text(xml.text, result)
851 convert_inner(ctx, xml, result)
852 result.append('</strong></span>')
853 append_text(xml.tail, result)
854 return result
857 def convert_variablelist(ctx, xml):
858 result = ["""<div class="variablelist"><table border="0" class="variablelist">
859 <colgroup>
860 <col align="left" valign="top">
861 <col>
862 </colgroup>
863 <tbody>"""]
864 convert_inner(ctx, xml, result)
865 result.append("""</tbody>
866 </table></div>""")
867 return result
870 def convert_varlistentry(ctx, xml):
871 result = ['<tr>']
873 result.append('<td><p>')
874 term = xml.find('term')
875 result.extend(convert_span(ctx, term))
876 result.append('</p></td>')
878 result.append('<td>')
879 listitem = xml.find('listitem')
880 convert_inner(ctx, listitem, result)
881 result.append('</td>')
883 result.append('<tr>')
884 return result
887 # TODO(ensonic): turn into class with converters as functions and ctx as self
888 convert_tags = {
889 'abstract': convert_abstract,
890 'acronym': convert_acronym,
891 'anchor': convert_anchor,
892 'application': convert_span,
893 'bookinfo': convert_bookinfo,
894 'blockquote': convert_blockquote,
895 'caption': convert_div,
896 'code': convert_code,
897 'colspec': convert_colspec,
898 'constant': convert_code,
899 'command': convert_command,
900 'corpauthor': convert_corpauthor,
901 'emphasis': convert_span,
902 'entry': convert_entry,
903 'envar': convert_code,
904 'footnote': convert_footnote,
905 'filename': convert_code,
906 'formalpara': convert_formalpara,
907 'function': convert_code,
908 'glossdef': convert_glossdef,
909 'glossdiv': convert_glossdiv,
910 'glossentry': convert_glossentry,
911 'glossterm': convert_glossterm,
912 'imageobject': convert_imageobject,
913 'indexdiv': convert_indexdiv,
914 'indexentry': convert_ignore,
915 'indexterm': convert_skip,
916 'informalexample': convert_div,
917 'informaltable': convert_informaltable,
918 'inlinemediaobject': convert_span,
919 'itemizedlist': convert_itemizedlist,
920 'legalnotice': convert_div,
921 'link': convert_link,
922 'listitem': convert_listitem,
923 'literal': convert_code,
924 'literallayout': convert_literallayout,
925 'mediaobject': convert_div,
926 'note': convert_div,
927 'option': convert_code,
928 'orderedlist': convert_orderedlist,
929 'para': convert_para,
930 'partintro': convert_div,
931 'parameter': convert_em_class,
932 'phrase': convert_phrase,
933 'primaryie': convert_primaryie,
934 'programlisting': convert_programlisting,
935 'quote': convert_quote,
936 'releaseinfo': convert_para_like,
937 'refsect1': convert_refsect1,
938 'refsect2': convert_refsect2,
939 'refsect3': convert_refsect3,
940 'replaceable': convert_em_class,
941 'returnvalue': convert_span,
942 'row': convert_row,
943 'screen': convert_pre,
944 'sect1': convert_sect1_tag,
945 'sect2': convert_sect2,
946 'sect3': convert_sect3,
947 'simpara': convert_simpara,
948 'structfield': convert_em_class,
949 'structname': convert_span,
950 'synopsis': convert_pre,
951 'symbol': convert_span,
952 'table': convert_table,
953 'tbody': convert_tbody,
954 'term': convert_span,
955 'tgroup': convert_tgroup,
956 'thead': convert_thead,
957 'type': convert_span,
958 'ulink': convert_ulink,
959 'userinput': convert_userinput,
960 'varname': convert_code,
961 'variablelist': convert_variablelist,
962 'varlistentry': convert_varlistentry,
963 'warning': convert_div,
966 # conversion helpers
968 HTML_HEADER = """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
969 <html>
970 <head>
971 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
972 <title>%s</title>
973 %s<link rel="stylesheet" href="style.css" type="text/css">
974 </head>
975 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
979 def generate_head_links(ctx):
980 n = ctx['nav_home']
981 result = [
982 '<link rel="home" href="%s" title="%s">\n' % (n.filename, n.title)
984 if 'nav_up' in ctx:
985 n = ctx['nav_up']
986 result.append('<link rel="up" href="%s" title="%s">\n' % (n.filename, n.title))
987 if 'nav_prev' in ctx:
988 n = ctx['nav_prev']
989 result.append('<link rel="prev" href="%s" title="%s">\n' % (n.filename, n.title))
990 if 'nav_next' in ctx:
991 n = ctx['nav_next']
992 result.append('<link rel="next" href="%s" title="%s">\n' % (n.filename, n.title))
993 return ''.join(result)
996 def generate_nav_links(ctx):
997 n = ctx['nav_home']
998 result = [
999 '<td><a accesskey="h" href="%s"><img src="home.png" width="16" height="16" border="0" alt="Home"></a></td>' % n.filename
1001 if 'nav_up' in ctx:
1002 n = ctx['nav_up']
1003 result.append(
1004 '<td><a accesskey="u" href="%s"><img src="up.png" width="16" height="16" border="0" alt="Up"></a></td>' % n.filename)
1005 else:
1006 result.append('<td><img src="up-insensitive.png" width="16" height="16" border="0"></td>')
1007 if 'nav_prev' in ctx:
1008 n = ctx['nav_prev']
1009 result.append(
1010 '<td><a accesskey="p" href="%s"><img src="left.png" width="16" height="16" border="0" alt="Prev"></a></td>' % n.filename)
1011 else:
1012 result.append('<td><img src="left-insensitive.png" width="16" height="16" border="0"></td>')
1013 if 'nav_next' in ctx:
1014 n = ctx['nav_next']
1015 result.append(
1016 '<td><a accesskey="n" href="%s"><img src="right.png" width="16" height="16" border="0" alt="Next"></a></td>' % n.filename)
1017 else:
1018 result.append('<td><img src="right-insensitive.png" width="16" height="16" border="0"></td>')
1020 return ''.join(result)
1023 def generate_toc(ctx, node):
1024 result = []
1025 for c in node.children:
1026 # TODO: urlencode the filename: urllib.parse.quote_plus()
1027 result.append('<dt><span class="%s"><a href="%s">%s</a></span>\n' % (
1028 c.title_tag, c.filename, c.title))
1029 if c.subtitle:
1030 result.append('<span class="%s"> — %s</span>' % (c.subtitle_tag, c.subtitle))
1031 result.append('</dt>\n')
1032 if c.children:
1033 result.append('<dd><dl>')
1034 result.extend(generate_toc(ctx, c))
1035 result.append('</dl></dd>')
1036 return result
1039 def generate_basic_nav(ctx):
1040 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1041 <tr valign="middle">
1042 <td width="100%%" align="left" class="shortcuts"></td>
1044 </tr>
1045 </table>
1046 """ % generate_nav_links(ctx)
1049 def generate_alpha_nav(ctx, divs, prefix):
1050 ix_nav = []
1051 for s in divs:
1052 title = xml_get_title(s)
1053 ix_nav.append('<a class="shortcut" href="#%s%s">%s</a>' % (prefix, title, title))
1055 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1056 <tr valign="middle">
1057 <td width="100%%" align="left" class="shortcuts">
1058 <span id="nav_index">
1060 </span>
1061 </td>
1063 </tr>
1064 </table>
1065 """ % ('\n<span class="dim">|</span>\n'.join(ix_nav), generate_nav_links(ctx))
1068 def generate_refentry_nav(ctx, refsect1s, result):
1069 result.append("""<table class="navigation" id="top" width="100%" cellpadding="2" cellspacing="5">
1070 <tr valign="middle">
1071 <td width="100%" align="left" class="shortcuts">
1072 <a href="#" class="shortcut">Top</a>""")
1074 for s in refsect1s:
1075 # don't list TOC sections (role="xxx_proto")
1076 if s.attrib.get('role', '').endswith("_proto"):
1077 continue
1078 # skip section without 'id' attrs
1079 if 'id' not in s.attrib:
1080 continue
1082 title = xml_get_title(s)
1083 result.append("""
1084   <span class="dim">|</span> 
1085 <a href="#%s" class="shortcut">%s</a>
1086 """ % (s.attrib['id'], title))
1087 result.append("""
1088 </td>
1090 </tr>
1091 </table>
1092 """ % generate_nav_links(ctx))
1095 def generate_footer(ctx):
1096 result = []
1097 if 'footnotes' in ctx:
1098 result.append("""<div class="footnotes">\n
1099 <br><hr style="width:100; text-align:left;margin-left: 0">
1100 """)
1101 for f in ctx['footnotes']:
1102 result.extend(f)
1103 result.append('</div>\n')
1104 return result
1107 def get_id(node):
1108 xml = node.xml
1109 node_id = xml.attrib.get('id', None)
1110 if node_id:
1111 return node_id
1113 logging.info('%d: No "id" attribute on "%s", generating one',
1114 xml.sourceline, xml.tag)
1115 ix = []
1116 # Generate the 'id'. We need to walk up the xml-tree and check the positions
1117 # for each sibling.
1118 parent = xml.getparent()
1119 while parent is not None:
1120 children = parent.getchildren()
1121 ix.insert(0, str(children.index(xml) + 1))
1122 xml = parent
1123 parent = xml.getparent()
1124 # logging.warning('%s: id indexes: %s', node.filename, str(ix))
1125 return 'id-1.' + '.'.join(ix)
1128 def convert_chunk_with_toc(ctx, div_class, title_tag):
1129 node = ctx['node']
1130 result = [
1131 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
1132 generate_basic_nav(ctx),
1133 '<div class="%s">' % div_class,
1135 title = node.xml.find('title')
1136 if title is not None:
1137 result.append("""
1138 <div class="titlepage">
1139 <%s class="title"><a name="%s"></a>%s</%s>
1140 </div>""" % (
1141 title_tag, get_id(node), title.text, title_tag))
1142 node.xml.remove(title)
1143 convert_inner(ctx, node.xml, result)
1144 result.append("""<p>
1145 <b>Table of Contents</b>
1146 </p>
1147 <div class="toc">
1148 <dl class="toc">
1149 """)
1150 result.extend(generate_toc(ctx, node))
1151 result.append("""</dl>
1152 </div>
1153 """)
1154 result.extend(generate_footer(ctx))
1155 result.append("""</div>
1156 </body>
1157 </html>""")
1158 return result
1161 # docbook chunks
1164 def convert_book(ctx):
1165 node = ctx['node']
1166 result = [
1167 HTML_HEADER % (node.title, generate_head_links(ctx)),
1168 """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="0">
1169 <tr><th valign="middle"><p class="title">%s</p></th></tr>
1170 </table>
1171 <div class="book">
1172 """ % node.title
1174 bookinfo = node.xml.findall('bookinfo')[0]
1175 # we already used the title
1176 title = bookinfo.find('title')
1177 if title is not None:
1178 bookinfo.remove(title)
1179 result.extend(convert_bookinfo(ctx, bookinfo))
1180 result.append("""<div class="toc">
1181 <dl class="toc">
1182 """)
1183 result.extend(generate_toc(ctx, node.root))
1184 result.append("""</dl>
1185 </div>
1186 """)
1187 result.extend(generate_footer(ctx))
1188 result.append("""</div>
1189 </body>
1190 </html>""")
1191 return result
1194 def convert_chapter(ctx):
1195 return convert_chunk_with_toc(ctx, 'chapter', 'h2')
1198 def convert_glossary(ctx):
1199 node = ctx['node']
1200 glossdivs = node.xml.findall('glossdiv')
1202 result = [
1203 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
1204 generate_alpha_nav(ctx, glossdivs, 'gls'),
1205 """<div class="glossary">
1206 <div class="titlepage"><h1 class="title">
1207 <a name="%s"></a>%s</h1>
1208 </div>""" % (get_id(node), node.title)
1210 for i in glossdivs:
1211 result.extend(convert_glossdiv(ctx, i))
1212 result.extend(generate_footer(ctx))
1213 result.append("""</div>
1214 </body>
1215 </html>""")
1216 return result
1219 def convert_index(ctx):
1220 node = ctx['node']
1221 # Get all indexdivs under indexdiv
1222 indexdivs = node.xml.find('indexdiv').findall('indexdiv')
1224 result = [
1225 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
1226 generate_alpha_nav(ctx, indexdivs, 'idx'),
1227 """<div class="index">
1228 <div class="titlepage"><h2 class="title">
1229 <a name="%s"></a>%s</h2>
1230 </div>""" % (get_id(node), node.title)
1232 for i in indexdivs:
1233 result.extend(convert_indexdiv(ctx, i))
1234 result.extend(generate_footer(ctx))
1235 result.append("""</div>
1236 </body>
1237 </html>""")
1238 return result
1241 def convert_part(ctx):
1242 return convert_chunk_with_toc(ctx, 'part', 'h1')
1245 def convert_preface(ctx):
1246 node = ctx['node']
1247 result = [
1248 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
1249 generate_basic_nav(ctx),
1250 '<div class="preface">'
1252 title = node.xml.find('title')
1253 if title is not None:
1254 result.append("""
1255 <div class="titlepage">
1256 <h2 class="title"><a name="%s"></a>%s</h2>
1257 </div>""" % (get_id(node), title.text))
1258 node.xml.remove(title)
1259 convert_inner(ctx, node.xml, result)
1260 result.extend(generate_footer(ctx))
1261 result.append("""</div>
1262 </body>
1263 </html>""")
1264 return result
1267 def convert_reference(ctx):
1268 return convert_chunk_with_toc(ctx, 'reference', 'h1')
1271 def convert_refentry(ctx):
1272 node = ctx['node']
1273 node_id = get_id(node)
1274 refsect1s = node.xml.findall('refsect1')
1276 result = [
1277 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx))
1279 generate_refentry_nav(ctx, refsect1s, result)
1280 result.append("""
1281 <div class="refentry">
1282 <a name="%s"></a>
1283 <div class="refnamediv">
1284 <table width="100%%"><tr>
1285 <td valign="top">
1286 <h2><span class="refentrytitle"><a name="%s.top_of_page"></a>%s</span></h2>
1287 <p>%s — module for gtk-doc unit test</p>
1288 </td>
1289 <td class="gallery_image" valign="top" align="right"></td>
1290 </tr></table>
1291 </div>
1292 """ % (node_id, node_id, node.title, node.title))
1294 for s in refsect1s:
1295 result.extend(convert_refsect1(ctx, s))
1296 result.extend(generate_footer(ctx))
1297 result.append("""</div>
1298 </body>
1299 </html>""")
1300 return result
1303 def convert_sect1(ctx):
1304 return convert_chunk_with_toc(ctx, 'sect1', 'h2')
1307 # TODO(ensonic): turn into class with converters as functions and ctx as self
1308 convert_chunks = {
1309 'book': convert_book,
1310 'chapter': convert_chapter,
1311 'glossary': convert_glossary,
1312 'index': convert_index,
1313 'part': convert_part,
1314 'preface': convert_preface,
1315 'reference': convert_reference,
1316 'refentry': convert_refentry,
1317 'sect1': convert_sect1,
1321 def generate_nav_nodes(files, node):
1322 nav = {
1323 'nav_home': node.root,
1325 # nav params: up, prev, next
1326 if node.parent:
1327 nav['nav_up'] = node.parent
1328 ix = files.index(node)
1329 if ix > 0:
1330 nav['nav_prev'] = files[ix - 1]
1331 if ix < len(files) - 1:
1332 nav['nav_next'] = files[ix + 1]
1333 return nav
1336 def convert(out_dir, module, files, node):
1337 """Convert the docbook chunks to a html file.
1339 Args:
1340 out_dir: already created output dir
1341 files: list of nodes in the tree in pre-order
1342 node: current tree node
1345 logging.info('Writing: %s', node.filename)
1346 with open(os.path.join(out_dir, node.filename), 'wt',
1347 newline='\n', encoding='utf-8') as html:
1348 ctx = {
1349 'module': module,
1350 'files': files,
1351 'node': node,
1353 ctx.update(generate_nav_nodes(files, node))
1355 if node.name in convert_chunks:
1356 for line in convert_chunks[node.name](ctx):
1357 html.write(line)
1358 else:
1359 logging.warning('Add converter/template for "%s"', node.name)
1362 def create_devhelp2_toc(node):
1363 result = []
1364 for c in node.children:
1365 if c.children:
1366 result.append('<sub name="%s" link="%s">\n' % (c.title, c.filename))
1367 result.extend(create_devhelp2_toc(c))
1368 result.append('</sub>\n')
1369 else:
1370 result.append('<sub name="%s" link="%s"/>\n' % (c.title, c.filename))
1371 return result
1374 def create_devhelp2_condition_attribs(node):
1375 if 'condition' in node.attrib:
1376 # condition -> since, deprecated, ... (separated with '|')
1377 cond = node.attrib['condition'].replace('"', '&quot;').split('|')
1378 keywords = []
1379 for c in cond:
1380 if ':' in c:
1381 keywords.append('{}="{}"'.format(*c.split(':', 1)))
1382 else:
1383 # deprecated can have no description
1384 keywords.append('{}="{}"'.format(c, ''))
1385 return ' ' + ' '.join(keywords)
1386 else:
1387 return ''
1390 def create_devhelp2_refsect2_keyword(node, base_link):
1391 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1392 node.attrib['role'], xml_get_title(node), base_link + node.attrib['id'],
1393 create_devhelp2_condition_attribs(node))
1396 def create_devhelp2_refsect3_keyword(node, base_link, title, name):
1397 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1398 node.attrib['role'], title, base_link + name,
1399 create_devhelp2_condition_attribs(node))
1402 def create_devhelp2(out_dir, module, xml, files):
1403 with open(os.path.join(out_dir, module + '.devhelp2'), 'wt',
1404 newline='\n', encoding='utf-8') as idx:
1405 bookinfo_nodes = xml.xpath('/book/bookinfo')
1406 title = ''
1407 if bookinfo_nodes is not None:
1408 bookinfo = bookinfo_nodes[0]
1409 title = bookinfo.xpath('./title/text()')[0]
1410 online_url = bookinfo.xpath('./releaseinfo/ulink[@role="online-location"]/@url')[0]
1411 # TODO: support author too (see devhelp2.xsl)
1412 # TODO: fixxref uses '--src-lang' to set the language
1413 result = [
1414 """<?xml version="1.0" encoding="utf-8" standalone="no"?>
1415 <book xmlns="http://www.devhelp.net/book" title="%s" link="index.html" author="" name="%s" version="2" language="c" online="%s">
1416 <chapters>
1417 """ % (title, module, online_url)
1419 # toc
1420 result.extend(create_devhelp2_toc(files[0].root))
1421 result.append(""" </chapters>
1422 <functions>
1423 """)
1424 # keywords from all refsect2 and refsect3
1425 refsect2 = etree.XPath('//refsect2[@role]')
1426 refsect3_enum = etree.XPath('refsect3[@role="enum_members"]/informaltable/tgroup/tbody/row[@role="constant"]')
1427 refsect3_enum_details = etree.XPath('entry[@role="enum_member_name"]/para')
1428 refsect3_struct = etree.XPath('refsect3[@role="struct_members"]/informaltable/tgroup/tbody/row[@role="member"]')
1429 refsect3_struct_details = etree.XPath('entry[@role="struct_member_name"]/para/structfield')
1430 for node in files:
1431 base_link = node.filename + '#'
1432 refsect2_nodes = refsect2(node.xml)
1433 for refsect2_node in refsect2_nodes:
1434 result.append(create_devhelp2_refsect2_keyword(refsect2_node, base_link))
1435 refsect3_nodes = refsect3_enum(refsect2_node)
1436 for refsect3_node in refsect3_nodes:
1437 details_node = refsect3_enum_details(refsect3_node)[0]
1438 name = details_node.attrib['id']
1439 result.append(create_devhelp2_refsect3_keyword(refsect3_node, base_link, details_node.text, name))
1440 refsect3_nodes = refsect3_struct(refsect2_node)
1441 for refsect3_node in refsect3_nodes:
1442 details_node = refsect3_struct_details(refsect3_node)[0]
1443 name = details_node.attrib['id']
1444 result.append(create_devhelp2_refsect3_keyword(refsect3_node, base_link, name, name))
1446 result.append(""" </functions>
1447 </book>
1448 """)
1449 for line in result:
1450 idx.write(line)
1453 def get_dirs(uninstalled):
1454 if uninstalled:
1455 # this does not work from buiddir!=srcdir
1456 gtkdocdir = os.path.split(sys.argv[0])[0]
1457 if not os.path.exists(gtkdocdir + '/gtk-doc.xsl'):
1458 # try 'srcdir' (set from makefiles) too
1459 if os.path.exists(os.environ.get("ABS_TOP_SRCDIR", '') + '/gtk-doc.xsl'):
1460 gtkdocdir = os.environ['ABS_TOP_SRCDIR']
1461 styledir = gtkdocdir + '/style'
1462 else:
1463 gtkdocdir = os.path.join(config.datadir, 'gtk-doc/data')
1464 styledir = gtkdocdir
1465 return (gtkdocdir, styledir)
1468 def main(module, index_file, out_dir, uninstalled):
1469 tree = etree.parse(index_file)
1470 tree.xinclude()
1472 (gtkdocdir, styledir) = get_dirs(uninstalled)
1473 # copy navigation images and stylesheets to html directory ...
1474 css_file = os.path.join(styledir, 'style.css')
1475 for f in glob(os.path.join(styledir, '*.png')) + [css_file]:
1476 shutil.copy(f, out_dir)
1477 css_file = os.path.join(out_dir, 'style.css')
1478 with open(css_file, 'at', newline='\n', encoding='utf-8') as css:
1479 css.write(HTML_FORMATTER.get_style_defs())
1481 # TODO: migrate options from fixxref
1482 # TODO: do in parallel with loading the xml above.
1483 fixxref.LoadIndicies(out_dir, '/usr/share/gtk-doc/html', [])
1485 # We do multiple passes:
1486 # 1) recursively walk the tree and chunk it into a python tree so that we
1487 # can generate navigation and link tags.
1488 files = chunk(tree.getroot())
1489 files = list(PreOrderIter(files))
1490 # 2) extract tables:
1491 # TODO: use multiprocessing
1492 # - find all 'id' attribs and add them to the link map
1493 add_id_links(files, fixxref.Links)
1494 # - build glossary dict
1495 build_glossary(files)
1497 # 3) create a xxx.devhelp2 file, do this before 3), since we modify the tree
1498 create_devhelp2(out_dir, module, tree.getroot(), files)
1499 # 4) iterate the tree and output files
1500 # TODO: use multiprocessing
1501 for node in files:
1502 convert(out_dir, module, files, node)
1505 def run(options):
1506 logging.info('options: %s', str(options.__dict__))
1507 module = options.args[0]
1508 document = options.args[1]
1510 # TODO: rename to 'html' later on
1511 # - right now in mkhtml, the dir is created by the Makefile and mkhtml
1512 # outputs into the working directory
1513 out_dir = os.path.join(os.path.dirname(document), 'db2html')
1514 try:
1515 os.mkdir(out_dir)
1516 except OSError as e:
1517 if e.errno != errno.EEXIST:
1518 raise
1520 sys.exit(main(module, document, out_dir, options.uninstalled))