mkhtml2: add boilerplate to generate the devhelp2 files
[gtk-doc.git] / gtkdoc / mkhtml2.py
blob6a0fe7f49ddafddafc70acccc0cf27317ca23536
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 """Prototype for builtin docbook processing
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 htnml 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 TODO:
33 - more chunk converters
34 - check each docbook tag if it can contain #PCDATA, if not don't check for
35 xml.text
36 - integrate fixxref:
37 - as a step, we could run FixHTMLFile() on each output file
38 - integrate syntax-highlighing from fixxref
39 - maybe handle the combination <informalexample><programlisting> directly
40 - switch to http://pygments.org/docs/quickstart/?
41 - integrate MakeXRef from fixxref
42 - create devhelp2 output
44 OPTIONAL:
45 - minify html: https://pypi.python.org/pypi/htmlmin/
47 Requirements:
48 sudo pip3 install anytree lxml
50 Examples:
51 ./gtkdoc-mkhtml2 tests/gobject/docs/tester-docs.xml
52 ll tests/gobject/docs/db2html
54 ./gtkdoc-mkhtml2 tests/bugs/docs/tester-docs.xml
55 ll tests/bugs/docs/db2html
56 cp tests/bugs/docs/html/*.{css,png} tests/bugs/docs/db2html/
57 xdg-open tests/bugs/docs/db2html/index.html
58 meld tests/bugs/docs/{html,db2html}
60 Benchmarking:
61 (cd tests/bugs/docs/; rm html-build.stamp; time make html-build.stamp)
62 """
64 import argparse
65 import errno
66 import logging
67 import os
68 import sys
70 from anytree import Node, PreOrderIter
71 from lxml import etree
73 from .fixxref import NoLinks
75 # http://www.sagehill.net/docbookxsl/Chunking.html
76 CHUNK_TAGS = [
77 'appendix',
78 'article',
79 'bibliography', # in article or book
80 'book',
81 'chapter',
82 'colophon',
83 'glossary', # in article or book
84 'index', # in article or book
85 'part',
86 'preface',
87 'refentry',
88 'reference',
89 'sect1', # except first
90 'section', # if equivalent to sect1
91 'set',
92 'setindex',
96 class ChunkParams(object):
97 def __init__(self, prefix, parent=None):
98 self.prefix = prefix
99 self.parent = None
100 self.count = 0
103 # TODO: look up the abbrevs and hierarchy for other tags
104 # http://www.sagehill.net/docbookxsl/Chunking.html#GeneratedFilenames
105 # https://github.com/oreillymedia/HTMLBook/blob/master/htmlbook-xsl/chunk.xsl#L33
106 CHUNK_PARAMS = {
107 'appendix': ChunkParams('app', 'book'),
108 'book': ChunkParams('bk'),
109 'chapter': ChunkParams('ch', 'book'),
110 'index': ChunkParams('ix', 'book'),
111 'part': ChunkParams('pt', 'book'),
112 'sect1': ChunkParams('s', 'chapter'),
113 'section': ChunkParams('s', 'chapter'),
116 TITLE_XPATHS = {
117 '_': (etree.XPath('./title'), None),
118 'book': (etree.XPath('./bookinfo/title'), None),
119 'refentry': (
120 etree.XPath('./refmeta/refentrytitle'),
121 etree.XPath('./refnamediv/refpurpose')
126 def gen_chunk_name(node):
127 if 'id' in node.attrib:
128 return node.attrib['id']
130 tag = node.tag
131 if tag not in CHUNK_PARAMS:
132 CHUNK_PARAMS[tag] = ChunkParams(node.tag[:2])
133 logging.warning('Add CHUNK_PARAMS for "%s"', tag)
135 naming = CHUNK_PARAMS[tag]
136 naming.count += 1
137 name = ('%s%02d' % (naming.prefix, naming.count))
138 # handle parents to make names of nested tags unique
139 # TODO: we only need to prepend the parent if there are > 1 of them in the
140 # xml
141 # while naming.parent:
142 # parent = naming.parent
143 # if parent not in CHUNK_PARAMS:
144 # break;
145 # naming = CHUNK_PARAMS[parent]
146 # name = ('%s%02d' % (naming.prefix, naming.count)) + name
147 return name
150 def get_chunk_titles(node):
151 tag = node.tag
152 if tag not in TITLE_XPATHS:
153 # Use defaults
154 (title, subtitle) = TITLE_XPATHS['_']
155 else:
156 (title, subtitle) = TITLE_XPATHS[tag]
158 xml = title(node)[0]
159 result = {
160 'title': xml.text
162 if xml.tag != 'title':
163 result['title_tag'] = xml.tag
164 else:
165 result['title_tag'] = tag
167 if subtitle:
168 xml = subtitle(node)[0]
169 result['subtitle'] = xml.text
170 result['subtitle_tag'] = xml.tag
171 else:
172 result['subtitle'] = None
173 result['subtitle_tag'] = None
174 return result
177 def chunk(xml_node, parent=None):
178 """Chunk the tree.
180 The first time, we're called with parent=None and in that case we return
181 the new_node as the root of the tree
183 # print('<%s %s>' % (xml_node.tag, xml_node.attrib))
184 if xml_node.tag in CHUNK_TAGS:
185 # TODO: do we need to remove the xml-node from the parent?
187 # from copy import deepcopy
188 # sub_tree = deepcopy(xml_node)
189 # xml_node.getparent().remove(xml_node)
190 # # or:
191 # sub_tree = etree.ElementTree(xml_node).getroot()
192 title_args = get_chunk_titles(xml_node)
193 parent = Node(xml_node.tag, parent=parent, xml=xml_node,
194 filename=gen_chunk_name(xml_node) + '.html',
195 **title_args)
196 for child in xml_node:
197 chunk(child, parent)
199 return parent
201 # conversion helpers
204 def escape_entities(text):
205 return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
208 def convert_inner(ctx, xml, result):
209 for child in xml:
210 result.extend(convert_tags.get(child.tag, convert__unknown)(ctx, child))
213 def convert_ignore(ctx, xml):
214 result = []
215 convert_inner(ctx, xml, result)
216 return result
219 def convert_skip(ctx, xml):
220 return ['']
223 missing_tags = {}
226 def convert__unknown(ctx, xml):
227 # don't recurse on subchunks
228 if xml.tag in CHUNK_TAGS:
229 return []
230 # warn only once
231 if xml.tag not in missing_tags:
232 logging.warning('Add tag converter for "%s"', xml.tag)
233 missing_tags[xml.tag] = True
234 result = ['<!-- ' + xml.tag + '-->\n']
235 convert_inner(ctx, xml, result)
236 result.append('<!-- /' + xml.tag + '-->\n')
237 return result
240 def convert_refsect(ctx, xml, h_tag, inner_func=convert_inner):
241 result = ['<div class="%s">\n' % xml.tag]
242 title = xml.find('title')
243 if title is not None:
244 if 'id' in xml.attrib:
245 result.append('<a name="%s"></a>' % xml.attrib['id'])
246 result.append('<%s>%s</%s>' % (h_tag, title.text, h_tag))
247 xml.remove(title)
248 if xml.text:
249 result.append(xml.text)
250 inner_func(ctx, xml, result)
251 result.append('</div>')
252 if xml.tail:
253 result.append(xml.tail)
254 return result
257 def xml_get_title(xml):
258 title = xml.find('title')
259 if title is not None:
260 return title.text
261 else:
262 # TODO(ensonic): any way to get the file (inlcudes) too?
263 logging.warning('%s: Expected title tag under "%s"', xml.sourceline, xml.tag)
264 return ''
267 # docbook tags
269 def convert_bookinfo(ctx, xml):
270 result = ['<div class="titlepage">']
271 for releaseinfo in xml.findall('releaseinfo'):
272 result.extend(convert_para(ctx, releaseinfo))
273 result.append("""<hr>
274 </div>""")
275 if xml.tail:
276 result.append(xml.tail)
277 return result
280 def convert_colspec(ctx, xml):
281 result = ['<col']
282 a = xml.attrib
283 if 'colname' in a:
284 result.append(' class="%s"' % a['colname'])
285 if 'colwidth' in a:
286 result.append(' width="%s"' % a['colwidth'])
287 result.append('>\n')
288 # is in tgroup and there can be no 'text'
289 return result
292 def convert_div(ctx, xml):
293 result = ['<div class="%s">\n' % xml.tag]
294 if xml.text:
295 result.append(xml.text)
296 convert_inner(ctx, xml, result)
297 result.append('</div>')
298 if xml.tail:
299 result.append(xml.tail)
300 return result
303 def convert_em_class(ctx, xml):
304 result = ['<em class="%s"><code>' % xml.tag]
305 if xml.text:
306 result.append(xml.text)
307 convert_inner(ctx, xml, result)
308 result.append('</code></em>')
309 if xml.tail:
310 result.append(xml.tail)
311 return result
314 def convert_entry(ctx, xml):
315 result = ['<td']
316 if 'role' in xml.attrib:
317 result.append(' class="%s">' % xml.attrib['role'])
318 else:
319 result.append('>')
320 if xml.text:
321 result.append(xml.text)
322 convert_inner(ctx, xml, result)
323 result.append('</td>')
324 if xml.tail:
325 result.append(xml.tail)
326 return result
329 def convert_indexdiv(ctx, xml):
330 title_tag = xml.find('title')
331 title = title_tag.text
332 xml.remove(title_tag)
333 result = [
334 '<a name="idx%s"></a><h3 class="title">%s</h3>' % (title, title)
336 convert_inner(ctx, xml, result)
337 return result
340 def convert_informaltable(ctx, xml):
341 result = ['<div class="informaltable"><table class="informaltable"']
342 a = xml.attrib
343 if 'pgwide' in a and a['pgwide'] == '1':
344 result.append(' width="100%"')
345 if 'frame' in a and a['frame'] == 'none':
346 result.append(' border="0"')
347 result.append('>\n')
348 convert_inner(ctx, xml, result)
349 result.append('</table></div>')
350 if xml.tail:
351 result.append(xml.tail)
352 return result
355 def convert_itemizedlist(ctx, xml):
356 result = ['<div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; ">']
357 convert_inner(ctx, xml, result)
358 result.append('</ul></div>')
359 if xml.tail:
360 result.append(xml.tail)
361 return result
364 def convert_link(ctx, xml):
365 # TODO: inline more fixxref functionality
366 # TODO: need to build an 'id' map and resolve against internal links too
367 linkend = xml.attrib['linkend']
368 if linkend in NoLinks:
369 linkend = None
370 result = []
371 if linkend:
372 result = ['<!-- GTKDOCLINK HREF="%s" -->' % linkend]
373 if xml.text:
374 result.append(xml.text)
375 convert_inner(ctx, xml, result)
376 if linkend:
377 result.append('<!-- /GTKDOCLINK -->')
378 if xml.tail:
379 result.append(xml.tail)
380 return result
383 def convert_listitem(ctx, xml):
384 result = ['<li class="listitem">']
385 convert_inner(ctx, xml, result)
386 result.append('</li>')
387 # is in itemizedlist and there can be no 'text'
388 return result
391 def convert_literal(ctx, xml):
392 result = ['<code class="%s">' % xml.tag]
393 if xml.text:
394 result.append(xml.text)
395 convert_inner(ctx, xml, result)
396 result.append('</code>')
397 if xml.tail:
398 result.append(xml.tail)
399 return result
402 def convert_para(ctx, xml):
403 result = ['<p>']
404 if xml.tag != 'para':
405 result = ['<p class="%s">' % xml.tag]
406 if xml.text:
407 result.append(xml.text)
408 convert_inner(ctx, xml, result)
409 result.append('</p>')
410 if xml.tail:
411 result.append(xml.tail)
412 return result
415 def convert_phrase(ctx, xml):
416 result = ['<span']
417 if 'role' in xml.attrib:
418 result.append(' class="%s">' % xml.attrib['role'])
419 else:
420 result.append('>')
421 if xml.text:
422 result.append(xml.text)
423 convert_inner(ctx, xml, result)
424 result.append('</span>')
425 if xml.tail:
426 result.append(xml.tail)
427 return result
430 def convert_primaryie(ctx, xml):
431 result = ['<dt>']
432 convert_inner(ctx, xml, result)
433 result.append('</dt>\n<dd></dd>\n')
434 return result
437 def convert_programlisting(ctx, xml):
438 result = ['<pre class="programlisting">']
439 if xml.text:
440 result.append(escape_entities(xml.text))
441 convert_inner(ctx, xml, result)
442 result.append('</pre>')
443 if xml.tail:
444 result.append(xml.tail)
445 return result
448 def convert_refsect1(ctx, xml):
449 # Add a divider between two consequitive refsect2
450 def convert_inner(ctx, xml, result):
451 prev = None
452 for child in xml:
453 if child.tag == 'refsect2' and prev is not None and prev.tag == child.tag:
454 result.append('<hr>\n')
455 result.extend(convert_tags.get(child.tag, convert__unknown)(ctx, child))
456 prev = child
457 return convert_refsect(ctx, xml, 'h2', convert_inner)
460 def convert_refsect2(ctx, xml):
461 return convert_refsect(ctx, xml, 'h3')
464 def convert_refsect3(ctx, xml):
465 return convert_refsect(ctx, xml, 'h4')
468 def convert_row(ctx, xml):
469 result = ['<tr>\n']
470 convert_inner(ctx, xml, result)
471 result.append('</tr>\n')
472 return result
475 def convert_span(ctx, xml):
476 result = ['<span class="%s">' % xml.tag]
477 if xml.text:
478 result.append(xml.text)
479 convert_inner(ctx, xml, result)
480 result.append('</span>')
481 if xml.tail:
482 result.append(xml.tail)
483 return result
486 def convert_tbody(ctx, xml):
487 result = ['<tbody>']
488 convert_inner(ctx, xml, result)
489 result.append('</tbody>')
490 # is in tgroup and there can be no 'text'
491 return result
494 def convert_tgroup(ctx, xml):
495 # tgroup does not expand to anything, but the nested colspecs need to
496 # be put into a colgroup
497 cols = xml.findall('colspec')
498 result = []
499 if cols:
500 result.append('<colgroup>\n')
501 for col in cols:
502 result.extend(convert_colspec(ctx, col))
503 xml.remove(col)
504 result.append('</colgroup>\n')
505 convert_inner(ctx, xml, result)
506 # is in informaltable and there can be no 'text'
507 return result
510 def convert_ulink(ctx, xml):
511 result = ['<a class="%s" href="%s">%s</a>' % (xml.tag, xml.attrib['url'], xml.text)]
512 if xml.tail:
513 result.append(xml.tail)
514 return result
517 # TODO(ensonic): turn into class with converters as functions and ctx as self
518 convert_tags = {
519 'bookinfo': convert_bookinfo,
520 'colspec': convert_colspec,
521 'entry': convert_entry,
522 'function': convert_span,
523 'indexdiv': convert_indexdiv,
524 'indexentry': convert_ignore,
525 'indexterm': convert_skip,
526 'informalexample': convert_div,
527 'informaltable': convert_informaltable,
528 'itemizedlist': convert_itemizedlist,
529 'link': convert_link,
530 'listitem': convert_listitem,
531 'literal': convert_literal,
532 'para': convert_para,
533 'parameter': convert_em_class,
534 'phrase': convert_phrase,
535 'primaryie': convert_primaryie,
536 'programlisting': convert_programlisting,
537 'releaseinfo': convert_para,
538 'refsect1': convert_refsect1,
539 'refsect2': convert_refsect2,
540 'refsect3': convert_refsect3,
541 'returnvalue': convert_span,
542 'row': convert_row,
543 'structfield': convert_em_class,
544 'tbody': convert_tbody,
545 'tgroup': convert_tgroup,
546 'type': convert_span,
547 'ulink': convert_ulink,
548 'warning': convert_div,
551 # conversion helpers
553 HTML_HEADER = """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
554 <html>
555 <head>
556 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
557 <title>%s</title>
558 %s<link rel="stylesheet" href="style.css" type="text/css">
559 </head>
560 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
564 def generate_head_links(ctx):
565 n = ctx['nav_home']
566 result = [
567 '<link rel="home" href="%s" title="%s">\n' % (n.filename, n.title)
569 if 'nav_up' in ctx:
570 n = ctx['nav_up']
571 result.append('<link rel="up" href="%s" title="%s">\n' % (n.filename, n.title))
572 if 'nav_prev' in ctx:
573 n = ctx['nav_prev']
574 result.append('<link rel="prev" href="%s" title="%s">\n' % (n.filename, n.title))
575 if 'nav_next' in ctx:
576 n = ctx['nav_next']
577 result.append('<link rel="next" href="%s" title="%s">\n' % (n.filename, n.title))
578 return ''.join(result)
581 def generate_nav_links(ctx):
582 n = ctx['nav_home']
583 result = [
584 '<td><a accesskey="h" href="%s"><img src="home.png" width="16" height="16" border="0" alt="Home"></a></td>' % n.filename
586 if 'nav_up' in ctx:
587 n = ctx['nav_up']
588 result.append(
589 '<td><a accesskey="u" href="%s"><img src="up.png" width="16" height="16" border="0" alt="Up"></a></td>' % n.filename)
590 else:
591 result.append('<td><img src="up-insensitive.png" width="16" height="16" border="0"></td>')
592 if 'nav_prev' in ctx:
593 n = ctx['nav_prev']
594 result.append(
595 '<td><a accesskey="p" href="%s"><img src="left.png" width="16" height="16" border="0" alt="Prev"></a></td>' % n.filename)
596 else:
597 result.append('<td><img src="left-insensitive.png" width="16" height="16" border="0"></td>')
598 if 'nav_next' in ctx:
599 n = ctx['nav_next']
600 result.append(
601 '<td><a accesskey="n" href="%s"><img src="right.png" width="16" height="16" border="0" alt="Next"></a></td>' % n.filename)
602 else:
603 result.append('<td><img src="right-insensitive.png" width="16" height="16" border="0"></td>')
605 return ''.join(result)
608 def generate_toc(ctx, node):
609 result = []
610 for c in node.children:
611 # TODO: urlencode the filename: urllib.parse.quote_plus()
612 result.append('<dt><span class="%s"><a href="%s">%s</a></span>\n' % (
613 c.title_tag, c.filename, c.title))
614 if c.subtitle:
615 result.append('<span class="%s"> — %s</span>' % (c.subtitle_tag, c.subtitle))
616 result.append('</dt>\n')
617 if c.children:
618 result.append('<dd><dl>')
619 result.extend(generate_toc(ctx, c))
620 result.append('</dl></dd>')
621 return result
624 def generate_basic_nav(ctx):
625 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
626 <tr valign="middle">
627 <td width="100%%" align="left" class="shortcuts"></td>
629 </tr>
630 </table>
631 """ % generate_nav_links(ctx)
634 def generate_index_nav(ctx, indexdivs):
635 ix_nav = []
636 for s in indexdivs:
637 title = xml_get_title(s)
638 ix_nav.append('<a class="shortcut" href="#idx%s">%s</a>' % (title, title))
640 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
641 <tr valign="middle">
642 <td width="100%%" align="left" class="shortcuts">
643 <span id="nav_index">
645 </span>
646 </td>
648 </tr>
649 </table>
650 """ % ('\n<span class="dim">|</span>\n'.join(ix_nav), generate_nav_links(ctx))
653 def generate_refentry_nav(ctx, refsect1s, result):
654 result.append("""<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
655 <tr valign="middle">
656 <td width="100%%" align="left" class="shortcuts">
657 <a href="#" class="shortcut">Top</a>""")
659 for s in refsect1s:
660 # don't list TOC sections (role="xxx_proto")
661 if s.attrib.get('role', '').endswith("_proto"):
662 continue
664 title = xml_get_title(s)
665 result.append("""
666 <span id="nav_description">
667   <span class="dim">|</span> 
668 <a href="#%s" class="shortcut">%s</a>
669 </span>""" % (s.attrib['id'], title))
670 result.append("""
671 </td>
673 </tr>
674 </table>
675 """ % generate_nav_links(ctx))
678 def get_id(node):
679 xml = node.xml
680 node_id = xml.attrib.get('id', None)
681 if node_id:
682 return node_id
684 logging.warning('%d: No "id" attribute on "%s"', xml.sourceline, xml.tag)
685 ix = []
686 # Generate the 'id'. We need to walk up the xml-tree and check the positions
687 # for each sibling.
688 parent = xml.getparent()
689 while parent is not None:
690 children = parent.getchildren()
691 ix.insert(0, str(children.index(xml) + 1))
692 xml = parent
693 parent = xml.getparent()
694 # logging.warning('%s: id indexes: %s', node.filename, str(ix))
695 return 'id-1.' + '.'.join(ix)
698 # docbook chunks
701 def convert_book(ctx):
702 node = ctx['node']
703 result = [
704 HTML_HEADER % (node.title, generate_head_links(ctx)),
705 """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="0">
706 <tr><th valign="middle"><p class="title">%s</p></th></tr>
707 </table>
708 <div class="book">
709 """ % node.title
711 bookinfo = node.xml.findall('bookinfo')[0]
712 result.extend(convert_bookinfo(ctx, bookinfo))
713 result.append("""<div class="toc">
714 <dl class="toc">
715 """)
716 result.extend(generate_toc(ctx, node.root))
717 result.append("""</dl>
718 </div>
719 </div>
720 </body>
721 </html>""")
722 return result
725 def convert_chapter(ctx):
726 node = ctx['node']
727 result = [
728 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
729 generate_basic_nav(ctx),
730 '<div class="chapter">',
732 title = node.xml.find('title')
733 if title is not None:
734 result.append('<div class="titlepage"><h1 class="title"><a name="%s"></a>%s</h1></div>' % (
735 get_id(node), title.text))
736 node.xml.remove(title)
737 convert_inner(ctx, node.xml, result)
738 result.append("""<div class="toc">
739 <dl class="toc">
740 """)
741 result.extend(generate_toc(ctx, node))
742 result.append("""</dl>
743 </div>
744 </div>
745 </body>
746 </html>""")
747 return result
750 def convert_index(ctx):
751 node = ctx['node']
752 node_id = get_id(node)
753 # Get all indexdivs under indexdiv
754 indexdivs = node.xml.find('indexdiv').findall('indexdiv')
756 result = [
757 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)),
758 generate_index_nav(ctx, indexdivs),
759 """<div class="index">
760 <div class="titlepage"><h1 class="title">
761 <a name="%s"></a>%s</h1>
762 </div>""" % (node_id, node.title)
764 for i in indexdivs:
765 result.extend(convert_indexdiv(ctx, i))
766 result.append("""</div>
767 </body>
768 </html>""")
769 return result
772 def convert_refentry(ctx):
773 node = ctx['node']
774 node_id = get_id(node)
775 refsect1s = node.xml.findall('refsect1')
777 result = [
778 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx))
780 generate_refentry_nav(ctx, refsect1s, result)
781 result.append("""
782 <div class="refentry">
783 <a name="%s"></a>
784 <div class="refnamediv">
785 <table width="100%%"><tr>
786 <td valign="top">
787 <h2><span class="refentrytitle"><a name="%s.top_of_page"></a>%s</span></h2>
788 <p>%s — module for gtk-doc unit test</p>
789 </td>
790 <td class="gallery_image" valign="top" align="right"></td>
791 </tr></table>
792 </div>
793 """ % (node_id, node_id, node.title, node.title))
795 for s in refsect1s:
796 result.extend(convert_refsect1(ctx, s))
797 result.append("""</div>
798 </body>
799 </html>""")
800 return result
803 # TODO(ensonic): turn into class with converters as functions and ctx as self
804 convert_chunks = {
805 'book': convert_book,
806 'chapter': convert_chapter,
807 'index': convert_index,
808 'refentry': convert_refentry,
812 def generate_nav_nodes(files, node):
813 nav = {
814 'nav_home': node.root,
816 # nav params: up, prev, next
817 if node.parent:
818 nav['nav_up'] = node.parent
819 ix = files.index(node)
820 if ix > 0:
821 nav['nav_prev'] = files[ix - 1]
822 if ix < len(files) - 1:
823 nav['nav_next'] = files[ix + 1]
824 return nav
827 def convert(out_dir, files, node):
828 """Convert the docbook chunks to a html file.
830 Args:
831 out_dir: already created output dir
832 files: list of nodes in the tree in pre-order
833 node: current tree node
836 logging.info('Writing: %s', node.filename)
837 with open(os.path.join(out_dir, node.filename), 'wt') as html:
838 ctx = {
839 'files': files,
840 'node': node,
842 ctx.update(generate_nav_nodes(files, node))
844 if node.name in convert_chunks:
845 for line in convert_chunks[node.name](ctx):
846 html.write(line)
847 else:
848 logging.warning('Add converter/template for "%s"', node.name)
851 def create_devhelp2(out_dir, module, xml):
852 with open(os.path.join(out_dir, module + '.devhelp2'), 'wt') as idx:
853 bookinfo_nodes = xml.xpath('/book/bookinfo')
854 title = ''
855 if bookinfo_nodes is not None:
856 bookinfo = bookinfo_nodes[0]
857 title = bookinfo.xpath('./title/text()')[0]
858 online_url = bookinfo.xpath('./releaseinfo/ulink[@role="online-location"]/@url')[0]
859 # TODO: support author too (see devhelp2.xsl)
860 # TODO: fixxref uses '--src-lang' to set the language
861 result = [
862 """<?xml version="1.0" encoding="utf-8" standalone="no"?>
863 <book xmlns="http://www.devhelp.net/book" title="%s" link="index.html" author="" name="%s" version="2" language="c" online="%s">
864 <chapters>
865 """ % (title, module, online_url)
867 # TODO: toc under 'chapter'
868 result.append(""" </chapters>
869 <functions>
870 """)
871 # TODO: keywords under 'functions' from all refsect2 and refsect3
873 result.append(""" </functions>
874 </book>
875 """)
876 for line in result:
877 idx.write(line)
880 def main(module, index_file):
881 tree = etree.parse(index_file)
882 tree.xinclude()
884 dir_name = os.path.dirname(index_file)
886 # for testing: dump to output file
887 # out_file = os.path.join(dir_name, 'db2html.xml')
888 # tree.write(out_file)
890 # TODO: rename to 'html' later on
891 # - right now in mkhtml, the dir is created by the Makefile and mkhtml
892 # outputs into the working directory
893 out_dir = os.path.join(dir_name, 'db2html')
894 try:
895 os.mkdir(out_dir)
896 except OSError as e:
897 if e.errno != errno.EEXIST:
898 raise
900 # We do multiple passes:
901 # 1) recursively walk the tree and chunk it into a python tree so that we
902 # can generate navigation and link tags.
903 # TODO: also collect all 'id' attributes on the way and build map of
904 # id:rel-link (in fixxref it is called Links[])
905 files = chunk(tree.getroot())
906 # 2) iterate the tree and output files
907 # TODO: use multiprocessing
908 files = list(PreOrderIter(files))
909 for node in files:
910 convert(out_dir, files, node)
911 # 3) create a xxx.devhelp2 file
912 create_devhelp2(out_dir, module, tree.getroot())
915 def run(options):
916 logging.info('options: %s', str(options.__dict__))
917 module = options.args[0]
918 document = options.args[1]
919 sys.exit(main(module, document))