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
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
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 - check each docbook tag if it can contain #PCDATA, if not don't check for
43 xml.text/xml.tail and add a comment (# no PCDATA allowed here)
44 - consider some perf-warnings flag
45 - see 'No "id" attribute on'
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
49 - do we need to find them on the respective tags (inlinegraphic, imageobject)
50 and search them in the path setup by '--path'
53 --path 'Extra source directories' - used to find images
55 - consolidate title handling:
56 - always use the titles-dict
57 - there only store what we have (xml, tag, ...)
58 - when chunking generate 'id's and add entries to titles-dict
59 - add accessors for title and raw_title that lazily get them
63 - we add the chunk label to the title in toc, on the page and in nav tooltips
64 - docbook xsl only sometimes adds the label to the titles and when it does it
65 adds name chunk type too (e.g. 'Part I.' instead of 'I.')
67 - we always add an up-link except on the first page
69 - we're nov omitting the footer
71 - we always add "Table of Contents' before a toc
72 - docbook does that for some pages, it is configurable
75 - minify html: https://pypi.python.org/pypi/htmlmin/
78 sudo pip3 install anytree lxml pygments
82 ../../../gtkdoc-mkhtml2 tester tester-docs.xml
83 xdg-open db2html/index.html
88 rm html-build.stamp; time make html-build.stamp
98 from anytree
import Node
, PreOrderIter
99 from copy
import deepcopy
100 from glob
import glob
101 from lxml
import etree
102 from pygments
import highlight
103 from pygments
.lexers
import CLexer
104 from pygments
.formatters
import HtmlFormatter
105 from timeit
import default_timer
as timer
107 from . import config
, fixxref
110 # lazily constructed lexer cache
114 HTML_FORMATTER
= HtmlFormatter(nowrap
=True)
117 class ChunkParams(object):
118 def __init__(self
, prefix
, parent
=None, min_idx
=0):
121 self
.min_idx
= min_idx
125 DONT_CHUNK
= float('inf')
126 # docbook-xsl defines the chunk tags here.
127 # http://www.sagehill.net/docbookxsl/Chunking.html#GeneratedFilenames
128 # https://github.com/oreillymedia/HTMLBook/blob/master/htmlbook-xsl/chunk.xsl#L33
129 # If not defined, we can just create an example without an 'id' attr and see
132 # For toc levels see http://www.sagehill.net/docbookxsl/TOCcontrol.html
133 # TODO: this list has also a flag that controls wheter we add the
134 # 'Table of Contents' heading in convert_chunk_with_toc()
136 'appendix': ChunkParams('app', 'book'),
137 'book': ChunkParams('bk'),
138 'chapter': ChunkParams('ch', 'book'),
139 'glossary': ChunkParams('go', 'book'),
140 'index': ChunkParams('ix', 'book'),
141 'part': ChunkParams('pt', 'book'),
142 'preface': ChunkParams('pr', 'book'),
143 'refentry': ChunkParams('re', 'book'),
144 'reference': ChunkParams('rn', 'book'),
145 'sect1': ChunkParams('s', 'chapter', 1),
146 'section': ChunkParams('s', 'chapter', 1),
147 'sect2': ChunkParams('s', 'sect1', DONT_CHUNK
),
148 'sect3': ChunkParams('s', 'sect2', DONT_CHUNK
),
149 'sect4': ChunkParams('s', 'sect3', DONT_CHUNK
),
150 'sect5': ChunkParams('s', 'sect4', DONT_CHUNK
),
152 # TAGS we don't support:
153 # 'article', 'bibliography', 'colophon', 'set', 'setindex'
156 '_': (etree
.XPath('./title'), None),
157 'book': (etree
.XPath('./bookinfo/title'), None),
159 etree
.XPath('./refmeta/refentrytitle'),
160 etree
.XPath('./refnamediv/refpurpose')
164 ID_XPATH
= etree
.XPath('//*[@id]')
166 GLOSSENTRY_XPATH
= etree
.XPath('//glossentry')
171 # nested dict with subkeys:
172 # title: textual title
174 # xml: title xml node
178 def encode_entities(text
):
179 return text
.replace('&', '&').replace('<', '<').replace('>', '>')
183 return etree
.tostring(xml
, method
="text", encoding
=str).strip()
186 def gen_chunk_name(node
, chunk_params
):
187 """Generate a chunk file name
189 This is either based on the id or on the position in the doc. In the latter
190 case it uses a prefix from CHUNK_PARAMS and a sequence number for each chunk
193 if 'id' in node
.attrib
:
194 return node
.attrib
['id']
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:
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
)
215 def get_chunk_titles(module
, node
):
217 (title
, subtitle
) = TITLE_XPATHS
.get(tag
, TITLE_XPATHS
['_'])
231 # handle chunk label for tocs
232 label
= node
.attrib
.get('label')
239 # TODO: consider to eval 'title'/'raw_title' lazily
240 result
['title'] = label
+ ''.join(convert_title(ctx
, xml
))
241 result
['raw_title'] = encode_entities(raw_text(xml
))
242 if xml
.tag
!= 'title':
243 result
['title_tag'] = xml
.tag
245 result
['title_tag'] = tag
251 result
['subtitle'] = ''.join(convert_title(ctx
, xml
))
252 result
['subtitle_tag'] = xml
.tag
256 def chunk(xml_node
, module
, depth
=0, idx
=0, parent
=None):
259 The first time, we're called with parent=None and in that case we return
260 the new_node as the root of the tree. For each tree-node we generate a
261 filename and process the children.
264 chunk_params
= CHUNK_PARAMS
.get(tag
)
266 title_args
= get_chunk_titles(module
, xml_node
)
267 chunk_name
= gen_chunk_name(xml_node
, chunk_params
)
269 # check idx to handle 'sect1'/'section' special casing and title-only
271 if idx
>= chunk_params
.min_idx
:
272 logging
.info('chunk tag: "%s"[%d]', tag
, idx
)
274 # remove the xml-node from the parent
275 sub_tree
= etree
.ElementTree(deepcopy(xml_node
)).getroot()
276 xml_node
.getparent().remove(xml_node
)
279 parent
= Node(tag
, parent
=parent
, xml
=xml_node
, depth
=depth
,
281 filename
=chunk_name
+ '.html', anchor
=None,
284 parent
= Node(tag
, parent
=parent
, xml
=xml_node
, depth
=depth
,
286 filename
=parent
.filename
, anchor
='#' + chunk_name
,
291 for child
in xml_node
:
292 chunk(child
, module
, depth
, idx
, parent
)
293 if child
.tag
in CHUNK_PARAMS
:
299 def add_id_links_and_titles(files
, links
):
301 chunk_name
= node
.filename
[:-5]
302 chunk_base
= node
.filename
+ '#'
303 for elem
in ID_XPATH(node
.xml
):
304 attr
= elem
.attrib
['id']
305 if attr
== chunk_name
:
306 links
[attr
] = node
.filename
308 links
[attr
] = chunk_base
+ attr
310 title
= TITLE_XPATHS
.get(elem
.tag
, TITLE_XPATHS
['_'])[0]
314 # TODO: consider to eval 'title' lazily
316 'title': encode_entities(raw_text(xml
)),
322 def build_glossary(files
):
324 if node
.xml
.tag
!= 'glossary':
326 for term
in GLOSSENTRY_XPATH(node
.xml
):
327 # TODO: there can be all kind of things in a glossary. This only supports
328 # what we commonly use, glossterm is mandatory
329 key_node
= term
.find('glossterm')
330 val_node
= term
.find('glossdef')
331 if key_node
is not None and val_node
is not None:
332 glossary
[raw_text(key_node
)] = raw_text(val_node
)
336 debug
.append('missing key')
338 debug
.append('missing val')
339 logging
.warning('Broken glossentry "%s": %s',
340 term
.attrib
['id'], ','.join(debug
))
346 def convert_inner(ctx
, xml
, result
):
348 result
.extend(convert_tags
.get(child
.tag
, convert__unknown
)(ctx
, child
))
351 def convert_ignore(ctx
, xml
):
353 convert_inner(ctx
, xml
, result
)
357 def convert_skip(ctx
, xml
):
361 def append_idref(attrib
, result
):
363 result
.append('<a name="%s"></a>' % attrib
['id'])
366 def append_text(ctx
, text
, result
):
367 if text
and ('no-strip' in ctx
or text
.strip()):
368 result
.append(encode_entities(text
))
374 def convert__unknown(ctx
, xml
):
375 # don't recurse on subchunks
376 if xml
.tag
in CHUNK_PARAMS
:
378 if isinstance(xml
, etree
._Comment
):
379 return ['<!-- ' + xml
.text
+ '-->\n']
382 if xml
.tag
not in missing_tags
:
383 logging
.warning('Add tag converter for "%s"', xml
.tag
)
384 missing_tags
[xml
.tag
] = True
385 result
= ['<!-- ' + xml
.tag
+ '-->\n']
386 convert_inner(ctx
, xml
, result
)
387 result
.append('<!-- /' + xml
.tag
+ '-->\n')
391 def convert_mediaobject_children(ctx
, xml
, result
):
392 # look for textobject/phrase
394 textobject
= xml
.find('textobject')
395 if textobject
is not None:
396 phrase
= textobject
.findtext('phrase')
398 alt_text
= ' alt="%s"' % phrase
400 # look for imageobject/imagedata
401 imageobject
= xml
.find('imageobject')
402 if imageobject
is not None:
403 imagedata
= imageobject
.find('imagedata')
404 if imagedata
is not None:
405 # TODO(ensonic): warn on missing fileref attr?
406 result
.append('<img src="%s"%s>' % (
407 imagedata
.attrib
.get('fileref', ''), alt_text
))
410 def convert_sect(ctx
, xml
, h_tag
, inner_func
=convert_inner
):
411 result
= ['<div class="%s">\n' % xml
.tag
]
412 title_tag
= xml
.find('title')
413 if title_tag
is not None:
414 if 'id' in xml
.attrib
:
415 result
.append('<a name="%s"></a>' % xml
.attrib
['id'])
416 result
.append('<%s>%s</%s>' % (
417 h_tag
, ''.join(convert_title(ctx
, title_tag
)), h_tag
))
418 append_text(ctx
, xml
.text
, result
)
419 inner_func(ctx
, xml
, result
)
420 result
.append('</div>')
421 append_text(ctx
, xml
.tail
, result
)
425 def xml_get_title(ctx
, xml
):
426 title_tag
= xml
.find('title')
427 if title_tag
is not None:
428 return ''.join(convert_title(ctx
, title_tag
))
430 logging
.warning('%s: Expected title tag under "%s %s"', xml
.sourceline
, xml
.tag
, str(xml
.attrib
))
437 def convert_abstract(ctx
, xml
):
438 result
= ["""<div class="abstract">
439 <p class="title"><b>Abstract</b></p>"""]
440 append_text(ctx
, xml
.text
, result
)
441 convert_inner(ctx
, xml
, result
)
442 result
.append('</div>')
443 append_text(ctx
, xml
.tail
, result
)
447 def convert_acronym(ctx
, xml
):
449 title
= glossary
.get(key
, '')
450 # TODO: print a sensible warning if missing
451 result
= ['<acronym title="%s"><span class="acronym">%s</span></acronym>' % (title
, key
)]
453 result
.append(xml
.tail
)
457 def convert_anchor(ctx
, xml
):
458 return ['<a name="%s"></a>' % xml
.attrib
['id']]
461 def convert_bookinfo(ctx
, xml
):
462 result
= ['<div class="titlepage">']
463 convert_inner(ctx
, xml
, result
)
464 result
.append("""<hr>
467 result
.append(xml
.tail
)
471 def convert_blockquote(ctx
, xml
):
472 result
= ['<div class="blockquote">\n<blockquote class="blockquote">']
473 append_text(ctx
, xml
.text
, result
)
474 convert_inner(ctx
, xml
, result
)
475 result
.append('</blockquote>\n</div>')
476 append_text(ctx
, xml
.tail
, result
)
480 def convert_code(ctx
, xml
):
481 result
= ['<code class="%s">' % xml
.tag
]
482 append_text(ctx
, xml
.text
, result
)
483 convert_inner(ctx
, xml
, result
)
484 result
.append('</code>')
485 append_text(ctx
, xml
.tail
, result
)
489 def convert_colspec(ctx
, xml
):
493 result
.append(' class="%s"' % a
['colname'])
495 result
.append(' width="%s"' % a
['colwidth'])
497 # is in tgroup and there can be no 'text'
501 def convert_command(ctx
, xml
):
502 result
= ['<strong class="userinput"><code>']
503 append_text(ctx
, xml
.text
, result
)
504 convert_inner(ctx
, xml
, result
)
505 result
.append('</code></strong>')
506 append_text(ctx
, xml
.tail
, result
)
510 def convert_corpauthor(ctx
, xml
):
511 result
= ['<div><h3 class="corpauthor">\n']
512 append_text(ctx
, xml
.text
, result
)
513 convert_inner(ctx
, xml
, result
)
514 result
.append('</h3></div>\n')
515 append_text(ctx
, xml
.tail
, result
)
519 def convert_div(ctx
, xml
):
520 result
= ['<div class="%s">\n' % xml
.tag
]
521 append_text(ctx
, xml
.text
, result
)
522 convert_inner(ctx
, xml
, result
)
523 result
.append('</div>')
524 append_text(ctx
, xml
.tail
, result
)
528 def convert_emphasis(ctx
, xml
):
529 if 'role' in xml
.attrib
:
530 result
= ['<span class="%s">' % xml
.attrib
['role']]
533 result
= ['<span class="emphasis"><em>']
535 append_text(ctx
, xml
.text
, result
)
536 convert_inner(ctx
, xml
, result
)
538 append_text(ctx
, xml
.tail
, result
)
542 def convert_em(ctx
, xml
):
543 result
= ['<em class="%s">' % xml
.tag
]
544 append_text(ctx
, xml
.text
, result
)
545 convert_inner(ctx
, xml
, result
)
546 result
.append('</em>')
547 append_text(ctx
, xml
.tail
, result
)
551 def convert_em_code(ctx
, xml
):
552 result
= ['<em class="%s"><code>' % xml
.tag
]
553 append_idref(xml
.attrib
, result
)
554 append_text(ctx
, xml
.text
, result
)
555 convert_inner(ctx
, xml
, result
)
556 result
.append('</code></em>')
557 append_text(ctx
, xml
.tail
, result
)
561 def convert_entry(ctx
, xml
):
562 entry_type
= ctx
['table.entry']
563 result
= ['<' + entry_type
]
564 if 'role' in xml
.attrib
:
565 result
.append(' class="%s"' % xml
.attrib
['role'])
566 if 'morerows' in xml
.attrib
:
567 result
.append(' rowspan="%s"' % (1 + int(xml
.attrib
['morerows'])))
569 append_text(ctx
, xml
.text
, result
)
570 convert_inner(ctx
, xml
, result
)
571 result
.append('</' + entry_type
+ '>')
572 append_text(ctx
, xml
.tail
, result
)
576 def convert_footnote(ctx
, xml
):
577 footnotes
= ctx
.get('footnotes', [])
578 # footnotes idx is not per page, but per doc
583 # need a pair of ids for each footnote (docbook generates different ids)
584 this_id
= 'footnote-%d' % idx
585 that_id
= 'ftn.' + this_id
587 inner
= ['<div id="%s" class="footnote">' % that_id
]
588 inner
.append('<p><a href="#%s" class="para"><sup class="para">[%d] </sup></a>' % (
590 # TODO(ensonic): this can contain all kind of tags, if we convert them we'll
591 # get double nested paras :/.
592 # convert_inner(ctx, xml, inner)
593 para
= xml
.find('para')
595 para
= xml
.find('simpara')
597 inner
.append(para
.text
)
599 logging
.warning('%s: Unhandled footnote content: %s', xml
.sourceline
, raw_text(xml
))
600 inner
.append('</p></div>')
601 footnotes
.append(inner
)
602 ctx
['footnotes'] = footnotes
603 return ['<a href="#%s" class="footnote" name="%s"><sup class="footnote">[%s]</sup></a>' % (
604 that_id
, this_id
, idx
)]
607 def convert_formalpara(ctx
, xml
):
609 title_tag
= xml
.find('title')
610 result
= ['<p><b>%s</b>' % ''.join(convert_title(ctx
, title_tag
))]
611 para_tag
= xml
.find('para')
612 append_text(ctx
, para_tag
.text
, result
)
613 convert_inner(ctx
, para_tag
, result
)
614 append_text(ctx
, para_tag
.tail
, result
)
615 result
.append('</p>')
616 append_text(ctx
, xml
.tail
, result
)
620 def convert_glossdef(ctx
, xml
):
621 result
= ['<dd class="glossdef">']
622 convert_inner(ctx
, xml
, result
)
623 result
.append('</dd>\n')
627 def convert_glossdiv(ctx
, xml
):
628 title_tag
= xml
.find('title')
629 title
= title_tag
.text
630 xml
.remove(title_tag
)
632 '<a name="gls%s"></a><h3 class="title">%s</h3>' % (title
, title
)
634 convert_inner(ctx
, xml
, result
)
638 def convert_glossentry(ctx
, xml
):
640 convert_inner(ctx
, xml
, result
)
644 def convert_glossterm(ctx
, xml
):
647 anchor
= xml
.find('anchor')
648 if anchor
is not None:
649 glossid
= anchor
.attrib
.get('id', '')
650 text
+= anchor
.tail
or ''
651 text
+= xml
.text
or ''
653 glossid
= 'glossterm-' + text
655 '<dt><span class="glossterm"><a name="%s"></a>%s</span></dt>' % (
660 def convert_indexdiv(ctx
, xml
):
661 title_tag
= xml
.find('title')
662 title
= title_tag
.text
663 xml
.remove(title_tag
)
665 '<a name="idx%s"></a><h3 class="title">%s</h3>' % (title
, title
)
667 convert_inner(ctx
, xml
, result
)
671 def convert_informaltable(ctx
, xml
):
672 result
= ['<div class="informaltable"><table class="informaltable"']
674 if 'pgwide' in a
and a
['pgwide'] == '1':
675 result
.append(' width="100%"')
676 if 'frame' in a
and a
['frame'] == 'none':
677 result
.append(' border="0"')
679 convert_inner(ctx
, xml
, result
)
680 result
.append('</table></div>')
682 result
.append(xml
.tail
)
686 def convert_inlinegraphic(ctx
, xml
):
687 # TODO(ensonic): warn on missing fileref attr?
688 return ['<img src="%s">' % xml
.attrib
.get('fileref', '')]
691 def convert_inlinemediaobject(ctx
, xml
):
692 result
= ['<span class="inlinemediaobject">']
693 # no PCDATA allowed here
694 convert_mediaobject_children(ctx
, xml
, result
)
695 result
.append('</span>')
696 append_text(ctx
, xml
.tail
, result
)
700 def convert_itemizedlist(ctx
, xml
):
701 result
= ['<div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; ">']
702 convert_inner(ctx
, xml
, result
)
703 result
.append('</ul></div>')
705 result
.append(xml
.tail
)
709 def convert_link(ctx
, xml
):
710 linkend
= xml
.attrib
['linkend']
714 append_text(ctx
, xml
.text
, link_text
)
715 convert_inner(ctx
, xml
, link_text
)
716 text
= ''.join(link_text
)
718 (tid
, href
) = fixxref
.GetXRef(linkend
)
721 title
= titles
.get(tid
)
723 title_attr
= ' title="%s"' % title
['title']
725 href
= fixxref
.MakeRelativeXRef(ctx
['module'], href
)
726 result
= ['<a href="%s"%s>%s</a>' % (href
, title_attr
, text
)]
728 # TODO: filename is for the output and xml.sourceline is on the masterdoc ...
729 fixxref
.ReportBadXRef(ctx
['node'].filename
, 0, linkend
, text
)
732 append_text(ctx
, xml
.text
, result
)
733 convert_inner(ctx
, xml
, result
)
734 append_text(ctx
, xml
.tail
, result
)
738 def convert_listitem(ctx
, xml
):
739 result
= ['<li class="listitem">']
740 convert_inner(ctx
, xml
, result
)
741 result
.append('</li>')
742 # no PCDATA allowed here, is in itemizedlist
746 def convert_literallayout(ctx
, xml
):
747 result
= ['<div class="literallayout"><p><br>\n']
748 append_text(ctx
, xml
.text
, result
)
749 convert_inner(ctx
, xml
, result
)
750 result
.append('</p></div>')
751 append_text(ctx
, xml
.tail
, result
)
755 def convert_mediaobject(ctx
, xml
):
756 result
= ['<div class="mediaobject">\n']
757 # no PCDATA allowed here
758 convert_mediaobject_children(ctx
, xml
, result
)
759 result
.append('</div>')
760 append_text(ctx
, xml
.tail
, result
)
764 def convert_orderedlist(ctx
, xml
):
765 result
= ['<div class="orderedlist"><ol class="orderedlist" type="1">']
766 convert_inner(ctx
, xml
, result
)
767 result
.append('</ol></div>')
768 append_text(ctx
, xml
.tail
, result
)
772 def convert_para(ctx
, xml
):
774 if 'role' in xml
.attrib
:
775 result
.append('<p class="%s">' % xml
.attrib
['role'])
778 if 'id' in xml
.attrib
:
779 result
.append('<a name="%s"></a>' % xml
.attrib
['id'])
780 append_text(ctx
, xml
.text
, result
)
781 convert_inner(ctx
, xml
, result
)
782 result
.append('</p>')
783 append_text(ctx
, xml
.tail
, result
)
787 def convert_para_like(ctx
, xml
):
789 if 'id' in xml
.attrib
:
790 result
.append('<a name="%s"></a>' % xml
.attrib
['id'])
791 result
.append('<p class="%s">' % xml
.tag
)
792 append_text(ctx
, xml
.text
, result
)
793 convert_inner(ctx
, xml
, result
)
794 result
.append('</p>')
795 append_text(ctx
, xml
.tail
, result
)
799 def convert_phrase(ctx
, xml
):
801 if 'role' in xml
.attrib
:
802 result
.append(' class="%s">' % xml
.attrib
['role'])
805 append_text(ctx
, xml
.text
, result
)
806 convert_inner(ctx
, xml
, result
)
807 result
.append('</span>')
808 append_text(ctx
, xml
.tail
, result
)
812 def convert_primaryie(ctx
, xml
):
814 convert_inner(ctx
, xml
, result
)
815 result
.append('\n</dt>\n<dd></dd>\n')
819 def convert_pre(ctx
, xml
):
820 # Since we're inside <pre> don't skip newlines
821 ctx
['no-strip'] = True
822 result
= ['<pre class="%s">' % xml
.tag
]
823 append_text(ctx
, xml
.text
, result
)
824 convert_inner(ctx
, xml
, result
)
825 result
.append('</pre>')
827 append_text(ctx
, xml
.tail
, result
)
831 def convert_programlisting(ctx
, xml
):
833 if xml
.attrib
.get('role', '') == 'example':
835 lang
= xml
.attrib
.get('language', ctx
['src-lang']).lower()
836 if lang
not in LEXERS
:
837 LEXERS
[lang
] = get_lexer_by_name(lang
)
838 lexer
= LEXERS
.get(lang
, None)
840 highlighted
= highlight(xml
.text
, lexer
, HTML_FORMATTER
)
842 # we do own line-numbering
843 line_count
= highlighted
.count('\n')
844 source_lines
= '\n'.join([str(i
) for i
in range(1, line_count
+ 1)])
845 result
.append("""<table class="listing_frame" border="0" cellpadding="0" cellspacing="0">
848 <td class="listing_lines" align="right"><pre>%s</pre></td>
849 <td class="listing_code"><pre class="programlisting">%s</pre></td>
853 """ % (source_lines
, highlighted
))
855 logging
.warn('No pygments lexer for language="%s"', lang
)
856 result
.append('<pre class="programlisting">')
857 result
.append(xml
.text
)
858 result
.append('</pre>')
860 result
.append('<pre class="programlisting">')
861 append_text(ctx
, xml
.text
, result
)
862 convert_inner(ctx
, xml
, result
)
863 result
.append('</pre>')
864 append_text(ctx
, xml
.tail
, result
)
868 def convert_quote(ctx
, xml
):
869 result
= ['<span class="quote">"<span class="quote">']
870 append_text(ctx
, xml
.text
, result
)
871 convert_inner(ctx
, xml
, result
)
872 result
.append('</span>"</span>')
873 append_text(ctx
, xml
.tail
, result
)
877 def convert_refsect1(ctx
, xml
):
878 # Add a divider between two consequitive refsect2
879 def convert_inner(ctx
, xml
, result
):
882 if child
.tag
== 'refsect2' and prev
is not None and prev
.tag
== child
.tag
:
883 result
.append('<hr>\n')
884 result
.extend(convert_tags
.get(child
.tag
, convert__unknown
)(ctx
, child
))
886 return convert_sect(ctx
, xml
, 'h2', convert_inner
)
889 def convert_refsect2(ctx
, xml
):
890 return convert_sect(ctx
, xml
, 'h3')
893 def convert_refsect3(ctx
, xml
):
894 return convert_sect(ctx
, xml
, 'h4')
897 def convert_row(ctx
, xml
):
899 convert_inner(ctx
, xml
, result
)
900 result
.append('</tr>\n')
904 def convert_sbr(ctx
, xml
):
908 def convert_sect1_tag(ctx
, xml
):
909 return convert_sect(ctx
, xml
, 'h2')
912 def convert_sect2(ctx
, xml
):
913 return convert_sect(ctx
, xml
, 'h3')
916 def convert_sect3(ctx
, xml
):
917 return convert_sect(ctx
, xml
, 'h4')
920 def convert_simpara(ctx
, xml
):
922 append_text(ctx
, xml
.text
, result
)
923 convert_inner(ctx
, xml
, result
)
924 result
.append('</p>')
925 append_text(ctx
, xml
.tail
, result
)
929 def convert_span(ctx
, xml
):
930 result
= ['<span class="%s">' % xml
.tag
]
931 append_text(ctx
, xml
.text
, result
)
932 convert_inner(ctx
, xml
, result
)
933 result
.append('</span>')
934 append_text(ctx
, xml
.tail
, result
)
938 def convert_table(ctx
, xml
):
939 result
= ['<div class="table">']
940 append_idref(xml
.attrib
, result
)
941 title_tag
= xml
.find('title')
942 if title_tag
is not None:
943 result
.append('<p class="title"><b>')
944 # TODO(ensonic): Add a 'Table X. ' prefix, needs a table counter
945 result
.extend(convert_title(ctx
, title_tag
))
946 result
.append('</b></p>')
947 result
.append('<div class="table-contents"><table class="table" summary="g_object_new" border="1">')
949 convert_inner(ctx
, xml
, result
)
951 result
.append('</table></div></div>')
952 append_text(ctx
, xml
.tail
, result
)
956 def convert_tbody(ctx
, xml
):
958 ctx
['table.entry'] = 'td'
959 convert_inner(ctx
, xml
, result
)
960 result
.append('</tbody>')
961 # is in tgroup and there can be no 'text'
965 def convert_tgroup(ctx
, xml
):
966 # tgroup does not expand to anything, but the nested colspecs need to
967 # be put into a colgroup
968 cols
= xml
.findall('colspec')
971 result
.append('<colgroup>\n')
973 result
.extend(convert_colspec(ctx
, col
))
975 result
.append('</colgroup>\n')
976 convert_inner(ctx
, xml
, result
)
977 # is in informaltable and there can be no 'text'
981 def convert_thead(ctx
, xml
):
983 ctx
['table.entry'] = 'th'
984 convert_inner(ctx
, xml
, result
)
985 result
.append('</thead>')
986 # is in tgroup and there can be no 'text'
990 def convert_title(ctx
, xml
):
991 # This is always explicitly called from some context
993 append_text(ctx
, xml
.text
, result
)
994 convert_inner(ctx
, xml
, result
)
995 append_text(ctx
, xml
.tail
, result
)
999 def convert_ulink(ctx
, xml
):
1001 result
= ['<a class="%s" href="%s">%s</a>' % (xml
.tag
, xml
.attrib
['url'], xml
.text
)]
1003 url
= xml
.attrib
['url']
1004 result
= ['<a class="%s" href="%s">%s</a>' % (xml
.tag
, url
, url
)]
1005 append_text(ctx
, xml
.tail
, result
)
1009 def convert_userinput(ctx
, xml
):
1010 result
= ['<span class="command"><strong>']
1011 append_text(ctx
, xml
.text
, result
)
1012 convert_inner(ctx
, xml
, result
)
1013 result
.append('</strong></span>')
1014 append_text(ctx
, xml
.tail
, result
)
1018 def convert_variablelist(ctx
, xml
):
1019 result
= ["""<div class="variablelist"><table border="0" class="variablelist">
1021 <col align="left" valign="top">
1025 convert_inner(ctx
, xml
, result
)
1026 result
.append("""</tbody>
1031 def convert_varlistentry(ctx
, xml
):
1034 result
.append('<td><p>')
1035 term
= xml
.find('term')
1036 result
.extend(convert_span(ctx
, term
))
1037 result
.append('</p></td>')
1039 result
.append('<td>')
1040 listitem
= xml
.find('listitem')
1041 convert_inner(ctx
, listitem
, result
)
1042 result
.append('</td>')
1044 result
.append('<tr>')
1048 def convert_xref(ctx
, xml
):
1049 linkend
= xml
.attrib
['linkend']
1050 (tid
, href
) = fixxref
.GetXRef(linkend
)
1051 title
= titles
.get(tid
)
1052 # all sectN need to become 'section
1062 '<a class="xref" href="%s" title="%s">the %s called “%s”</a>' %
1063 (href
, title
['title'], tag
, ''.join(convert_title(ctx
, title
['xml'])))
1066 append_text(ctx
, xml
.tail
, result
)
1070 # TODO(ensonic): turn into class with converters as functions and ctx as self
1072 'abstract': convert_abstract
,
1073 'acronym': convert_acronym
,
1074 'anchor': convert_anchor
,
1075 'application': convert_span
,
1076 'bookinfo': convert_bookinfo
,
1077 'blockquote': convert_blockquote
,
1078 'classname': convert_code
,
1079 'caption': convert_div
,
1080 'code': convert_code
,
1081 'colspec': convert_colspec
,
1082 'constant': convert_code
,
1083 'command': convert_command
,
1084 'corpauthor': convert_corpauthor
,
1085 'emphasis': convert_emphasis
,
1086 'entry': convert_entry
,
1087 'envar': convert_code
,
1088 'footnote': convert_footnote
,
1089 'filename': convert_code
,
1090 'firstterm': convert_em
,
1091 'formalpara': convert_formalpara
,
1092 'function': convert_code
,
1093 'glossdef': convert_glossdef
,
1094 'glossdiv': convert_glossdiv
,
1095 'glossentry': convert_glossentry
,
1096 'glossterm': convert_glossterm
,
1097 'indexdiv': convert_indexdiv
,
1098 'indexentry': convert_ignore
,
1099 'indexterm': convert_skip
,
1100 'informalexample': convert_div
,
1101 'informaltable': convert_informaltable
,
1102 'inlinegraphic': convert_inlinegraphic
,
1103 'inlinemediaobject': convert_inlinemediaobject
,
1104 'interfacename': convert_code
,
1105 'itemizedlist': convert_itemizedlist
,
1106 'legalnotice': convert_div
,
1107 'link': convert_link
,
1108 'listitem': convert_listitem
,
1109 'literal': convert_code
,
1110 'literallayout': convert_literallayout
,
1111 'mediaobject': convert_mediaobject
,
1112 'note': convert_div
,
1113 'option': convert_code
,
1114 'orderedlist': convert_orderedlist
,
1115 'para': convert_para
,
1116 'partintro': convert_div
,
1117 'parameter': convert_em_code
,
1118 'phrase': convert_phrase
,
1119 'primaryie': convert_primaryie
,
1120 'programlisting': convert_programlisting
,
1121 'quote': convert_quote
,
1122 'releaseinfo': convert_para_like
,
1123 'refsect1': convert_refsect1
,
1124 'refsect2': convert_refsect2
,
1125 'refsect3': convert_refsect3
,
1126 'replaceable': convert_em_code
,
1127 'returnvalue': convert_span
,
1130 'screen': convert_pre
,
1131 'section': convert_sect2
, # FIXME: need tracking of nesting
1132 'sect1': convert_sect1_tag
,
1133 'sect2': convert_sect2
,
1134 'sect3': convert_sect3
,
1135 'simpara': convert_simpara
,
1136 'simplesect': convert_sect2
, # FIXME: need tracking of nesting
1137 'structfield': convert_em_code
,
1138 'structname': convert_span
,
1139 'synopsis': convert_pre
,
1140 'symbol': convert_span
,
1141 'table': convert_table
,
1142 'tbody': convert_tbody
,
1143 'term': convert_span
,
1144 'tgroup': convert_tgroup
,
1145 'thead': convert_thead
,
1146 'title': convert_skip
,
1147 'type': convert_span
,
1148 'ulink': convert_ulink
,
1149 'userinput': convert_userinput
,
1150 'varname': convert_code
,
1151 'variablelist': convert_variablelist
,
1152 'varlistentry': convert_varlistentry
,
1153 'warning': convert_div
,
1154 'xref': convert_xref
,
1157 # conversion helpers
1159 HTML_HEADER
= """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
1162 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1164 %s<link rel="stylesheet" href="style.css" type="text/css">
1166 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
1170 def generate_head_links(ctx
):
1173 '<link rel="home" href="%s" title="%s">\n' % (n
.filename
, n
.raw_title
)
1177 result
.append('<link rel="up" href="%s" title="%s">\n' % (n
.filename
, n
.raw_title
))
1178 if 'nav_prev' in ctx
:
1180 result
.append('<link rel="prev" href="%s" title="%s">\n' % (n
.filename
, n
.raw_title
))
1181 if 'nav_next' in ctx
:
1183 result
.append('<link rel="next" href="%s" title="%s">\n' % (n
.filename
, n
.raw_title
))
1184 return ''.join(result
)
1187 def generate_nav_links(ctx
):
1190 '<td><a accesskey="h" href="%s"><img src="home.png" width="16" height="16" border="0" alt="Home"></a></td>' % n
.filename
1195 '<td><a accesskey="u" href="%s"><img src="up.png" width="16" height="16" border="0" alt="Up"></a></td>' % n
.filename
)
1197 result
.append('<td><img src="up-insensitive.png" width="16" height="16" border="0"></td>')
1198 if 'nav_prev' in ctx
:
1201 '<td><a accesskey="p" href="%s"><img src="left.png" width="16" height="16" border="0" alt="Prev"></a></td>' % n
.filename
)
1203 result
.append('<td><img src="left-insensitive.png" width="16" height="16" border="0"></td>')
1204 if 'nav_next' in ctx
:
1207 '<td><a accesskey="n" href="%s"><img src="right.png" width="16" height="16" border="0" alt="Next"></a></td>' % n
.filename
)
1209 result
.append('<td><img src="right-insensitive.png" width="16" height="16" border="0"></td>')
1211 return ''.join(result
)
1214 def generate_toc(ctx
, node
):
1216 for c
in node
.children
:
1217 # TODO: urlencode the filename: urllib.parse.quote_plus()
1221 result
.append('<dt><span class="%s"><a href="%s">%s</a></span>\n' % (
1222 c
.title_tag
, link
, c
.title
))
1224 result
.append('<span class="%s"> — %s</span>' % (c
.subtitle_tag
, c
.subtitle
))
1225 result
.append('</dt>\n')
1227 result
.append('<dd><dl>')
1228 result
.extend(generate_toc(ctx
, c
))
1229 result
.append('</dl></dd>')
1233 def generate_basic_nav(ctx
):
1234 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1235 <tr valign="middle">
1236 <td width="100%%" align="left" class="shortcuts"></td>
1240 """ % generate_nav_links(ctx
)
1243 def generate_alpha_nav(ctx
, divs
, prefix
, span_id
):
1246 title
= xml_get_title(ctx
, s
)
1247 ix_nav
.append('<a class="shortcut" href="#%s%s">%s</a>' % (prefix
, title
, title
))
1249 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1250 <tr valign="middle">
1251 <td width="100%%" align="left" class="shortcuts">
1259 """ % (span_id
, '\n<span class="dim">|</span>\n'.join(ix_nav
), generate_nav_links(ctx
))
1262 def generate_refentry_nav(ctx
, refsect1s
, result
):
1263 result
.append("""<table class="navigation" id="top" width="100%" cellpadding="2" cellspacing="5">
1264 <tr valign="middle">
1265 <td width="100%" align="left" class="shortcuts">
1266 <a href="#" class="shortcut">Top</a>""")
1269 # don't list TOC sections (role="xxx_proto")
1270 if s
.attrib
.get('role', '').endswith("_proto"):
1272 # skip section without 'id' attrs
1273 if 'id' not in s
.attrib
:
1276 ref_id
= s
.attrib
['id']
1277 # skip foreign sections
1278 if '.' not in ref_id
:
1281 title
= xml_get_title(ctx
, s
)
1282 span_id
= ref_id
.split('.')[1].replace('-', '_')
1286 <span class="dim">|</span>
1287 <a href="#%s" class="shortcut">%s</a>
1289 """ % (span_id
, ref_id
, title
))
1295 """ % generate_nav_links(ctx
))
1298 def generate_footer(ctx
):
1300 if 'footnotes' in ctx
:
1301 result
.append("""<div class="footnotes">\n
1302 <br><hr style="width:100; text-align:left;margin-left: 0">
1304 for f
in ctx
['footnotes']:
1306 result
.append('</div>\n')
1310 def get_id_path(node
):
1311 """ Generate the 'id'.
1312 We need to walk up the xml-tree and check the positions for each sibling.
1313 When reaching the top of the tree we collect remaining index entries from
1318 parent
= xml
.getparent()
1319 while parent
is not None:
1320 children
= parent
.getchildren()
1321 ix
.insert(0, str(children
.index(xml
) + 1))
1323 parent
= xml
.getparent()
1324 while node
is not None:
1325 ix
.insert(0, str(node
.idx
+ 1))
1333 node_id
= xml
.attrib
.get('id', None)
1337 # TODO: this is moot if nothing links to it, we could also consider to omit
1338 # the <a name="$id"></a> tag.
1339 logging
.info('%d: No "id" attribute on "%s", generating one',
1340 xml
.sourceline
, xml
.tag
)
1341 ix
= get_id_path(node
)
1342 # logging.warning('%s: id indexes: %s', node.filename, str(ix))
1343 return 'id-' + '.'.join(ix
)
1346 def convert_chunk_with_toc(ctx
, div_class
, title_tag
):
1349 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1350 generate_basic_nav(ctx
),
1351 '<div class="%s">' % div_class
,
1355 <div class="titlepage">
1356 <%s class="title"><a name="%s"></a>%s</%s>
1358 title_tag
, get_id(node
), node
.title
, title_tag
))
1360 toc
= generate_toc(ctx
, node
)
1362 # TODO: not all docbook page types use this extra heading
1363 result
.append("""<p><b>Table of Contents</b></p>
1368 result
.append("""</dl>
1371 convert_inner(ctx
, node
.xml
, result
)
1372 result
.extend(generate_footer(ctx
))
1373 result
.append("""</div>
1382 def convert_book(ctx
):
1385 HTML_HEADER
% (node
.title
, generate_head_links(ctx
)),
1386 """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="0">
1387 <tr><th valign="middle"><p class="title">%s</p></th></tr>
1392 bookinfo
= node
.xml
.findall('bookinfo')[0]
1393 result
.extend(convert_bookinfo(ctx
, bookinfo
))
1394 result
.append("""<div class="toc">
1397 result
.extend(generate_toc(ctx
, node
.root
))
1398 result
.append("""</dl>
1401 result
.extend(generate_footer(ctx
))
1402 result
.append("""</div>
1408 def convert_chapter(ctx
):
1409 return convert_chunk_with_toc(ctx
, 'chapter', 'h2')
1412 def convert_glossary(ctx
):
1414 glossdivs
= node
.xml
.findall('glossdiv')
1417 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1418 generate_alpha_nav(ctx
, glossdivs
, 'gls', 'glossary'),
1419 """<div class="glossary">
1420 <div class="titlepage"><h%1d class="title">
1421 <a name="%s"></a>%s</h%1d>
1422 </div>""" % (node
.depth
, get_id(node
), node
.title
, node
.depth
)
1425 result
.extend(convert_glossdiv(ctx
, i
))
1426 result
.extend(generate_footer(ctx
))
1427 result
.append("""</div>
1433 def convert_index(ctx
):
1435 # Get all indexdivs under indexdiv
1436 indexdivs
= node
.xml
.find('indexdiv').findall('indexdiv')
1439 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1440 generate_alpha_nav(ctx
, indexdivs
, 'idx', 'index'),
1441 """<div class="index">
1442 <div class="titlepage"><h%1d class="title">
1443 <a name="%s"></a>%s</h%1d>
1444 </div>""" % (node
.depth
, get_id(node
), node
.title
, node
.depth
)
1447 result
.extend(convert_indexdiv(ctx
, i
))
1448 result
.extend(generate_footer(ctx
))
1449 result
.append("""</div>
1455 def convert_part(ctx
):
1456 return convert_chunk_with_toc(ctx
, 'part', 'h1')
1459 def convert_preface(ctx
):
1462 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1463 generate_basic_nav(ctx
),
1464 '<div class="preface">'
1468 <div class="titlepage">
1469 <h2 class="title"><a name="%s"></a>%s</h2>
1470 </div>""" % (get_id(node
), node
.title
))
1471 convert_inner(ctx
, node
.xml
, result
)
1472 result
.extend(generate_footer(ctx
))
1473 result
.append("""</div>
1479 def convert_reference(ctx
):
1480 return convert_chunk_with_toc(ctx
, 'reference', 'h1')
1483 def convert_refentry(ctx
):
1485 node_id
= get_id(node
)
1486 refsect1s
= node
.xml
.findall('refsect1')
1489 refmeta
= node
.xml
.find('refmeta')
1490 if refmeta
is not None:
1491 refmiscinfo
= refmeta
.find('refmiscinfo')
1492 if refmiscinfo
is not None:
1493 inlinegraphic
= refmiscinfo
.find('inlinegraphic')
1494 if inlinegraphic
is not None:
1495 gallery
= ''.join(convert_inlinegraphic(ctx
, inlinegraphic
))
1498 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
))
1500 generate_refentry_nav(ctx
, refsect1s
, result
)
1502 <div class="refentry">
1504 <div class="refnamediv">
1505 <table width="100%%"><tr>
1507 <h2><span class="refentrytitle"><a name="%s.top_of_page"></a>%s</span></h2>
1510 <td class="gallery_image" valign="top" align="right">%s</td>
1513 """ % (node_id
, node_id
, node
.title
, node
.title
, node
.subtitle
, gallery
))
1516 result
.extend(convert_refsect1(ctx
, s
))
1517 result
.extend(generate_footer(ctx
))
1518 result
.append("""</div>
1524 def convert_section(ctx
):
1525 return convert_chunk_with_toc(ctx
, 'section', 'h2')
1528 def convert_sect1(ctx
):
1529 return convert_chunk_with_toc(ctx
, 'sect1', 'h2')
1532 # TODO(ensonic): turn into class with converters as functions and ctx as self
1534 'book': convert_book
,
1535 'chapter': convert_chapter
,
1536 'glossary': convert_glossary
,
1537 'index': convert_index
,
1538 'part': convert_part
,
1539 'preface': convert_preface
,
1540 'reference': convert_reference
,
1541 'refentry': convert_refentry
,
1542 'section': convert_section
,
1543 'sect1': convert_sect1
,
1547 def generate_nav_nodes(files
, node
):
1549 'nav_home': node
.root
,
1551 # nav params: up, prev, next
1553 nav
['nav_up'] = node
.parent
1554 ix
= files
.index(node
)
1556 nav
['nav_prev'] = files
[ix
- 1]
1557 if ix
< len(files
) - 1:
1558 nav
['nav_next'] = files
[ix
+ 1]
1562 def convert(out_dir
, module
, files
, node
, src_lang
):
1563 """Convert the docbook chunks to a html file.
1566 out_dir: already created output dir
1567 files: list of nodes in the tree in pre-order
1568 node: current tree node
1571 logging
.info('Writing: %s', node
.filename
)
1572 with
open(os
.path
.join(out_dir
, node
.filename
), 'wt',
1573 newline
='\n', encoding
='utf-8') as html
:
1578 'src-lang': src_lang
,
1580 ctx
.update(generate_nav_nodes(files
, node
))
1582 if node
.name
in convert_chunks
:
1583 for line
in convert_chunks
[node
.name
](ctx
):
1586 logging
.warning('Add converter/template for "%s"', node
.name
)
1589 def create_devhelp2_toc(node
):
1591 for c
in node
.children
:
1593 result
.append('<sub name="%s" link="%s">\n' % (c
.raw_title
, c
.filename
))
1594 result
.extend(create_devhelp2_toc(c
))
1595 result
.append('</sub>\n')
1597 result
.append('<sub name="%s" link="%s"/>\n' % (c
.raw_title
, c
.filename
))
1601 def create_devhelp2_condition_attribs(node
):
1602 if 'condition' in node
.attrib
:
1603 # condition -> since, deprecated, ... (separated with '|')
1604 cond
= node
.attrib
['condition'].replace('"', '"').split('|')
1608 keywords
.append('{}="{}"'.format(*c
.split(':', 1)))
1610 # deprecated can have no description
1611 keywords
.append('{}="{}"'.format(c
, ''))
1612 return ' ' + ' '.join(keywords
)
1617 def create_devhelp2_refsect2_keyword(node
, base_link
):
1618 node_id
= node
.attrib
['id']
1619 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1620 node
.attrib
['role'], titles
[node_id
]['title'], base_link
+ node_id
,
1621 create_devhelp2_condition_attribs(node
))
1624 def create_devhelp2_refsect3_keyword(node
, base_link
, title
, name
):
1625 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1626 node
.attrib
['role'], title
, base_link
+ name
,
1627 create_devhelp2_condition_attribs(node
))
1630 def create_devhelp2(out_dir
, module
, xml
, files
):
1631 with
open(os
.path
.join(out_dir
, module
+ '.devhelp2'), 'wt',
1632 newline
='\n', encoding
='utf-8') as idx
:
1633 bookinfo_nodes
= xml
.xpath('/book/bookinfo')
1635 if bookinfo_nodes
is not None:
1636 bookinfo
= bookinfo_nodes
[0]
1637 title
= bookinfo
.xpath('./title/text()')[0]
1638 online_url
= bookinfo
.xpath('./releaseinfo/ulink[@role="online-location"]/@url')[0]
1639 # TODO: support author too (see devhelp2.xsl)
1640 # TODO: fixxref uses '--src-lang' to set the language
1642 """<?xml version="1.0" encoding="utf-8" standalone="no"?>
1643 <book xmlns="http://www.devhelp.net/book" title="%s" link="index.html" author="" name="%s" version="2" language="c" online="%s">
1645 """ % (title
, module
, online_url
)
1648 result
.extend(create_devhelp2_toc(files
[0].root
))
1649 result
.append(""" </chapters>
1652 # keywords from all refsect2 and refsect3
1653 refsect2
= etree
.XPath('//refsect2[@role]')
1654 refsect3_enum
= etree
.XPath('refsect3[@role="enum_members"]/informaltable/tgroup/tbody/row[@role="constant"]')
1655 refsect3_enum_details
= etree
.XPath('entry[@role="enum_member_name"]/para')
1656 refsect3_struct
= etree
.XPath('refsect3[@role="struct_members"]/informaltable/tgroup/tbody/row[@role="member"]')
1657 refsect3_struct_details
= etree
.XPath('entry[@role="struct_member_name"]/para/structfield')
1659 base_link
= node
.filename
+ '#'
1660 refsect2_nodes
= refsect2(node
.xml
)
1661 for refsect2_node
in refsect2_nodes
:
1662 result
.append(create_devhelp2_refsect2_keyword(refsect2_node
, base_link
))
1663 refsect3_nodes
= refsect3_enum(refsect2_node
)
1664 for refsect3_node
in refsect3_nodes
:
1665 details_node
= refsect3_enum_details(refsect3_node
)[0]
1666 name
= details_node
.attrib
['id']
1667 result
.append(create_devhelp2_refsect3_keyword(refsect3_node
, base_link
, details_node
.text
, name
))
1668 refsect3_nodes
= refsect3_struct(refsect2_node
)
1669 for refsect3_node
in refsect3_nodes
:
1670 details_node
= refsect3_struct_details(refsect3_node
)[0]
1671 name
= details_node
.attrib
['id']
1672 result
.append(create_devhelp2_refsect3_keyword(refsect3_node
, base_link
, name
, name
))
1674 result
.append(""" </functions>
1681 def get_dirs(uninstalled
):
1683 # this does not work from buiddir!=srcdir
1684 gtkdocdir
= os
.path
.split(sys
.argv
[0])[0]
1685 if not os
.path
.exists(gtkdocdir
+ '/gtk-doc.xsl'):
1686 # try 'srcdir' (set from makefiles) too
1687 if os
.path
.exists(os
.environ
.get("ABS_TOP_SRCDIR", '') + '/gtk-doc.xsl'):
1688 gtkdocdir
= os
.environ
['ABS_TOP_SRCDIR']
1689 styledir
= gtkdocdir
+ '/style'
1691 gtkdocdir
= os
.path
.join(config
.datadir
, 'gtk-doc/data')
1692 styledir
= gtkdocdir
1693 return (gtkdocdir
, styledir
)
1696 def main(module
, index_file
, out_dir
, uninstalled
, src_lang
):
1698 # == Loading phase ==
1699 # the next 3 steps could be done in paralel
1701 # 1) load the docuemnt
1703 # does not seem to be faster
1704 # parser = etree.XMLParser(collect_ids=False)
1705 # tree = etree.parse(index_file, parser)
1706 tree
= etree
.parse(index_file
)
1708 logging
.warning("1: %7.3lf: load doc", timer() - _t
)
1712 # TODO: handle additional images
1713 (gtkdocdir
, styledir
) = get_dirs(uninstalled
)
1714 # copy navigation images and stylesheets to html directory ...
1715 css_file
= os
.path
.join(styledir
, 'style.css')
1716 for f
in glob(os
.path
.join(styledir
, '*.png')) + [css_file
]:
1717 shutil
.copy(f
, out_dir
)
1718 css_file
= os
.path
.join(out_dir
, 'style.css')
1719 with
open(css_file
, 'at', newline
='\n', encoding
='utf-8') as css
:
1720 css
.write(HTML_FORMATTER
.get_style_defs())
1721 logging
.warning("2: %7.3lf: copy datafiles", timer() - _t
)
1723 # 3) load xref targets
1725 # TODO: migrate options from fixxref
1726 # TODO: ideally explicity specify the files we need, this will save us the
1727 # globbing and we'll load less files.
1728 fixxref
.LoadIndicies(out_dir
, '/usr/share/gtk-doc/html', [])
1729 logging
.warning("3: %7.3lf: load xrefs", timer() - _t
)
1731 # == Processing phase ==
1733 # 4) recursively walk the tree and chunk it into a python tree so that we
1734 # can generate navigation and link tags.
1736 files
= chunk(tree
.getroot(), module
)
1737 files
= [f
for f
in PreOrderIter(files
) if f
.anchor
is None]
1738 logging
.warning("4: %7.3lf: chunk doc", timer() - _t
)
1740 # 5) extract tables:
1742 # TODO: can be done in parallel
1743 # - find all 'id' attribs and add them to the link map
1744 # - .. get their titles and store them into the titles map
1745 add_id_links_and_titles(files
, fixxref
.Links
)
1746 # - build glossary dict
1747 build_glossary(files
)
1748 logging
.warning("5: %7.3lf: extract tables", timer() - _t
)
1750 # == Output phase ==
1751 # the next two step could be done in parllel
1753 # 6) create a xxx.devhelp2 file
1755 create_devhelp2(out_dir
, module
, tree
.getroot(), files
)
1756 logging
.warning("6: %7.3lf: create devhelp2", timer() - _t
)
1758 # 7) iterate the tree and output files
1760 # TODO: can be done in parallel, figure out why this is not faster
1761 # from multiprocessing.pool import Pool
1762 # with Pool(4) as p:
1763 # p.apply_async(convert, args=(out_dir, module, files))
1764 # from multiprocessing.pool import ThreadPool
1765 # with ThreadPool(4) as p:
1766 # p.apply_async(convert, args=(out_dir, module, files))
1768 convert(out_dir
, module
, files
, node
, src_lang
)
1769 logging
.warning("7: %7.3lf: create html", timer() - _t
)
1773 logging
.info('options: %s', str(options
.__dict
__))
1774 module
= options
.args
[0]
1775 document
= options
.args
[1]
1777 # TODO: rename to 'html' later on
1778 # - right now in mkhtml, the dir is created by the Makefile and mkhtml
1779 # outputs into the working directory
1780 out_dir
= os
.path
.join(os
.path
.dirname(document
), 'db2html')
1783 except OSError as e
:
1784 if e
.errno
!= errno
.EEXIST
:
1787 sys
.exit(main(module
, document
, out_dir
, options
.uninstalled
, options
.src_lang
))