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 - inside 'glossentry' we're only handling 'glossterm' and 'glossdef'
43 - convert_{figure,table} need counters.
44 - check each docbook tag if it can contain #PCDATA, if not don't check for
45 xml.text/xml.tail and add a comment (# no PCDATA allowed here)
46 - consider some perf-warnings flag
47 - see 'No "id" attribute on'
48 - find a better way to print context for warnings
49 - we use 'xml.sourceline', but this all does not help a lot due to xi:include
50 - consolidate title handling:
51 - always use the titles-dict
52 - there only store what we have (xml, tag, ...)
53 - when chunking generate 'id's and add entries to titles-dict
54 - add accessors for title and raw_title that lazily get them
58 - we add the chunk label to the title in toc, on the page and in nav tooltips
59 - docbook xsl only sometimes adds the label to the titles and when it does it
60 adds name chunk type too (e.g. 'Part I.' instead of 'I.')
62 - we always add an up-link except on the first page
64 - we're nov omitting the footer
66 - we always add "Table of Contents' before a toc
67 - docbook does that for some pages, it is configurable
70 - minify html: https://pypi.python.org/pypi/htmlmin/
73 sudo pip3 install anytree lxml pygments
77 ../../../gtkdoc-mkhtml2 tester tester-docs.xml
78 xdg-open db2html/index.html
83 rm html-build.stamp; time make html-build.stamp
93 from anytree
import Node
, PreOrderIter
94 from copy
import deepcopy
96 from lxml
import etree
97 from pygments
import highlight
98 from pygments
.lexers
import CLexer
99 from pygments
.formatters
import HtmlFormatter
100 from timeit
import default_timer
as timer
102 from . import config
, fixxref
105 # lazily constructed lexer cache
109 HTML_FORMATTER
= HtmlFormatter(nowrap
=True)
112 class ChunkParams(object):
113 def __init__(self
, prefix
, parent
=None, min_idx
=0):
116 self
.min_idx
= min_idx
120 DONT_CHUNK
= float('inf')
121 # docbook-xsl defines the chunk tags here.
122 # http://www.sagehill.net/docbookxsl/Chunking.html#GeneratedFilenames
123 # https://github.com/oreillymedia/HTMLBook/blob/master/htmlbook-xsl/chunk.xsl#L33
124 # If not defined, we can just create an example without an 'id' attr and see
127 # For toc levels see http://www.sagehill.net/docbookxsl/TOCcontrol.html
128 # TODO: this list has also a flag that controls wheter we add the
129 # 'Table of Contents' heading in convert_chunk_with_toc()
131 'appendix': ChunkParams('app', 'book'),
132 'book': ChunkParams('bk'),
133 'chapter': ChunkParams('ch', 'book'),
134 'glossary': ChunkParams('go', 'book'),
135 'index': ChunkParams('ix', 'book'),
136 'part': ChunkParams('pt', 'book'),
137 'preface': ChunkParams('pr', 'book'),
138 'refentry': ChunkParams('re', 'book'),
139 'reference': ChunkParams('rn', 'book'),
140 'sect1': ChunkParams('s', 'chapter', 1),
141 'section': ChunkParams('s', 'chapter', 1),
142 'sect2': ChunkParams('s', 'sect1', DONT_CHUNK
),
143 'sect3': ChunkParams('s', 'sect2', DONT_CHUNK
),
144 'sect4': ChunkParams('s', 'sect3', DONT_CHUNK
),
145 'sect5': ChunkParams('s', 'sect4', DONT_CHUNK
),
147 # TAGS we don't support:
148 # 'article', 'bibliography', 'colophon', 'set', 'setindex'
151 '_': (etree
.XPath('./title'), None),
152 'book': (etree
.XPath('./bookinfo/title'), None),
154 etree
.XPath('./refmeta/refentrytitle'),
155 etree
.XPath('./refnamediv/refpurpose')
159 ID_XPATH
= etree
.XPath('//*[@id]')
161 GLOSSENTRY_XPATH
= etree
.XPath('//glossentry')
166 # nested dict with subkeys:
167 # title: textual title
169 # xml: title xml node
176 def encode_entities(text
):
177 return text
.replace('&', '&').replace('<', '<').replace('>', '>')
181 return etree
.tostring(xml
, method
="text", encoding
=str).strip()
184 def gen_chunk_name(node
, chunk_params
):
185 """Generate a chunk file name
187 This is either based on the id or on the position in the doc. In the latter
188 case it uses a prefix from CHUNK_PARAMS and a sequence number for each chunk
191 idval
= node
.attrib
.get('id')
192 if idval
is not None:
195 name
= ('%s%02d' % (chunk_params
.prefix
, chunk_params
.idx
))
196 chunk_params
.idx
+= 1
198 # handle parents to make names of nested tags like in docbook
199 # - we only need to prepend the parent if there are > 1 of them in the
200 # xml. None, the parents we have are not sufficient, e.g. 'index' can
201 # be in 'book' or 'part' or ... Maybe we can track the chunk_parents
202 # when we chunk explicitly and on each level maintain the 'idx'
203 # while chunk_params.parent:
204 # parent = chunk_params.parent
205 # if parent not in CHUNK_PARAMS:
207 # chunk_params = CHUNK_PARAMS[parent]
208 # name = ('%s%02d' % (chunk_params.prefix, chunk_params.idx)) + name
210 logging
.info('Gen chunk name: "%s"', name
)
214 def get_chunk_titles(module
, node
):
216 (title
, subtitle
) = TITLE_XPATHS
.get(tag
, TITLE_XPATHS
['_'])
230 # handle chunk label for tocs
231 label
= node
.attrib
.get('label')
238 # TODO: consider to eval 'title'/'raw_title' lazily
239 result
['title'] = label
+ ''.join(convert_title(ctx
, xml
))
240 result
['raw_title'] = encode_entities(raw_text(xml
))
241 if xml
.tag
!= 'title':
242 result
['title_tag'] = xml
.tag
244 result
['title_tag'] = tag
250 result
['subtitle'] = ''.join(convert_title(ctx
, xml
))
251 result
['subtitle_tag'] = xml
.tag
255 def chunk(xml_node
, module
, depth
=0, idx
=0, parent
=None):
258 The first time, we're called with parent=None and in that case we return
259 the new_node as the root of the tree. For each tree-node we generate a
260 filename and process the children.
263 chunk_params
= CHUNK_PARAMS
.get(tag
)
265 title_args
= get_chunk_titles(module
, xml_node
)
266 chunk_name
= gen_chunk_name(xml_node
, chunk_params
)
268 # check idx to handle 'sect1'/'section' special casing and title-only
270 if idx
>= chunk_params
.min_idx
:
271 logging
.info('chunk tag: "%s"[%d]', tag
, idx
)
273 # remove the xml-node from the parent
274 sub_tree
= etree
.ElementTree(deepcopy(xml_node
)).getroot()
275 xml_node
.getparent().remove(xml_node
)
278 parent
= Node(tag
, parent
=parent
, xml
=xml_node
, depth
=depth
,
280 filename
=chunk_name
+ '.html', anchor
=None,
283 parent
= Node(tag
, parent
=parent
, xml
=xml_node
, depth
=depth
,
285 filename
=parent
.filename
, anchor
='#' + chunk_name
,
290 for child
in xml_node
:
291 chunk(child
, module
, depth
, idx
, parent
)
292 if child
.tag
in CHUNK_PARAMS
:
298 def add_id_links_and_titles(files
, links
):
300 chunk_name
= node
.filename
[:-5]
301 chunk_base
= node
.filename
+ '#'
302 for elem
in ID_XPATH(node
.xml
):
303 attr
= elem
.attrib
['id']
304 if attr
== chunk_name
:
305 links
[attr
] = node
.filename
307 links
[attr
] = chunk_base
+ attr
309 title
= TITLE_XPATHS
.get(elem
.tag
, TITLE_XPATHS
['_'])[0]
313 # TODO: consider to eval 'title' lazily
315 'title': encode_entities(raw_text(xml
)),
321 def build_glossary(files
):
323 if node
.xml
.tag
!= 'glossary':
325 for term
in GLOSSENTRY_XPATH(node
.xml
):
326 # TODO: there can be all kind of things in a glossary. This only supports
327 # what we commonly use, glossterm is mandatory
328 key_node
= term
.find('glossterm')
329 val_node
= term
.find('glossdef')
330 if key_node
is not None and val_node
is not None:
331 glossary
[raw_text(key_node
)] = raw_text(val_node
)
335 debug
.append('missing key')
337 debug
.append('missing val')
338 logging
.warning('Broken glossentry "%s": %s',
339 term
.attrib
['id'], ','.join(debug
))
345 def convert_inner(ctx
, xml
, result
):
347 result
.extend(convert_tags
.get(child
.tag
, convert__unknown
)(ctx
, child
))
350 def convert_ignore(ctx
, xml
):
352 convert_inner(ctx
, xml
, result
)
356 def convert_skip(ctx
, xml
):
360 def append_idref(attrib
, result
):
361 idval
= attrib
.get('id')
362 if idval
is not None:
363 result
.append('<a name="%s"></a>' % idval
)
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 fileref
= imagedata
.attrib
.get('fileref', '')
409 result
.append('<img src="%s"%s>' % (fileref
, alt_text
))
412 def convert_sect(ctx
, xml
, h_tag
, inner_func
=convert_inner
):
413 result
= ['<div class="%s">\n' % xml
.tag
]
414 title_tag
= xml
.find('title')
415 if title_tag
is not None:
416 append_idref(xml
.attrib
, result
)
417 result
.append('<%s>%s</%s>' % (
418 h_tag
, ''.join(convert_title(ctx
, title_tag
)), h_tag
))
419 append_text(ctx
, xml
.text
, result
)
420 inner_func(ctx
, xml
, result
)
421 result
.append('</div>')
422 append_text(ctx
, xml
.tail
, result
)
426 def xml_get_title(ctx
, xml
):
427 title_tag
= xml
.find('title')
428 if title_tag
is not None:
429 return ''.join(convert_title(ctx
, title_tag
))
431 logging
.warning('%s: Expected title tag under "%s %s"', xml
.sourceline
, xml
.tag
, str(xml
.attrib
))
438 def convert_abstract(ctx
, xml
):
439 result
= ["""<div class="abstract">
440 <p class="title"><b>Abstract</b></p>"""]
441 append_text(ctx
, xml
.text
, result
)
442 convert_inner(ctx
, xml
, result
)
443 result
.append('</div>')
444 append_text(ctx
, xml
.tail
, result
)
448 def convert_acronym(ctx
, xml
):
450 title
= glossary
.get(key
, '')
451 # TODO: print a sensible warning if missing
452 result
= ['<acronym title="%s"><span class="acronym">%s</span></acronym>' % (title
, key
)]
454 result
.append(xml
.tail
)
458 def convert_anchor(ctx
, xml
):
459 return ['<a name="%s"></a>' % xml
.attrib
['id']]
462 def convert_bookinfo(ctx
, xml
):
463 result
= ['<div class="titlepage">']
464 convert_inner(ctx
, xml
, result
)
465 result
.append("""<hr>
468 result
.append(xml
.tail
)
472 def convert_blockquote(ctx
, xml
):
473 result
= ['<div class="blockquote">\n<blockquote class="blockquote">']
474 append_text(ctx
, xml
.text
, result
)
475 convert_inner(ctx
, xml
, result
)
476 result
.append('</blockquote>\n</div>')
477 append_text(ctx
, xml
.tail
, result
)
481 def convert_code(ctx
, xml
):
482 result
= ['<code class="%s">' % xml
.tag
]
483 append_text(ctx
, xml
.text
, result
)
484 convert_inner(ctx
, xml
, result
)
485 result
.append('</code>')
486 append_text(ctx
, xml
.tail
, result
)
490 def convert_colspec(ctx
, xml
):
492 colname
= xml
.attrib
.get('colname')
493 if colname
is not None:
494 result
.append(' class="%s"' % colname
)
495 colwidth
= xml
.attrib
.get('colwidth')
496 if colwidth
is not None:
497 result
.append(' width="%s"' % colwidth
)
499 # is in tgroup and there can be no 'text'
503 def convert_command(ctx
, xml
):
504 result
= ['<strong class="userinput"><code>']
505 append_text(ctx
, xml
.text
, result
)
506 convert_inner(ctx
, xml
, result
)
507 result
.append('</code></strong>')
508 append_text(ctx
, xml
.tail
, result
)
512 def convert_corpauthor(ctx
, xml
):
513 result
= ['<div><h3 class="corpauthor">\n']
514 append_text(ctx
, xml
.text
, result
)
515 convert_inner(ctx
, xml
, result
)
516 result
.append('</h3></div>\n')
517 append_text(ctx
, xml
.tail
, result
)
521 def convert_div(ctx
, xml
):
522 result
= ['<div class="%s">\n' % xml
.tag
]
523 append_text(ctx
, xml
.text
, result
)
524 convert_inner(ctx
, xml
, result
)
525 result
.append('</div>')
526 append_text(ctx
, xml
.tail
, result
)
530 def convert_emphasis(ctx
, xml
):
531 role
= xml
.attrib
.get('role')
533 result
= ['<span class="%s">' % role
]
536 result
= ['<span class="emphasis"><em>']
538 append_text(ctx
, xml
.text
, result
)
539 convert_inner(ctx
, xml
, result
)
541 append_text(ctx
, xml
.tail
, result
)
545 def convert_em(ctx
, xml
):
546 result
= ['<em class="%s">' % xml
.tag
]
547 append_text(ctx
, xml
.text
, result
)
548 convert_inner(ctx
, xml
, result
)
549 result
.append('</em>')
550 append_text(ctx
, xml
.tail
, result
)
554 def convert_em_code(ctx
, xml
):
555 result
= ['<em class="%s"><code>' % xml
.tag
]
556 append_idref(xml
.attrib
, result
)
557 append_text(ctx
, xml
.text
, result
)
558 convert_inner(ctx
, xml
, result
)
559 result
.append('</code></em>')
560 append_text(ctx
, xml
.tail
, result
)
564 def convert_entry(ctx
, xml
):
565 entry_type
= ctx
['table.entry']
566 result
= ['<' + entry_type
]
567 role
= xml
.attrib
.get('role')
569 result
.append(' class="%s"' % role
)
570 morerows
= xml
.attrib
.get('morerows')
571 if morerows
is not None:
572 result
.append(' rowspan="%s"' % (1 + int(morerows
)))
574 append_text(ctx
, xml
.text
, result
)
575 convert_inner(ctx
, xml
, result
)
576 result
.append('</' + entry_type
+ '>')
577 append_text(ctx
, xml
.tail
, result
)
581 def convert_figure(ctx
, xml
):
582 result
= ['<div class="figure">\n']
583 append_idref(xml
.attrib
, result
)
584 title_tag
= xml
.find('title')
585 if title_tag
is not None:
586 # TODO(ensonic): Add a 'Figure X. ' prefix, needs a figure counter
587 result
.append('<p><b>%s</b></p>' % ''.join(convert_title(ctx
, title_tag
)))
588 result
.append('<div class="figure-contents">')
589 # TODO(ensonic): title can become alt on inner 'graphic' element
590 convert_inner(ctx
, xml
, result
)
591 result
.append('</div></div><br class="figure-break"/>')
592 append_text(ctx
, xml
.tail
, result
)
596 def convert_footnote(ctx
, xml
):
597 footnotes
= ctx
.get('footnotes', [])
598 # footnotes idx is not per page, but per doc
603 # need a pair of ids for each footnote (docbook generates different ids)
604 this_id
= 'footnote-%d' % idx
605 that_id
= 'ftn.' + this_id
607 inner
= ['<div id="%s" class="footnote">' % that_id
]
608 inner
.append('<p><a href="#%s" class="para"><sup class="para">[%d] </sup></a>' % (
610 # TODO(ensonic): this can contain all kind of tags, if we convert them we'll
611 # get double nested paras :/.
612 # convert_inner(ctx, xml, inner)
613 para
= xml
.find('para')
615 para
= xml
.find('simpara')
617 inner
.append(para
.text
)
619 logging
.warning('%s: Unhandled footnote content: %s', xml
.sourceline
, raw_text(xml
))
620 inner
.append('</p></div>')
621 footnotes
.append(inner
)
622 ctx
['footnotes'] = footnotes
623 return ['<a href="#%s" class="footnote" name="%s"><sup class="footnote">[%s]</sup></a>' % (
624 that_id
, this_id
, idx
)]
627 def convert_formalpara(ctx
, xml
):
629 title_tag
= xml
.find('title')
630 result
= ['<p><b>%s</b>' % ''.join(convert_title(ctx
, title_tag
))]
631 para_tag
= xml
.find('para')
632 append_text(ctx
, para_tag
.text
, result
)
633 convert_inner(ctx
, para_tag
, result
)
634 append_text(ctx
, para_tag
.tail
, result
)
635 result
.append('</p>')
636 append_text(ctx
, xml
.tail
, result
)
640 def convert_glossdef(ctx
, xml
):
641 result
= ['<dd class="glossdef">']
642 convert_inner(ctx
, xml
, result
)
643 result
.append('</dd>\n')
647 def convert_glossdiv(ctx
, xml
):
648 title_tag
= xml
.find('title')
649 title
= title_tag
.text
650 xml
.remove(title_tag
)
652 '<a name="gls%s"></a><h3 class="title">%s</h3>' % (title
, title
)
654 convert_inner(ctx
, xml
, result
)
658 def convert_glossentry(ctx
, xml
):
660 convert_inner(ctx
, xml
, result
)
664 def convert_glossterm(ctx
, xml
):
667 anchor
= xml
.find('anchor')
668 if anchor
is not None:
669 glossid
= anchor
.attrib
.get('id', '')
670 text
+= anchor
.tail
or ''
671 text
+= xml
.text
or ''
673 glossid
= 'glossterm-' + text
675 '<dt><span class="glossterm"><a name="%s"></a>%s</span></dt>' % (
680 def convert_graphic(ctx
, xml
):
681 # TODO(ensonic): warn on missing fileref attr?
682 fileref
= xml
.attrib
.get('fileref', '')
685 return ['<div><img src="%s"></div>' % fileref
]
688 def convert_indexdiv(ctx
, xml
):
689 title_tag
= xml
.find('title')
690 title
= title_tag
.text
691 xml
.remove(title_tag
)
693 '<a name="idx%s"></a><h3 class="title">%s</h3>' % (title
, title
)
695 convert_inner(ctx
, xml
, result
)
699 def convert_informaltable(ctx
, xml
):
700 result
= ['<div class="informaltable"><table class="informaltable"']
701 if xml
.attrib
.get('pgwide') == '1':
702 result
.append(' width="100%"')
703 if xml
.attrib
.get('frame') == 'none':
704 result
.append(' border="0"')
706 convert_inner(ctx
, xml
, result
)
707 result
.append('</table></div>')
709 result
.append(xml
.tail
)
713 def convert_inlinegraphic(ctx
, xml
):
714 # TODO(ensonic): warn on missing fileref attr?
715 fileref
= xml
.attrib
.get('fileref', '')
718 return ['<img src="%s">' % fileref
]
721 def convert_inlinemediaobject(ctx
, xml
):
722 result
= ['<span class="inlinemediaobject">']
723 # no PCDATA allowed here
724 convert_mediaobject_children(ctx
, xml
, result
)
725 result
.append('</span>')
726 append_text(ctx
, xml
.tail
, result
)
730 def convert_itemizedlist(ctx
, xml
):
731 result
= ['<div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; ">']
732 convert_inner(ctx
, xml
, result
)
733 result
.append('</ul></div>')
735 result
.append(xml
.tail
)
739 def convert_link(ctx
, xml
):
740 linkend
= xml
.attrib
['linkend']
744 append_text(ctx
, xml
.text
, link_text
)
745 convert_inner(ctx
, xml
, link_text
)
746 text
= ''.join(link_text
)
748 (tid
, href
) = fixxref
.GetXRef(linkend
)
751 title
= titles
.get(tid
)
753 title_attr
= ' title="%s"' % title
['title']
755 href
= fixxref
.MakeRelativeXRef(ctx
['module'], href
)
756 result
= ['<a href="%s"%s>%s</a>' % (href
, title_attr
, text
)]
758 # TODO: filename is for the output and xml.sourceline is on the masterdoc ...
759 fixxref
.ReportBadXRef(ctx
['node'].filename
, 0, linkend
, text
)
762 append_text(ctx
, xml
.text
, result
)
763 convert_inner(ctx
, xml
, result
)
764 append_text(ctx
, xml
.tail
, result
)
768 def convert_listitem(ctx
, xml
):
769 result
= ['<li class="listitem">']
770 convert_inner(ctx
, xml
, result
)
771 result
.append('</li>')
772 # no PCDATA allowed here, is in itemizedlist
776 def convert_literallayout(ctx
, xml
):
777 result
= ['<div class="literallayout"><p><br>\n']
778 append_text(ctx
, xml
.text
, result
)
779 convert_inner(ctx
, xml
, result
)
780 result
.append('</p></div>')
781 append_text(ctx
, xml
.tail
, result
)
785 def convert_mediaobject(ctx
, xml
):
786 result
= ['<div class="mediaobject">\n']
787 # no PCDATA allowed here
788 convert_mediaobject_children(ctx
, xml
, result
)
789 result
.append('</div>')
790 append_text(ctx
, xml
.tail
, result
)
794 def convert_orderedlist(ctx
, xml
):
795 result
= ['<div class="orderedlist"><ol class="orderedlist" type="1">']
796 convert_inner(ctx
, xml
, result
)
797 result
.append('</ol></div>')
798 append_text(ctx
, xml
.tail
, result
)
802 def convert_para(ctx
, xml
):
804 role
= xml
.attrib
.get('role')
806 result
.append('<p class="%s">' % role
)
809 append_idref(xml
.attrib
, result
)
810 append_text(ctx
, xml
.text
, result
)
811 convert_inner(ctx
, xml
, result
)
812 result
.append('</p>')
813 append_text(ctx
, xml
.tail
, result
)
817 def convert_para_like(ctx
, xml
):
819 append_idref(xml
.attrib
, result
)
820 result
.append('<p class="%s">' % xml
.tag
)
821 append_text(ctx
, xml
.text
, result
)
822 convert_inner(ctx
, xml
, result
)
823 result
.append('</p>')
824 append_text(ctx
, xml
.tail
, result
)
828 def convert_phrase(ctx
, xml
):
830 role
= xml
.attrib
.get('role')
832 result
.append(' class="%s">' % role
)
835 append_text(ctx
, xml
.text
, result
)
836 convert_inner(ctx
, xml
, result
)
837 result
.append('</span>')
838 append_text(ctx
, xml
.tail
, result
)
842 def convert_primaryie(ctx
, xml
):
844 convert_inner(ctx
, xml
, result
)
845 result
.append('\n</dt>\n<dd></dd>\n')
849 def convert_pre(ctx
, xml
):
850 # Since we're inside <pre> don't skip newlines
851 ctx
['no-strip'] = True
852 result
= ['<pre class="%s">' % xml
.tag
]
853 append_text(ctx
, xml
.text
, result
)
854 convert_inner(ctx
, xml
, result
)
855 result
.append('</pre>')
857 append_text(ctx
, xml
.tail
, result
)
861 def convert_programlisting(ctx
, xml
):
863 if xml
.attrib
.get('role', '') == 'example':
865 lang
= xml
.attrib
.get('language', ctx
['src-lang']).lower()
866 if lang
not in LEXERS
:
867 LEXERS
[lang
] = get_lexer_by_name(lang
)
868 lexer
= LEXERS
.get(lang
, None)
870 highlighted
= highlight(xml
.text
, lexer
, HTML_FORMATTER
)
872 # we do own line-numbering
873 line_count
= highlighted
.count('\n')
874 source_lines
= '\n'.join([str(i
) for i
in range(1, line_count
+ 1)])
875 result
.append("""<table class="listing_frame" border="0" cellpadding="0" cellspacing="0">
878 <td class="listing_lines" align="right"><pre>%s</pre></td>
879 <td class="listing_code"><pre class="programlisting">%s</pre></td>
883 """ % (source_lines
, highlighted
))
885 logging
.warn('No pygments lexer for language="%s"', lang
)
886 result
.append('<pre class="programlisting">')
887 result
.append(xml
.text
)
888 result
.append('</pre>')
890 result
.append('<pre class="programlisting">')
891 append_text(ctx
, xml
.text
, result
)
892 convert_inner(ctx
, xml
, result
)
893 result
.append('</pre>')
894 append_text(ctx
, xml
.tail
, result
)
898 def convert_quote(ctx
, xml
):
899 result
= ['<span class="quote">"<span class="quote">']
900 append_text(ctx
, xml
.text
, result
)
901 convert_inner(ctx
, xml
, result
)
902 result
.append('</span>"</span>')
903 append_text(ctx
, xml
.tail
, result
)
907 def convert_refsect1(ctx
, xml
):
908 # Add a divider between two consequitive refsect2
909 def convert_inner(ctx
, xml
, result
):
912 if child
.tag
== 'refsect2' and prev
is not None and prev
.tag
== child
.tag
:
913 result
.append('<hr>\n')
914 result
.extend(convert_tags
.get(child
.tag
, convert__unknown
)(ctx
, child
))
916 return convert_sect(ctx
, xml
, 'h2', convert_inner
)
919 def convert_refsect2(ctx
, xml
):
920 return convert_sect(ctx
, xml
, 'h3')
923 def convert_refsect3(ctx
, xml
):
924 return convert_sect(ctx
, xml
, 'h4')
927 def convert_row(ctx
, xml
):
929 convert_inner(ctx
, xml
, result
)
930 result
.append('</tr>\n')
934 def convert_sbr(ctx
, xml
):
938 def convert_sect1_tag(ctx
, xml
):
939 return convert_sect(ctx
, xml
, 'h2')
942 def convert_sect2(ctx
, xml
):
943 return convert_sect(ctx
, xml
, 'h3')
946 def convert_sect3(ctx
, xml
):
947 return convert_sect(ctx
, xml
, 'h4')
950 def convert_simpara(ctx
, xml
):
952 append_text(ctx
, xml
.text
, result
)
953 convert_inner(ctx
, xml
, result
)
954 result
.append('</p>')
955 append_text(ctx
, xml
.tail
, result
)
959 def convert_span(ctx
, xml
):
960 result
= ['<span class="%s">' % xml
.tag
]
961 append_text(ctx
, xml
.text
, result
)
962 convert_inner(ctx
, xml
, result
)
963 result
.append('</span>')
964 append_text(ctx
, xml
.tail
, result
)
968 def convert_table(ctx
, xml
):
969 result
= ['<div class="table">']
970 append_idref(xml
.attrib
, result
)
971 title_tag
= xml
.find('title')
972 if title_tag
is not None:
973 result
.append('<p class="title"><b>')
974 # TODO(ensonic): Add a 'Table X. ' prefix, needs a table counter
975 result
.extend(convert_title(ctx
, title_tag
))
976 result
.append('</b></p>')
977 result
.append('<div class="table-contents"><table class="table" summary="g_object_new" border="1">')
979 convert_inner(ctx
, xml
, result
)
981 result
.append('</table></div></div>')
982 append_text(ctx
, xml
.tail
, result
)
986 def convert_tag(ctx
, xml
):
987 classval
= xml
.attrib
.get('class')
988 if classval
is not None:
989 result
= ['<code class="sgmltag-%s">' % classval
]
992 append_text(ctx
, xml
.text
, result
)
993 result
.append('</code>')
994 append_text(ctx
, xml
.tail
, result
)
998 def convert_tbody(ctx
, xml
):
1000 ctx
['table.entry'] = 'td'
1001 convert_inner(ctx
, xml
, result
)
1002 result
.append('</tbody>')
1003 # is in tgroup and there can be no 'text'
1007 def convert_tgroup(ctx
, xml
):
1008 # tgroup does not expand to anything, but the nested colspecs need to
1009 # be put into a colgroup
1010 cols
= xml
.findall('colspec')
1013 result
.append('<colgroup>\n')
1015 result
.extend(convert_colspec(ctx
, col
))
1017 result
.append('</colgroup>\n')
1018 convert_inner(ctx
, xml
, result
)
1019 # is in informaltable and there can be no 'text'
1023 def convert_thead(ctx
, xml
):
1024 result
= ['<thead>']
1025 ctx
['table.entry'] = 'th'
1026 convert_inner(ctx
, xml
, result
)
1027 result
.append('</thead>')
1028 # is in tgroup and there can be no 'text'
1032 def convert_title(ctx
, xml
):
1033 # This is always explicitly called from some context
1035 append_text(ctx
, xml
.text
, result
)
1036 convert_inner(ctx
, xml
, result
)
1037 append_text(ctx
, xml
.tail
, result
)
1041 def convert_ulink(ctx
, xml
):
1043 result
= ['<a class="%s" href="%s">%s</a>' % (xml
.tag
, xml
.attrib
['url'], xml
.text
)]
1045 url
= xml
.attrib
['url']
1046 result
= ['<a class="%s" href="%s">%s</a>' % (xml
.tag
, url
, url
)]
1047 append_text(ctx
, xml
.tail
, result
)
1051 def convert_userinput(ctx
, xml
):
1052 result
= ['<span class="command"><strong>']
1053 append_text(ctx
, xml
.text
, result
)
1054 convert_inner(ctx
, xml
, result
)
1055 result
.append('</strong></span>')
1056 append_text(ctx
, xml
.tail
, result
)
1060 def convert_variablelist(ctx
, xml
):
1061 result
= ["""<div class="variablelist"><table border="0" class="variablelist">
1063 <col align="left" valign="top">
1067 convert_inner(ctx
, xml
, result
)
1068 result
.append("""</tbody>
1073 def convert_varlistentry(ctx
, xml
):
1076 result
.append('<td><p>')
1077 term
= xml
.find('term')
1078 result
.extend(convert_span(ctx
, term
))
1079 result
.append('</p></td>')
1081 result
.append('<td>')
1082 listitem
= xml
.find('listitem')
1083 convert_inner(ctx
, listitem
, result
)
1084 result
.append('</td>')
1086 result
.append('<tr>')
1090 def convert_xref(ctx
, xml
):
1092 linkend
= xml
.attrib
['linkend']
1093 (tid
, href
) = fixxref
.GetXRef(linkend
)
1096 # all sectN need to become 'section
1106 '<a class="xref" href="%s" title="%s">the %s called “%s”</a>' %
1107 (href
, title
['title'], tag
, ''.join(convert_title(ctx
, title
['xml'])))
1110 logging
.warning('invalid linkend "%s"', tid
)
1112 append_text(ctx
, xml
.tail
, result
)
1116 # TODO(ensonic): turn into class with converters as functions and ctx as self
1118 'abstract': convert_abstract
,
1119 'acronym': convert_acronym
,
1120 'anchor': convert_anchor
,
1121 'application': convert_span
,
1122 'bookinfo': convert_bookinfo
,
1123 'blockquote': convert_blockquote
,
1124 'classname': convert_code
,
1125 'caption': convert_div
,
1126 'code': convert_code
,
1127 'colspec': convert_colspec
,
1128 'constant': convert_code
,
1129 'command': convert_command
,
1130 'corpauthor': convert_corpauthor
,
1131 'emphasis': convert_emphasis
,
1132 'entry': convert_entry
,
1133 'envar': convert_code
,
1134 'footnote': convert_footnote
,
1135 'figure': convert_figure
,
1136 'filename': convert_code
,
1137 'firstterm': convert_em
,
1138 'formalpara': convert_formalpara
,
1139 'function': convert_code
,
1140 'glossdef': convert_glossdef
,
1141 'glossdiv': convert_glossdiv
,
1142 'glossentry': convert_glossentry
,
1143 'glossterm': convert_glossterm
,
1144 'graphic': convert_graphic
,
1145 'indexdiv': convert_indexdiv
,
1146 'indexentry': convert_ignore
,
1147 'indexterm': convert_skip
,
1148 'informalexample': convert_div
,
1149 'informaltable': convert_informaltable
,
1150 'inlinegraphic': convert_inlinegraphic
,
1151 'inlinemediaobject': convert_inlinemediaobject
,
1152 'interfacename': convert_code
,
1153 'itemizedlist': convert_itemizedlist
,
1154 'legalnotice': convert_div
,
1155 'link': convert_link
,
1156 'listitem': convert_listitem
,
1157 'literal': convert_code
,
1158 'literallayout': convert_literallayout
,
1159 'mediaobject': convert_mediaobject
,
1160 'note': convert_div
,
1161 'option': convert_code
,
1162 'orderedlist': convert_orderedlist
,
1163 'para': convert_para
,
1164 'partintro': convert_div
,
1165 'parameter': convert_em_code
,
1166 'phrase': convert_phrase
,
1167 'primaryie': convert_primaryie
,
1168 'programlisting': convert_programlisting
,
1169 'quote': convert_quote
,
1170 'releaseinfo': convert_para_like
,
1171 'refsect1': convert_refsect1
,
1172 'refsect2': convert_refsect2
,
1173 'refsect3': convert_refsect3
,
1174 'replaceable': convert_em_code
,
1175 'returnvalue': convert_span
,
1178 'screen': convert_pre
,
1179 'section': convert_sect2
, # FIXME: need tracking of nesting
1180 'sect1': convert_sect1_tag
,
1181 'sect2': convert_sect2
,
1182 'sect3': convert_sect3
,
1183 'simpara': convert_simpara
,
1184 'simplesect': convert_sect2
, # FIXME: need tracking of nesting
1185 'structfield': convert_em_code
,
1186 'structname': convert_span
,
1187 'synopsis': convert_pre
,
1188 'symbol': convert_span
,
1189 'table': convert_table
,
1191 'tbody': convert_tbody
,
1192 'term': convert_span
,
1193 'tgroup': convert_tgroup
,
1194 'thead': convert_thead
,
1195 'title': convert_skip
,
1196 'type': convert_span
,
1197 'ulink': convert_ulink
,
1198 'userinput': convert_userinput
,
1199 'varname': convert_code
,
1200 'variablelist': convert_variablelist
,
1201 'varlistentry': convert_varlistentry
,
1202 'warning': convert_div
,
1203 'xref': convert_xref
,
1206 # conversion helpers
1208 HTML_HEADER
= """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
1211 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1213 %s<link rel="stylesheet" href="style.css" type="text/css">
1215 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
1219 def generate_head_links(ctx
):
1222 '<link rel="home" href="%s" title="%s">\n' % (n
.filename
, n
.raw_title
)
1225 n
= ctx
.get('nav_up')
1227 result
.append('<link rel="up" href="%s" title="%s">\n' % (n
.filename
, n
.raw_title
))
1229 n
= ctx
.get('nav_prev')
1231 result
.append('<link rel="prev" href="%s" title="%s">\n' % (n
.filename
, n
.raw_title
))
1233 n
= ctx
.get('nav_next')
1235 result
.append('<link rel="next" href="%s" title="%s">\n' % (n
.filename
, n
.raw_title
))
1237 return ''.join(result
)
1240 def generate_nav_links(ctx
):
1243 '<td><a accesskey="h" href="%s"><img src="home.png" width="16" height="16" border="0" alt="Home"></a></td>' % n
.filename
1246 n
= ctx
.get('nav_up')
1249 '<td><a accesskey="u" href="%s"><img src="up.png" width="16" height="16" border="0" alt="Up"></a></td>' % n
.filename
)
1251 result
.append('<td><img src="up-insensitive.png" width="16" height="16" border="0"></td>')
1253 n
= ctx
.get('nav_prev')
1256 '<td><a accesskey="p" href="%s"><img src="left.png" width="16" height="16" border="0" alt="Prev"></a></td>' % n
.filename
)
1258 result
.append('<td><img src="left-insensitive.png" width="16" height="16" border="0"></td>')
1260 n
= ctx
.get('nav_next')
1263 '<td><a accesskey="n" href="%s"><img src="right.png" width="16" height="16" border="0" alt="Next"></a></td>' % n
.filename
)
1265 result
.append('<td><img src="right-insensitive.png" width="16" height="16" border="0"></td>')
1267 return ''.join(result
)
1270 def generate_toc(ctx
, node
):
1272 for c
in node
.children
:
1273 # TODO: urlencode the filename: urllib.parse.quote_plus()
1277 result
.append('<dt><span class="%s"><a href="%s">%s</a></span>\n' % (
1278 c
.title_tag
, link
, c
.title
))
1280 result
.append('<span class="%s"> — %s</span>' % (c
.subtitle_tag
, c
.subtitle
))
1281 result
.append('</dt>\n')
1283 result
.append('<dd><dl>')
1284 result
.extend(generate_toc(ctx
, c
))
1285 result
.append('</dl></dd>')
1289 def generate_basic_nav(ctx
):
1290 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1291 <tr valign="middle">
1292 <td width="100%%" align="left" class="shortcuts"></td>
1296 """ % generate_nav_links(ctx
)
1299 def generate_alpha_nav(ctx
, divs
, prefix
, span_id
):
1302 title
= xml_get_title(ctx
, s
)
1303 ix_nav
.append('<a class="shortcut" href="#%s%s">%s</a>' % (prefix
, title
, title
))
1305 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1306 <tr valign="middle">
1307 <td width="100%%" align="left" class="shortcuts">
1315 """ % (span_id
, '\n<span class="dim">|</span>\n'.join(ix_nav
), generate_nav_links(ctx
))
1318 def generate_refentry_nav(ctx
, refsect1s
, result
):
1319 result
.append("""<table class="navigation" id="top" width="100%" cellpadding="2" cellspacing="5">
1320 <tr valign="middle">
1321 <td width="100%" align="left" class="shortcuts">
1322 <a href="#" class="shortcut">Top</a>""")
1325 # don't list TOC sections (role="xxx_proto")
1326 if s
.attrib
.get('role', '').endswith("_proto"):
1328 # skip section without 'id' attrs
1329 ref_id
= s
.attrib
.get('id')
1333 # skip foreign sections
1334 if '.' not in ref_id
:
1337 title
= xml_get_title(ctx
, s
)
1338 span_id
= ref_id
.split('.')[1].replace('-', '_')
1342 <span class="dim">|</span>
1343 <a href="#%s" class="shortcut">%s</a>
1345 """ % (span_id
, ref_id
, title
))
1351 """ % generate_nav_links(ctx
))
1354 def generate_footer(ctx
):
1355 footnotes
= ctx
.get('footnotes')
1356 if footnotes
is None:
1359 result
= ["""<div class="footnotes">\n
1360 <br><hr style="width:100; text-align:left;margin-left: 0">
1364 result
.append('</div>\n')
1368 def get_id_path(node
):
1369 """ Generate the 'id'.
1370 We need to walk up the xml-tree and check the positions for each sibling.
1371 When reaching the top of the tree we collect remaining index entries from
1376 parent
= xml
.getparent()
1377 while parent
is not None:
1378 children
= parent
.getchildren()
1379 ix
.insert(0, str(children
.index(xml
) + 1))
1381 parent
= xml
.getparent()
1382 while node
is not None:
1383 ix
.insert(0, str(node
.idx
+ 1))
1391 node_id
= xml
.attrib
.get('id', None)
1395 # TODO: this is moot if nothing links to it, we could also consider to omit
1396 # the <a name="$id"></a> tag.
1397 logging
.info('%d: No "id" attribute on "%s", generating one',
1398 xml
.sourceline
, xml
.tag
)
1399 ix
= get_id_path(node
)
1400 # logging.warning('%s: id indexes: %s', node.filename, str(ix))
1401 return 'id-' + '.'.join(ix
)
1404 def convert_chunk_with_toc(ctx
, div_class
, title_tag
):
1407 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1408 generate_basic_nav(ctx
),
1409 '<div class="%s">' % div_class
,
1413 <div class="titlepage">
1414 <%s class="title"><a name="%s"></a>%s</%s>
1416 title_tag
, get_id(node
), node
.title
, title_tag
))
1418 toc
= generate_toc(ctx
, node
)
1420 # TODO: not all docbook page types use this extra heading
1421 result
.append("""<p><b>Table of Contents</b></p>
1426 result
.append("""</dl>
1429 convert_inner(ctx
, node
.xml
, result
)
1430 result
.extend(generate_footer(ctx
))
1431 result
.append("""</div>
1440 def convert_book(ctx
):
1443 HTML_HEADER
% (node
.title
, generate_head_links(ctx
)),
1444 """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="0">
1445 <tr><th valign="middle"><p class="title">%s</p></th></tr>
1450 bookinfo
= node
.xml
.findall('bookinfo')[0]
1451 result
.extend(convert_bookinfo(ctx
, bookinfo
))
1452 result
.append("""<div class="toc">
1455 result
.extend(generate_toc(ctx
, node
.root
))
1456 result
.append("""</dl>
1459 result
.extend(generate_footer(ctx
))
1460 result
.append("""</div>
1466 def convert_chapter(ctx
):
1467 return convert_chunk_with_toc(ctx
, 'chapter', 'h2')
1470 def convert_glossary(ctx
):
1472 glossdivs
= node
.xml
.findall('glossdiv')
1475 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1476 generate_alpha_nav(ctx
, glossdivs
, 'gls', 'glossary'),
1477 """<div class="glossary">
1478 <div class="titlepage"><h%1d class="title">
1479 <a name="%s"></a>%s</h%1d>
1480 </div>""" % (node
.depth
, get_id(node
), node
.title
, node
.depth
)
1483 result
.extend(convert_glossdiv(ctx
, i
))
1484 result
.extend(generate_footer(ctx
))
1485 result
.append("""</div>
1491 def convert_index(ctx
):
1493 # Get all indexdivs under indexdiv
1494 indexdivs
= node
.xml
.find('indexdiv').findall('indexdiv')
1497 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1498 generate_alpha_nav(ctx
, indexdivs
, 'idx', 'index'),
1499 """<div class="index">
1500 <div class="titlepage"><h%1d class="title">
1501 <a name="%s"></a>%s</h%1d>
1502 </div>""" % (node
.depth
, get_id(node
), node
.title
, node
.depth
)
1505 result
.extend(convert_indexdiv(ctx
, i
))
1506 result
.extend(generate_footer(ctx
))
1507 result
.append("""</div>
1513 def convert_part(ctx
):
1514 return convert_chunk_with_toc(ctx
, 'part', 'h1')
1517 def convert_preface(ctx
):
1520 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1521 generate_basic_nav(ctx
),
1522 '<div class="preface">'
1526 <div class="titlepage">
1527 <h2 class="title"><a name="%s"></a>%s</h2>
1528 </div>""" % (get_id(node
), node
.title
))
1529 convert_inner(ctx
, node
.xml
, result
)
1530 result
.extend(generate_footer(ctx
))
1531 result
.append("""</div>
1537 def convert_reference(ctx
):
1538 return convert_chunk_with_toc(ctx
, 'reference', 'h1')
1541 def convert_refentry(ctx
):
1543 node_id
= get_id(node
)
1544 refsect1s
= node
.xml
.findall('refsect1')
1547 refmeta
= node
.xml
.find('refmeta')
1548 if refmeta
is not None:
1549 refmiscinfo
= refmeta
.find('refmiscinfo')
1550 if refmiscinfo
is not None:
1551 inlinegraphic
= refmiscinfo
.find('inlinegraphic')
1552 if inlinegraphic
is not None:
1553 gallery
= ''.join(convert_inlinegraphic(ctx
, inlinegraphic
))
1556 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
))
1558 generate_refentry_nav(ctx
, refsect1s
, result
)
1560 <div class="refentry">
1562 <div class="refnamediv">
1563 <table width="100%%"><tr>
1565 <h2><span class="refentrytitle"><a name="%s.top_of_page"></a>%s</span></h2>
1568 <td class="gallery_image" valign="top" align="right">%s</td>
1571 """ % (node_id
, node_id
, node
.title
, node
.title
, node
.subtitle
, gallery
))
1574 result
.extend(convert_refsect1(ctx
, s
))
1575 result
.extend(generate_footer(ctx
))
1576 result
.append("""</div>
1582 def convert_section(ctx
):
1583 return convert_chunk_with_toc(ctx
, 'section', 'h2')
1586 def convert_sect1(ctx
):
1587 return convert_chunk_with_toc(ctx
, 'sect1', 'h2')
1590 # TODO(ensonic): turn into class with converters as functions and ctx as self
1592 'book': convert_book
,
1593 'chapter': convert_chapter
,
1594 'glossary': convert_glossary
,
1595 'index': convert_index
,
1596 'part': convert_part
,
1597 'preface': convert_preface
,
1598 'reference': convert_reference
,
1599 'refentry': convert_refentry
,
1600 'section': convert_section
,
1601 'sect1': convert_sect1
,
1605 def generate_nav_nodes(files
, node
):
1607 'nav_home': node
.root
,
1609 # nav params: up, prev, next
1611 nav
['nav_up'] = node
.parent
1612 ix
= files
.index(node
)
1614 nav
['nav_prev'] = files
[ix
- 1]
1615 if ix
< len(files
) - 1:
1616 nav
['nav_next'] = files
[ix
+ 1]
1620 def convert(out_dir
, module
, files
, node
, src_lang
):
1621 """Convert the docbook chunks to a html file.
1624 out_dir: already created output dir
1625 files: list of nodes in the tree in pre-order
1626 node: current tree node
1629 logging
.info('Writing: %s', node
.filename
)
1630 with
open(os
.path
.join(out_dir
, node
.filename
), 'wt',
1631 newline
='\n', encoding
='utf-8') as html
:
1636 'src-lang': src_lang
,
1638 ctx
.update(generate_nav_nodes(files
, node
))
1640 converter
= convert_chunks
.get(node
.name
)
1641 if converter
is not None:
1642 for line
in converter(ctx
):
1645 logging
.warning('Add chunk converter for "%s"', node
.name
)
1648 def create_devhelp2_toc(node
):
1650 for c
in node
.children
:
1652 result
.append('<sub name="%s" link="%s">\n' % (c
.raw_title
, c
.filename
))
1653 result
.extend(create_devhelp2_toc(c
))
1654 result
.append('</sub>\n')
1656 result
.append('<sub name="%s" link="%s"/>\n' % (c
.raw_title
, c
.filename
))
1660 def create_devhelp2_condition_attribs(node
):
1661 condition
= node
.attrib
.get('condition')
1662 if condition
is not None:
1663 # condition -> since, deprecated, ... (separated with '|')
1664 cond
= condition
.replace('"', '"').split('|')
1668 keywords
.append('{}="{}"'.format(*c
.split(':', 1)))
1670 # deprecated can have no description
1671 keywords
.append('{}="{}"'.format(c
, ''))
1672 return ' ' + ' '.join(keywords
)
1677 def create_devhelp2_refsect2_keyword(node
, base_link
):
1678 node_id
= node
.attrib
['id']
1679 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1680 node
.attrib
['role'], titles
[node_id
]['title'], base_link
+ node_id
,
1681 create_devhelp2_condition_attribs(node
))
1684 def create_devhelp2_refsect3_keyword(node
, base_link
, title
, name
):
1685 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1686 node
.attrib
['role'], title
, base_link
+ name
,
1687 create_devhelp2_condition_attribs(node
))
1690 def create_devhelp2(out_dir
, module
, xml
, files
):
1691 with
open(os
.path
.join(out_dir
, module
+ '.devhelp2'), 'wt',
1692 newline
='\n', encoding
='utf-8') as idx
:
1693 bookinfo_nodes
= xml
.xpath('/book/bookinfo')
1695 if bookinfo_nodes
is not None:
1696 bookinfo
= bookinfo_nodes
[0]
1697 title
= bookinfo
.xpath('./title/text()')[0]
1698 online_url
= bookinfo
.xpath('./releaseinfo/ulink[@role="online-location"]/@url')[0]
1699 # TODO: support author too (see devhelp2.xsl)
1700 # TODO: fixxref uses '--src-lang' to set the language
1702 """<?xml version="1.0" encoding="utf-8" standalone="no"?>
1703 <book xmlns="http://www.devhelp.net/book" title="%s" link="index.html" author="" name="%s" version="2" language="c" online="%s">
1705 """ % (title
, module
, online_url
)
1708 result
.extend(create_devhelp2_toc(files
[0].root
))
1709 result
.append(""" </chapters>
1712 # keywords from all refsect2 and refsect3
1713 refsect2
= etree
.XPath('//refsect2[@role]')
1714 refsect3_enum
= etree
.XPath('refsect3[@role="enum_members"]/informaltable/tgroup/tbody/row[@role="constant"]')
1715 refsect3_enum_details
= etree
.XPath('entry[@role="enum_member_name"]/para')
1716 refsect3_struct
= etree
.XPath('refsect3[@role="struct_members"]/informaltable/tgroup/tbody/row[@role="member"]')
1717 refsect3_struct_details
= etree
.XPath('entry[@role="struct_member_name"]/para/structfield')
1719 base_link
= node
.filename
+ '#'
1720 refsect2_nodes
= refsect2(node
.xml
)
1721 for refsect2_node
in refsect2_nodes
:
1722 result
.append(create_devhelp2_refsect2_keyword(refsect2_node
, base_link
))
1723 refsect3_nodes
= refsect3_enum(refsect2_node
)
1724 for refsect3_node
in refsect3_nodes
:
1725 details_node
= refsect3_enum_details(refsect3_node
)[0]
1726 name
= details_node
.attrib
['id']
1727 result
.append(create_devhelp2_refsect3_keyword(refsect3_node
, base_link
, details_node
.text
, name
))
1728 refsect3_nodes
= refsect3_struct(refsect2_node
)
1729 for refsect3_node
in refsect3_nodes
:
1730 details_node
= refsect3_struct_details(refsect3_node
)[0]
1731 name
= details_node
.attrib
['id']
1732 result
.append(create_devhelp2_refsect3_keyword(refsect3_node
, base_link
, name
, name
))
1734 result
.append(""" </functions>
1741 def get_dirs(uninstalled
):
1743 # this does not work from buiddir!=srcdir
1744 gtkdocdir
= os
.path
.split(sys
.argv
[0])[0]
1745 if not os
.path
.exists(gtkdocdir
+ '/gtk-doc.xsl'):
1746 # try 'srcdir' (set from makefiles) too
1747 if os
.path
.exists(os
.environ
.get("ABS_TOP_SRCDIR", '') + '/gtk-doc.xsl'):
1748 gtkdocdir
= os
.environ
['ABS_TOP_SRCDIR']
1749 styledir
= gtkdocdir
+ '/style'
1751 gtkdocdir
= os
.path
.join(config
.datadir
, 'gtk-doc/data')
1752 styledir
= gtkdocdir
1753 return (gtkdocdir
, styledir
)
1756 def main(module
, index_file
, out_dir
, uninstalled
, src_lang
, paths
):
1758 # == Loading phase ==
1759 # the next 3 steps could be done in paralel
1761 # 1) load the docuemnt
1763 # does not seem to be faster
1764 # parser = etree.XMLParser(collect_ids=False)
1765 # tree = etree.parse(index_file, parser)
1766 tree
= etree
.parse(index_file
)
1768 logging
.warning("1: %7.3lf: load doc", timer() - _t
)
1772 # TODO: handle additional images
1773 (gtkdocdir
, styledir
) = get_dirs(uninstalled
)
1774 # copy navigation images and stylesheets to html directory ...
1775 css_file
= os
.path
.join(styledir
, 'style.css')
1776 for f
in glob(os
.path
.join(styledir
, '*.png')) + [css_file
]:
1777 shutil
.copy(f
, out_dir
)
1778 css_file
= os
.path
.join(out_dir
, 'style.css')
1779 with
open(css_file
, 'at', newline
='\n', encoding
='utf-8') as css
:
1780 css
.write(HTML_FORMATTER
.get_style_defs())
1781 logging
.warning("2: %7.3lf: copy datafiles", timer() - _t
)
1783 # 3) load xref targets
1785 # TODO: migrate options from fixxref
1786 # TODO: ideally explicity specify the files we need, this will save us the
1787 # globbing and we'll load less files.
1788 fixxref
.LoadIndicies(out_dir
, '/usr/share/gtk-doc/html', [])
1789 logging
.warning("3: %7.3lf: load xrefs", timer() - _t
)
1791 # == Processing phase ==
1793 # 4) recursively walk the tree and chunk it into a python tree so that we
1794 # can generate navigation and link tags.
1796 files
= chunk(tree
.getroot(), module
)
1797 files
= [f
for f
in PreOrderIter(files
) if f
.anchor
is None]
1798 logging
.warning("4: %7.3lf: chunk doc", timer() - _t
)
1800 # 5) extract tables:
1802 # TODO: can be done in parallel
1803 # - find all 'id' attribs and add them to the link map
1804 # - .. get their titles and store them into the titles map
1805 add_id_links_and_titles(files
, fixxref
.Links
)
1806 # - build glossary dict
1807 build_glossary(files
)
1808 logging
.warning("5: %7.3lf: extract tables", timer() - _t
)
1810 # == Output phase ==
1811 # the next two step could be done in parllel
1813 # 6) create a xxx.devhelp2 file
1815 create_devhelp2(out_dir
, module
, tree
.getroot(), files
)
1816 logging
.warning("6: %7.3lf: create devhelp2", timer() - _t
)
1818 # 7) iterate the tree and output files
1820 # TODO: can be done in parallel, figure out why this is not faster
1821 # from multiprocessing.pool import Pool
1822 # with Pool(4) as p:
1823 # p.apply_async(convert, args=(out_dir, module, files))
1824 # from multiprocessing.pool import ThreadPool
1825 # with ThreadPool(4) as p:
1826 # p.apply_async(convert, args=(out_dir, module, files))
1828 convert(out_dir
, module
, files
, node
, src_lang
)
1829 logging
.warning("7: %7.3lf: create html", timer() - _t
)
1831 # 8) copy assets over
1833 paths
= set(paths
+ [os
.getcwd()])
1835 logging
.info('trying %s in %s', a
, str(paths
))
1839 shutil
.copy(os
.path
.join(p
, a
), out_dir
)
1841 except FileNotFoundError
:
1844 logging
.warning('file %s not found in path (did you add --path?)', a
)
1845 logging
.warning("8: %7.3lf: copy assets", timer() - _t
)
1849 logging
.info('options: %s', str(options
.__dict
__))
1850 module
= options
.args
[0]
1851 document
= options
.args
[1]
1853 # TODO: rename to 'html' later on
1854 # - right now in mkhtml, the dir is created by the Makefile and mkhtml
1855 # outputs into the working directory
1856 out_dir
= os
.path
.join(os
.path
.dirname(document
), 'db2html')
1859 except OSError as e
:
1860 if e
.errno
!= errno
.EEXIST
:
1863 sys
.exit(main(module
, document
, out_dir
, options
.uninstalled
, options
.src_lang
,