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 - inside 'footnote' one can have many tags, we only handle 'para'/'simpara'
40 - inside 'inlinemediaobject'/'mediaobject' a 'textobject' becomes the 'alt'
41 attr on the <img> tag of the 'imageobject'
42 - handle 'label' attributes on part/chapter/section-types
43 - the titles will have a generated prefix, such as 'Part I:'
44 - in the toc it would only be only the label: 'I.'
45 - 'linkend' seems to add a 'title' attr to 'a' if the targe has a title.
46 - check each docbook tag if it can contain #PCDATA, if not don't check for
48 - consider some perf-warnings flag
49 - see 'No "id" attribute on'
52 - minify html: https://pypi.python.org/pypi/htmlmin/
55 sudo pip3 install anytree lxml pygments
59 ../../../gtkdoc-mkhtml2 tester tester-docs.xml
60 xdg-open db2html/index.html
65 rm html-build.stamp; time make html-build.stamp
75 from anytree
import Node
, PreOrderIter
76 from copy
import deepcopy
78 from lxml
import etree
79 from pygments
import highlight
80 from pygments
.lexers
import CLexer
81 from pygments
.formatters
import HtmlFormatter
83 from . import config
, fixxref
86 # lazily constructed lexer cache
90 HTML_FORMATTER
= HtmlFormatter(nowrap
=True)
93 class ChunkParams(object):
94 def __init__(self
, prefix
, parent
=None, min_idx
=0):
97 self
.min_idx
= min_idx
101 DONT_CHUNK
= float('inf')
102 # docbook-xsl defines the chunk tags here.
103 # http://www.sagehill.net/docbookxsl/Chunking.html#GeneratedFilenames
104 # https://github.com/oreillymedia/HTMLBook/blob/master/htmlbook-xsl/chunk.xsl#L33
105 # If not defined, we can just create an example without an 'id' attr and see
108 # For toc levels see http://www.sagehill.net/docbookxsl/TOCcontrol.html
109 # TODO: this list has also a flag that controls wheter we add the
110 # 'Table of Contents' heading in convert_chunk_with_toc()
112 'appendix': ChunkParams('app', 'book'),
113 'book': ChunkParams('bk'),
114 'chapter': ChunkParams('ch', 'book'),
115 'glossary': ChunkParams('go', 'book'),
116 'index': ChunkParams('ix', 'book'),
117 'part': ChunkParams('pt', 'book'),
118 'preface': ChunkParams('pr', 'book'),
119 'refentry': ChunkParams('re', 'book'),
120 'reference': ChunkParams('rn', 'book'),
121 'sect1': ChunkParams('s', 'chapter', 1),
122 'section': ChunkParams('s', 'chapter', 1),
123 'sect2': ChunkParams('s', 'sect1', DONT_CHUNK
),
124 'sect3': ChunkParams('s', 'sect2', DONT_CHUNK
),
125 'sect4': ChunkParams('s', 'sect3', DONT_CHUNK
),
126 'sect5': ChunkParams('s', 'sect4', DONT_CHUNK
),
128 # TAGS we don't support:
129 # 'article', 'bibliography', 'colophon', 'set', 'setindex'
132 '_': (etree
.XPath('./title'), None),
133 'book': (etree
.XPath('./bookinfo/title'), None),
135 etree
.XPath('./refmeta/refentrytitle'),
136 etree
.XPath('./refnamediv/refpurpose')
140 ID_XPATH
= etree
.XPath('//@id')
142 GLOSSENTRY_XPATH
= etree
.XPath('//glossentry')
148 def gen_chunk_name(node
, chunk_params
):
149 """Generate a chunk file name
151 This is either based on the id or on the position in the doc. In the latter
152 case it uses a prefix from CHUNK_PARAMS and a sequence number for each chunk
155 if 'id' in node
.attrib
:
156 return node
.attrib
['id']
158 name
= ('%s%02d' % (chunk_params
.prefix
, chunk_params
.idx
))
159 chunk_params
.idx
+= 1
161 # handle parents to make names of nested tags like in docbook
162 # - we only need to prepend the parent if there are > 1 of them in the
163 # xml. None, the parents we have are not sufficient, e.g. 'index' can
164 # be in 'book' or 'part' or ... Maybe we can track the chunk_parents
165 # when we chunk explicitly and on each level maintain the 'idx'
166 # while chunk_params.parent:
167 # parent = chunk_params.parent
168 # if parent not in CHUNK_PARAMS:
170 # chunk_params = CHUNK_PARAMS[parent]
171 # name = ('%s%02d' % (chunk_params.prefix, chunk_params.idx)) + name
173 logging
.info('Gen chunk name: "%s"', name
)
177 def get_chunk_titles(module
, node
):
179 if tag
not in TITLE_XPATHS
:
181 (title
, subtitle
) = TITLE_XPATHS
['_']
183 (title
, subtitle
) = TITLE_XPATHS
[tag
]
197 result
['title'] = ''.join(convert_title(ctx
, xml
))
198 if xml
.tag
!= 'title':
199 result
['title_tag'] = xml
.tag
201 result
['title_tag'] = tag
207 result
['subtitle'] = ''.join(convert_title(ctx
, xml
))
208 result
['subtitle_tag'] = xml
.tag
212 def chunk(xml_node
, module
, depth
=0, idx
=0, parent
=None):
215 The first time, we're called with parent=None and in that case we return
216 the new_node as the root of the tree. For each tree-node we generate a
217 filename and process the children.
220 chunk_params
= CHUNK_PARAMS
.get(tag
)
222 title_args
= get_chunk_titles(module
, xml_node
)
223 chunk_name
= gen_chunk_name(xml_node
, chunk_params
)
225 # check idx to handle 'sect1'/'section' special casing and title-only
227 if idx
>= chunk_params
.min_idx
:
228 logging
.info('chunk tag: "%s"[%d]', tag
, idx
)
230 # remove the xml-node from the parent
231 sub_tree
= etree
.ElementTree(deepcopy(xml_node
)).getroot()
232 xml_node
.getparent().remove(xml_node
)
235 parent
= Node(tag
, parent
=parent
, xml
=xml_node
, depth
=depth
,
236 filename
=chunk_name
+ '.html', anchor
=None,
239 parent
= Node(tag
, parent
=parent
, xml
=xml_node
, depth
=depth
,
240 filename
=parent
.filename
, anchor
='#' + chunk_name
,
245 for child
in xml_node
:
246 chunk(child
, module
, depth
, idx
, parent
)
247 if child
.tag
in CHUNK_PARAMS
:
253 def add_id_links(files
, links
):
255 chunk_name
= node
.filename
[:-5]
256 chunk_base
= node
.filename
+ '#'
257 for attr
in ID_XPATH(node
.xml
):
258 if attr
== chunk_name
:
259 links
[attr
] = node
.filename
261 links
[attr
] = chunk_base
+ attr
264 def build_glossary(files
):
266 if node
.xml
.tag
!= 'glossary':
268 for term
in GLOSSENTRY_XPATH(node
.xml
):
269 # TODO: there can be all kind of things in a glossary. This only supports
270 # what we commonly use
271 key
= etree
.tostring(term
.find('glossterm'), method
="text", encoding
=str).strip()
272 value
= etree
.tostring(term
.find('glossdef'), method
="text", encoding
=str).strip()
273 glossary
[key
] = value
274 # logging.debug('glosentry: %s:%s', key, value)
280 def convert_inner(ctx
, xml
, result
):
282 result
.extend(convert_tags
.get(child
.tag
, convert__unknown
)(ctx
, child
))
285 def convert_ignore(ctx
, xml
):
287 convert_inner(ctx
, xml
, result
)
291 def convert_skip(ctx
, xml
):
295 def append_text(text
, result
):
296 if text
and text
.strip():
297 result
.append(text
.replace('<', '<').replace('>', '>'))
303 def convert__unknown(ctx
, xml
):
304 # don't recurse on subchunks
305 if xml
.tag
in CHUNK_PARAMS
:
307 if isinstance(xml
, etree
._Comment
):
308 return ['<!-- ' + xml
.text
+ '-->\n']
311 if xml
.tag
not in missing_tags
:
312 logging
.warning('Add tag converter for "%s"', xml
.tag
)
313 missing_tags
[xml
.tag
] = True
314 result
= ['<!-- ' + xml
.tag
+ '-->\n']
315 convert_inner(ctx
, xml
, result
)
316 result
.append('<!-- /' + xml
.tag
+ '-->\n')
320 def convert_sect(ctx
, xml
, h_tag
, inner_func
=convert_inner
):
321 result
= ['<div class="%s">\n' % xml
.tag
]
322 title
= xml
.find('title')
323 if title
is not None:
324 if 'id' in xml
.attrib
:
325 result
.append('<a name="%s"></a>' % xml
.attrib
['id'])
326 result
.append('<%s>%s</%s>' % (h_tag
, title
.text
, h_tag
))
328 append_text(xml
.text
, result
)
329 inner_func(ctx
, xml
, result
)
330 result
.append('</div>')
331 append_text(xml
.tail
, result
)
335 def xml_get_title(ctx
, xml
):
336 title_tag
= xml
.find('title')
337 if title_tag
is not None:
338 return ''.join(convert_title(ctx
, title_tag
))
340 # TODO(ensonic): any way to get the file (inlcudes) too?
341 logging
.warning('%s: Expected title tag under "%s %s"', xml
.sourceline
, xml
.tag
, str(xml
.attrib
))
348 def convert_abstract(ctx
, xml
):
349 result
= ["""<div class="abstract">
350 <p class="title"><b>Abstract</b></p>"""]
351 append_text(xml
.text
, result
)
352 convert_inner(ctx
, xml
, result
)
353 result
.append('</div>')
354 append_text(xml
.tail
, result
)
358 def convert_acronym(ctx
, xml
):
360 title
= glossary
.get(key
, '')
361 # TODO: print a sensible warning if missing
362 result
= ['<acronym title="%s"><span class="acronym">%s</span></acronym>' % (title
, key
)]
364 result
.append(xml
.tail
)
368 def convert_anchor(ctx
, xml
):
369 return ['<a name="%s"></a>' % xml
.attrib
['id']]
372 def convert_bookinfo(ctx
, xml
):
373 result
= ['<div class="titlepage">']
374 convert_inner(ctx
, xml
, result
)
375 result
.append("""<hr>
378 result
.append(xml
.tail
)
382 def convert_blockquote(ctx
, xml
):
383 result
= ['<div class="blockquote">\n<blockquote class="blockquote">']
384 append_text(xml
.text
, result
)
385 convert_inner(ctx
, xml
, result
)
386 result
.append('</blockquote>\n</div>')
387 append_text(xml
.tail
, result
)
391 def convert_code(ctx
, xml
):
392 result
= ['<code class="%s">' % xml
.tag
]
393 append_text(xml
.text
, result
)
394 convert_inner(ctx
, xml
, result
)
395 result
.append('</code>')
396 append_text(xml
.tail
, result
)
400 def convert_colspec(ctx
, xml
):
404 result
.append(' class="%s"' % a
['colname'])
406 result
.append(' width="%s"' % a
['colwidth'])
408 # is in tgroup and there can be no 'text'
412 def convert_command(ctx
, xml
):
413 result
= ['<strong class="userinput"><code>']
414 append_text(xml
.text
, result
)
415 convert_inner(ctx
, xml
, result
)
416 result
.append('</code></strong>')
417 append_text(xml
.tail
, result
)
421 def convert_corpauthor(ctx
, xml
):
422 result
= ['<div><h3 class="corpauthor">\n']
423 append_text(xml
.text
, result
)
424 convert_inner(ctx
, xml
, result
)
425 result
.append('</h3></div>\n')
426 append_text(xml
.tail
, result
)
430 def convert_div(ctx
, xml
):
431 result
= ['<div class="%s">\n' % xml
.tag
]
432 append_text(xml
.text
, result
)
433 convert_inner(ctx
, xml
, result
)
434 result
.append('</div>')
435 append_text(xml
.tail
, result
)
439 def convert_emphasis(ctx
, xml
):
440 result
= ['<span class="emphasis"><em>']
441 append_text(xml
.text
, result
)
442 convert_inner(ctx
, xml
, result
)
443 result
.append('</em></span>')
444 append_text(xml
.tail
, result
)
448 def convert_em_class(ctx
, xml
):
449 result
= ['<em class="%s"><code>' % xml
.tag
]
450 append_text(xml
.text
, result
)
451 convert_inner(ctx
, xml
, result
)
452 result
.append('</code></em>')
453 append_text(xml
.tail
, result
)
457 def convert_entry(ctx
, xml
):
458 entry_type
= ctx
['table.entry']
459 result
= ['<' + entry_type
]
460 if 'role' in xml
.attrib
:
461 result
.append(' class="%s"' % xml
.attrib
['role'])
462 if 'morerows' in xml
.attrib
:
463 result
.append(' rowspan="%s"' % (1 + int(xml
.attrib
['morerows'])))
465 append_text(xml
.text
, result
)
466 convert_inner(ctx
, xml
, result
)
467 result
.append('</' + entry_type
+ '>')
468 append_text(xml
.tail
, result
)
472 def convert_footnote(ctx
, xml
):
473 footnotes
= ctx
.get('footnotes', [])
474 # footnotes idx is not per page, but per doc
479 # need a pair of ids for each footnote (docbook generates different ids)
480 this_id
= 'footnote-%d' % idx
481 that_id
= 'ftn.' + this_id
483 inner
= ['<div id="%s" class="footnote">' % that_id
]
484 inner
.append('<p><a href="#%s" class="para"><sup class="para">[%d] </sup></a>' % (
486 # TODO(ensonic): this can contain all kind of tags, if we convert them we'll
487 # get double nested paras :/.
488 # convert_inner(ctx, xml, inner)
489 para
= xml
.find('para')
491 para
= xml
.find('simpara')
493 inner
.append(para
.text
)
495 logging
.warning('%s: Unhandled footnote content: %s', xml
.sourceline
,
496 etree
.tostring(xml
, method
="text", encoding
=str).strip())
497 inner
.append('</p></div>')
498 footnotes
.append(inner
)
499 ctx
['footnotes'] = footnotes
500 return ['<a href="#%s" class="footnote" name="%s"><sup class="footnote">[%s]</sup></a>' % (
501 that_id
, this_id
, idx
)]
504 def convert_formalpara(ctx
, xml
):
506 title_tag
= xml
.find('title')
507 result
= ['<p><b>%s</b>' % title_tag
.text
]
508 para_tag
= xml
.find('para')
509 append_text(para_tag
.text
, result
)
510 convert_inner(ctx
, para_tag
, result
)
511 append_text(para_tag
.tail
, result
)
512 result
.append('</p>')
513 append_text(xml
.tail
, result
)
517 def convert_glossdef(ctx
, xml
):
518 result
= ['<dd class="glossdef">']
519 convert_inner(ctx
, xml
, result
)
520 result
.append('</dd>\n')
524 def convert_glossdiv(ctx
, xml
):
525 title_tag
= xml
.find('title')
526 title
= title_tag
.text
527 xml
.remove(title_tag
)
529 '<a name="gls%s"></a><h3 class="title">%s</h3>' % (title
, title
)
531 convert_inner(ctx
, xml
, result
)
535 def convert_glossentry(ctx
, xml
):
537 convert_inner(ctx
, xml
, result
)
541 def convert_glossterm(ctx
, xml
):
544 anchor
= xml
.find('anchor')
545 if anchor
is not None:
546 glossid
= anchor
.attrib
.get('id', '')
547 text
+= anchor
.tail
or ''
548 text
+= xml
.text
or ''
550 glossid
= 'glossterm-' + text
552 '<dt><span class="glossterm"><a name="%s"></a>%s</span></dt>' % (
557 def convert_imageobject(ctx
, xml
):
558 imagedata
= xml
.find('imagedata')
559 if imagedata
is not None:
560 # TODO(ensonic): warn on missing fileref attr?
561 return ['<img src="%s">' % imagedata
.attrib
.get('fileref', '')]
566 def convert_indexdiv(ctx
, xml
):
567 title_tag
= xml
.find('title')
568 title
= title_tag
.text
569 xml
.remove(title_tag
)
571 '<a name="idx%s"></a><h3 class="title">%s</h3>' % (title
, title
)
573 convert_inner(ctx
, xml
, result
)
577 def convert_informaltable(ctx
, xml
):
578 result
= ['<div class="informaltable"><table class="informaltable"']
580 if 'pgwide' in a
and a
['pgwide'] == '1':
581 result
.append(' width="100%"')
582 if 'frame' in a
and a
['frame'] == 'none':
583 result
.append(' border="0"')
585 convert_inner(ctx
, xml
, result
)
586 result
.append('</table></div>')
588 result
.append(xml
.tail
)
592 def convert_inlinegraphic(ctx
, xml
):
593 # TODO(ensonic): warn on missing fileref attr?
594 return ['<img src="%s">' % xml
.attrib
.get('fileref', '')]
597 def convert_itemizedlist(ctx
, xml
):
598 result
= ['<div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; ">']
599 convert_inner(ctx
, xml
, result
)
600 result
.append('</ul></div>')
602 result
.append(xml
.tail
)
606 def convert_link(ctx
, xml
):
607 linkend
= xml
.attrib
['linkend']
608 if linkend
in fixxref
.NoLinks
:
613 append_text(xml
.text
, link_text
)
614 convert_inner(ctx
, xml
, link_text
)
615 # TODO: fixxref does some weird checks in xml.text
616 result
= [fixxref
.MakeXRef(ctx
['module'], '', 0, linkend
, ''.join(link_text
))]
618 append_text(xml
.text
, result
)
619 convert_inner(ctx
, xml
, result
)
620 append_text(xml
.tail
, result
)
624 def convert_listitem(ctx
, xml
):
625 result
= ['<li class="listitem">']
626 convert_inner(ctx
, xml
, result
)
627 result
.append('</li>')
628 # is in itemizedlist and there can be no 'text'
632 def convert_literallayout(ctx
, xml
):
633 result
= ['<div class="literallayout"><p><br>\n']
634 append_text(xml
.text
, result
)
635 convert_inner(ctx
, xml
, result
)
636 result
.append('</p></div>')
637 append_text(xml
.tail
, result
)
641 def convert_orderedlist(ctx
, xml
):
642 result
= ['<div class="orderedlist"><ol class="orderedlist" type="1">']
643 convert_inner(ctx
, xml
, result
)
644 result
.append('</ol></div>')
645 append_text(xml
.tail
, result
)
649 def convert_para(ctx
, xml
):
651 if 'id' in xml
.attrib
:
652 result
.append('<a name="%s"></a>' % xml
.attrib
['id'])
653 if 'role' in xml
.attrib
:
654 result
.append('<p class="%s">' % xml
.attrib
['role'])
657 append_text(xml
.text
, result
)
658 convert_inner(ctx
, xml
, result
)
659 result
.append('</p>')
660 append_text(xml
.tail
, result
)
664 def convert_para_like(ctx
, xml
):
666 if 'id' in xml
.attrib
:
667 result
.append('<a name="%s"></a>' % xml
.attrib
['id'])
668 result
.append('<p class="%s">' % xml
.tag
)
669 append_text(xml
.text
, result
)
670 convert_inner(ctx
, xml
, result
)
671 result
.append('</p>')
672 append_text(xml
.tail
, result
)
676 def convert_phrase(ctx
, xml
):
678 if 'role' in xml
.attrib
:
679 result
.append(' class="%s">' % xml
.attrib
['role'])
682 append_text(xml
.text
, result
)
683 convert_inner(ctx
, xml
, result
)
684 result
.append('</span>')
685 append_text(xml
.tail
, result
)
689 def convert_primaryie(ctx
, xml
):
691 convert_inner(ctx
, xml
, result
)
692 result
.append('\n</dt>\n<dd></dd>\n')
696 def convert_pre(ctx
, xml
):
697 result
= ['<pre class="%s">\n' % xml
.tag
]
698 append_text(xml
.text
, result
)
699 convert_inner(ctx
, xml
, result
)
700 result
.append('</pre>')
701 append_text(xml
.tail
, result
)
705 def convert_programlisting(ctx
, xml
):
707 if xml
.attrib
.get('role', '') == 'example':
709 lang
= xml
.attrib
.get('language', 'c').lower()
710 if lang
not in LEXERS
:
711 LEXERS
[lang
] = get_lexer_by_name(lang
)
712 lexer
= LEXERS
.get(lang
, None)
714 highlighted
= highlight(xml
.text
, lexer
, HTML_FORMATTER
)
716 # we do own line-numbering
717 line_count
= highlighted
.count('\n')
718 source_lines
= '\n'.join([str(i
) for i
in range(1, line_count
+ 1)])
719 result
.append("""<table class="listing_frame" border="0" cellpadding="0" cellspacing="0">
722 <td class="listing_lines" align="right"><pre>%s</pre></td>
723 <td class="listing_code"><pre class="programlisting">%s</pre></td>
727 """ % (source_lines
, highlighted
))
729 logging
.warn('No pygments lexer for language="%s"', lang
)
730 result
.append('<pre class="programlisting">')
731 result
.append(xml
.text
)
732 result
.append('</pre>')
734 result
.append('<pre class="programlisting">')
735 append_text(xml
.text
, result
)
736 convert_inner(ctx
, xml
, result
)
737 result
.append('</pre>')
738 append_text(xml
.tail
, result
)
742 def convert_quote(ctx
, xml
):
743 result
= ['<span class="quote">"<span class="quote">']
744 append_text(xml
.text
, result
)
745 convert_inner(ctx
, xml
, result
)
746 result
.append('</span>"</span>')
747 append_text(xml
.tail
, result
)
751 def convert_refsect1(ctx
, xml
):
752 # Add a divider between two consequitive refsect2
753 def convert_inner(ctx
, xml
, result
):
756 if child
.tag
== 'refsect2' and prev
is not None and prev
.tag
== child
.tag
:
757 result
.append('<hr>\n')
758 result
.extend(convert_tags
.get(child
.tag
, convert__unknown
)(ctx
, child
))
760 return convert_sect(ctx
, xml
, 'h2', convert_inner
)
763 def convert_refsect2(ctx
, xml
):
764 return convert_sect(ctx
, xml
, 'h3')
767 def convert_refsect3(ctx
, xml
):
768 return convert_sect(ctx
, xml
, 'h4')
771 def convert_row(ctx
, xml
):
773 convert_inner(ctx
, xml
, result
)
774 result
.append('</tr>\n')
778 def convert_sect1_tag(ctx
, xml
):
779 return convert_sect(ctx
, xml
, 'h2')
782 def convert_sect2(ctx
, xml
):
783 return convert_sect(ctx
, xml
, 'h3')
786 def convert_sect3(ctx
, xml
):
787 return convert_sect(ctx
, xml
, 'h4')
790 def convert_simpara(ctx
, xml
):
792 append_text(xml
.text
, result
)
793 result
.append('</p>')
794 append_text(xml
.tail
, result
)
798 def convert_span(ctx
, xml
):
799 result
= ['<span class="%s">' % xml
.tag
]
800 append_text(xml
.text
, result
)
801 convert_inner(ctx
, xml
, result
)
802 result
.append('</span>')
803 append_text(xml
.tail
, result
)
807 def convert_table(ctx
, xml
):
808 result
= ['<div class="table">']
809 if 'id' in xml
.attrib
:
810 result
.append('<a name="%s"></a>' % xml
.attrib
['id'])
811 title_tag
= xml
.find('title')
812 if title_tag
is not None:
813 result
.append('<p class="title"><b>')
814 # TODO(ensonic): Add a 'Table X. ' prefix, needs a table counter
815 result
.extend(convert_title(ctx
, title_tag
))
816 result
.append('</b></p>')
817 xml
.remove(title_tag
)
818 result
.append('<div class="table-contents"><table class="table" summary="g_object_new" border="1">')
820 convert_inner(ctx
, xml
, result
)
822 result
.append('</table></div></div>')
823 append_text(xml
.tail
, result
)
827 def convert_tbody(ctx
, xml
):
829 ctx
['table.entry'] = 'td'
830 convert_inner(ctx
, xml
, result
)
831 result
.append('</tbody>')
832 # is in tgroup and there can be no 'text'
836 def convert_tgroup(ctx
, xml
):
837 # tgroup does not expand to anything, but the nested colspecs need to
838 # be put into a colgroup
839 cols
= xml
.findall('colspec')
842 result
.append('<colgroup>\n')
844 result
.extend(convert_colspec(ctx
, col
))
846 result
.append('</colgroup>\n')
847 convert_inner(ctx
, xml
, result
)
848 # is in informaltable and there can be no 'text'
852 def convert_thead(ctx
, xml
):
854 ctx
['table.entry'] = 'th'
855 convert_inner(ctx
, xml
, result
)
856 result
.append('</thead>')
857 # is in tgroup and there can be no 'text'
861 def convert_title(ctx
, xml
):
862 # This is always called from some context
864 append_text(xml
.text
, result
)
865 convert_inner(ctx
, xml
, result
)
866 append_text(xml
.tail
, result
)
870 def convert_ulink(ctx
, xml
):
871 result
= ['<a class="%s" href="%s">%s</a>' % (xml
.tag
, xml
.attrib
['url'], xml
.text
)]
873 result
.append(xml
.tail
)
877 def convert_userinput(ctx
, xml
):
878 result
= ['<span class="command"><strong>']
879 append_text(xml
.text
, result
)
880 convert_inner(ctx
, xml
, result
)
881 result
.append('</strong></span>')
882 append_text(xml
.tail
, result
)
886 def convert_variablelist(ctx
, xml
):
887 result
= ["""<div class="variablelist"><table border="0" class="variablelist">
889 <col align="left" valign="top">
893 convert_inner(ctx
, xml
, result
)
894 result
.append("""</tbody>
899 def convert_varlistentry(ctx
, xml
):
902 result
.append('<td><p>')
903 term
= xml
.find('term')
904 result
.extend(convert_span(ctx
, term
))
905 result
.append('</p></td>')
907 result
.append('<td>')
908 listitem
= xml
.find('listitem')
909 convert_inner(ctx
, listitem
, result
)
910 result
.append('</td>')
912 result
.append('<tr>')
916 # TODO(ensonic): turn into class with converters as functions and ctx as self
918 'abstract': convert_abstract
,
919 'acronym': convert_acronym
,
920 'anchor': convert_anchor
,
921 'application': convert_span
,
922 'bookinfo': convert_bookinfo
,
923 'blockquote': convert_blockquote
,
924 'caption': convert_div
,
925 'code': convert_code
,
926 'colspec': convert_colspec
,
927 'constant': convert_code
,
928 'command': convert_command
,
929 'corpauthor': convert_corpauthor
,
930 'emphasis': convert_emphasis
,
931 'entry': convert_entry
,
932 'envar': convert_code
,
933 'footnote': convert_footnote
,
934 'filename': convert_code
,
935 'formalpara': convert_formalpara
,
936 'function': convert_code
,
937 'glossdef': convert_glossdef
,
938 'glossdiv': convert_glossdiv
,
939 'glossentry': convert_glossentry
,
940 'glossterm': convert_glossterm
,
941 'imageobject': convert_imageobject
,
942 'indexdiv': convert_indexdiv
,
943 'indexentry': convert_ignore
,
944 'indexterm': convert_skip
,
945 'informalexample': convert_div
,
946 'informaltable': convert_informaltable
,
947 'inlinegraphic': convert_inlinegraphic
,
948 'inlinemediaobject': convert_span
,
949 'itemizedlist': convert_itemizedlist
,
950 'legalnotice': convert_div
,
951 'link': convert_link
,
952 'listitem': convert_listitem
,
953 'literal': convert_code
,
954 'literallayout': convert_literallayout
,
955 'mediaobject': convert_div
,
957 'option': convert_code
,
958 'orderedlist': convert_orderedlist
,
959 'para': convert_para
,
960 'partintro': convert_div
,
961 'parameter': convert_em_class
,
962 'phrase': convert_phrase
,
963 'primaryie': convert_primaryie
,
964 'programlisting': convert_programlisting
,
965 'quote': convert_quote
,
966 'releaseinfo': convert_para_like
,
967 'refsect1': convert_refsect1
,
968 'refsect2': convert_refsect2
,
969 'refsect3': convert_refsect3
,
970 'replaceable': convert_em_class
,
971 'returnvalue': convert_span
,
973 'screen': convert_pre
,
974 'sect1': convert_sect1_tag
,
975 'sect2': convert_sect2
,
976 'sect3': convert_sect3
,
977 'simpara': convert_simpara
,
978 'structfield': convert_em_class
,
979 'structname': convert_span
,
980 'synopsis': convert_pre
,
981 'symbol': convert_span
,
982 'table': convert_table
,
983 'tbody': convert_tbody
,
984 'term': convert_span
,
985 'tgroup': convert_tgroup
,
986 'thead': convert_thead
,
987 'type': convert_span
,
988 'ulink': convert_ulink
,
989 'userinput': convert_userinput
,
990 'varname': convert_code
,
991 'variablelist': convert_variablelist
,
992 'varlistentry': convert_varlistentry
,
993 'warning': convert_div
,
998 HTML_HEADER
= """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
1001 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
1003 %s<link rel="stylesheet" href="style.css" type="text/css">
1005 <body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF">
1009 def generate_head_links(ctx
):
1012 '<link rel="home" href="%s" title="%s">\n' % (n
.filename
, n
.title
)
1016 result
.append('<link rel="up" href="%s" title="%s">\n' % (n
.filename
, n
.title
))
1017 if 'nav_prev' in ctx
:
1019 result
.append('<link rel="prev" href="%s" title="%s">\n' % (n
.filename
, n
.title
))
1020 if 'nav_next' in ctx
:
1022 result
.append('<link rel="next" href="%s" title="%s">\n' % (n
.filename
, n
.title
))
1023 return ''.join(result
)
1026 def generate_nav_links(ctx
):
1029 '<td><a accesskey="h" href="%s"><img src="home.png" width="16" height="16" border="0" alt="Home"></a></td>' % n
.filename
1034 '<td><a accesskey="u" href="%s"><img src="up.png" width="16" height="16" border="0" alt="Up"></a></td>' % n
.filename
)
1036 result
.append('<td><img src="up-insensitive.png" width="16" height="16" border="0"></td>')
1037 if 'nav_prev' in ctx
:
1040 '<td><a accesskey="p" href="%s"><img src="left.png" width="16" height="16" border="0" alt="Prev"></a></td>' % n
.filename
)
1042 result
.append('<td><img src="left-insensitive.png" width="16" height="16" border="0"></td>')
1043 if 'nav_next' in ctx
:
1046 '<td><a accesskey="n" href="%s"><img src="right.png" width="16" height="16" border="0" alt="Next"></a></td>' % n
.filename
)
1048 result
.append('<td><img src="right-insensitive.png" width="16" height="16" border="0"></td>')
1050 return ''.join(result
)
1053 def generate_toc(ctx
, node
):
1055 for c
in node
.children
:
1056 # TODO: urlencode the filename: urllib.parse.quote_plus()
1060 result
.append('<dt><span class="%s"><a href="%s">%s</a></span>\n' % (
1061 c
.title_tag
, link
, c
.title
))
1063 result
.append('<span class="%s"> — %s</span>' % (c
.subtitle_tag
, c
.subtitle
))
1064 result
.append('</dt>\n')
1066 result
.append('<dd><dl>')
1067 result
.extend(generate_toc(ctx
, c
))
1068 result
.append('</dl></dd>')
1072 def generate_basic_nav(ctx
):
1073 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1074 <tr valign="middle">
1075 <td width="100%%" align="left" class="shortcuts"></td>
1079 """ % generate_nav_links(ctx
)
1082 def generate_alpha_nav(ctx
, divs
, prefix
, span_id
):
1085 title
= xml_get_title(ctx
, s
)
1086 ix_nav
.append('<a class="shortcut" href="#%s%s">%s</a>' % (prefix
, title
, title
))
1088 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5">
1089 <tr valign="middle">
1090 <td width="100%%" align="left" class="shortcuts">
1098 """ % (span_id
, '\n<span class="dim">|</span>\n'.join(ix_nav
), generate_nav_links(ctx
))
1101 def generate_refentry_nav(ctx
, refsect1s
, result
):
1102 result
.append("""<table class="navigation" id="top" width="100%" cellpadding="2" cellspacing="5">
1103 <tr valign="middle">
1104 <td width="100%" align="left" class="shortcuts">
1105 <a href="#" class="shortcut">Top</a>""")
1108 # don't list TOC sections (role="xxx_proto")
1109 if s
.attrib
.get('role', '').endswith("_proto"):
1111 # skip section without 'id' attrs
1112 if 'id' not in s
.attrib
:
1115 ref_id
= s
.attrib
['id']
1116 # skip foreign sections
1117 if '.' not in ref_id
:
1120 title
= xml_get_title(ctx
, s
)
1121 span_id
= ref_id
.split('.')[1].replace('-', '_')
1125 Â Â <span class="dim">|</span>Â
1126 <a href="#%s" class="shortcut">%s</a>
1128 """ % (span_id
, ref_id
, title
))
1134 """ % generate_nav_links(ctx
))
1137 def generate_footer(ctx
):
1139 if 'footnotes' in ctx
:
1140 result
.append("""<div class="footnotes">\n
1141 <br><hr style="width:100; text-align:left;margin-left: 0">
1143 for f
in ctx
['footnotes']:
1145 result
.append('</div>\n')
1151 node_id
= xml
.attrib
.get('id', None)
1155 logging
.info('%d: No "id" attribute on "%s", generating one',
1156 xml
.sourceline
, xml
.tag
)
1158 # Generate the 'id'. We need to walk up the xml-tree and check the positions
1160 parent
= xml
.getparent()
1161 while parent
is not None:
1162 children
= parent
.getchildren()
1163 ix
.insert(0, str(children
.index(xml
) + 1))
1165 parent
= xml
.getparent()
1166 # logging.warning('%s: id indexes: %s', node.filename, str(ix))
1167 return 'id-1.' + '.'.join(ix
)
1170 def convert_chunk_with_toc(ctx
, div_class
, title_tag
):
1173 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1174 generate_basic_nav(ctx
),
1175 '<div class="%s">' % div_class
,
1177 title
= node
.xml
.find('title')
1178 if title
is not None:
1180 <div class="titlepage">
1181 <%s class="title"><a name="%s"></a>%s</%s>
1183 title_tag
, get_id(node
), title
.text
, title_tag
))
1184 node
.xml
.remove(title
)
1186 toc
= generate_toc(ctx
, node
)
1188 # TODO: not all docbook page types use this extra heading
1189 result
.append("""<p><b>Table of Contents</b></p>
1194 result
.append("""</dl>
1197 convert_inner(ctx
, node
.xml
, result
)
1198 result
.extend(generate_footer(ctx
))
1199 result
.append("""</div>
1208 def convert_book(ctx
):
1211 HTML_HEADER
% (node
.title
, generate_head_links(ctx
)),
1212 """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="0">
1213 <tr><th valign="middle"><p class="title">%s</p></th></tr>
1218 bookinfo
= node
.xml
.findall('bookinfo')[0]
1219 # we already used the title
1220 title
= bookinfo
.find('title')
1221 if title
is not None:
1222 bookinfo
.remove(title
)
1223 result
.extend(convert_bookinfo(ctx
, bookinfo
))
1224 result
.append("""<div class="toc">
1227 result
.extend(generate_toc(ctx
, node
.root
))
1228 result
.append("""</dl>
1231 result
.extend(generate_footer(ctx
))
1232 result
.append("""</div>
1238 def convert_chapter(ctx
):
1239 return convert_chunk_with_toc(ctx
, 'chapter', 'h2')
1242 def convert_glossary(ctx
):
1244 glossdivs
= node
.xml
.findall('glossdiv')
1247 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1248 generate_alpha_nav(ctx
, glossdivs
, 'gls', 'glossary'),
1249 """<div class="glossary">
1250 <div class="titlepage"><h%1d class="title">
1251 <a name="%s"></a>%s</h%1d>
1252 </div>""" % (node
.depth
, get_id(node
), node
.title
, node
.depth
)
1255 result
.extend(convert_glossdiv(ctx
, i
))
1256 result
.extend(generate_footer(ctx
))
1257 result
.append("""</div>
1263 def convert_index(ctx
):
1265 # Get all indexdivs under indexdiv
1266 indexdivs
= node
.xml
.find('indexdiv').findall('indexdiv')
1269 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1270 generate_alpha_nav(ctx
, indexdivs
, 'idx', 'index'),
1271 """<div class="index">
1272 <div class="titlepage"><h%1d class="title">
1273 <a name="%s"></a>%s</h%1d>
1274 </div>""" % (node
.depth
, get_id(node
), node
.title
, node
.depth
)
1277 result
.extend(convert_indexdiv(ctx
, i
))
1278 result
.extend(generate_footer(ctx
))
1279 result
.append("""</div>
1285 def convert_part(ctx
):
1286 return convert_chunk_with_toc(ctx
, 'part', 'h1')
1289 def convert_preface(ctx
):
1292 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
)),
1293 generate_basic_nav(ctx
),
1294 '<div class="preface">'
1296 title
= node
.xml
.find('title')
1297 if title
is not None:
1299 <div class="titlepage">
1300 <h2 class="title"><a name="%s"></a>%s</h2>
1301 </div>""" % (get_id(node
), title
.text
))
1302 node
.xml
.remove(title
)
1303 convert_inner(ctx
, node
.xml
, result
)
1304 result
.extend(generate_footer(ctx
))
1305 result
.append("""</div>
1311 def convert_reference(ctx
):
1312 return convert_chunk_with_toc(ctx
, 'reference', 'h1')
1315 def convert_refentry(ctx
):
1317 node_id
= get_id(node
)
1318 refsect1s
= node
.xml
.findall('refsect1')
1321 refmeta
= node
.xml
.find('refmeta')
1322 if refmeta
is not None:
1323 refmiscinfo
= refmeta
.find('refmiscinfo')
1324 if refmiscinfo
is not None:
1325 inlinegraphic
= refmiscinfo
.find('inlinegraphic')
1326 if inlinegraphic
is not None:
1327 gallery
= ''.join(convert_inlinegraphic(ctx
, inlinegraphic
))
1330 HTML_HEADER
% (node
.title
+ ": " + node
.root
.title
, generate_head_links(ctx
))
1332 generate_refentry_nav(ctx
, refsect1s
, result
)
1334 <div class="refentry">
1336 <div class="refnamediv">
1337 <table width="100%%"><tr>
1339 <h2><span class="refentrytitle"><a name="%s.top_of_page"></a>%s</span></h2>
1342 <td class="gallery_image" valign="top" align="right">%s</td>
1345 """ % (node_id
, node_id
, node
.title
, node
.title
, node
.subtitle
, gallery
))
1348 result
.extend(convert_refsect1(ctx
, s
))
1349 result
.extend(generate_footer(ctx
))
1350 result
.append("""</div>
1356 def convert_sect1(ctx
):
1357 return convert_chunk_with_toc(ctx
, 'sect1', 'h2')
1360 # TODO(ensonic): turn into class with converters as functions and ctx as self
1362 'book': convert_book
,
1363 'chapter': convert_chapter
,
1364 'glossary': convert_glossary
,
1365 'index': convert_index
,
1366 'part': convert_part
,
1367 'preface': convert_preface
,
1368 'reference': convert_reference
,
1369 'refentry': convert_refentry
,
1370 'sect1': convert_sect1
,
1374 def generate_nav_nodes(files
, node
):
1376 'nav_home': node
.root
,
1378 # nav params: up, prev, next
1380 nav
['nav_up'] = node
.parent
1381 ix
= files
.index(node
)
1383 nav
['nav_prev'] = files
[ix
- 1]
1384 if ix
< len(files
) - 1:
1385 nav
['nav_next'] = files
[ix
+ 1]
1389 def convert(out_dir
, module
, files
, node
):
1390 """Convert the docbook chunks to a html file.
1393 out_dir: already created output dir
1394 files: list of nodes in the tree in pre-order
1395 node: current tree node
1398 logging
.info('Writing: %s', node
.filename
)
1399 with
open(os
.path
.join(out_dir
, node
.filename
), 'wt',
1400 newline
='\n', encoding
='utf-8') as html
:
1406 ctx
.update(generate_nav_nodes(files
, node
))
1408 if node
.name
in convert_chunks
:
1409 for line
in convert_chunks
[node
.name
](ctx
):
1412 logging
.warning('Add converter/template for "%s"', node
.name
)
1415 def create_devhelp2_toc(node
):
1417 for c
in node
.children
:
1419 result
.append('<sub name="%s" link="%s">\n' % (c
.title
, c
.filename
))
1420 result
.extend(create_devhelp2_toc(c
))
1421 result
.append('</sub>\n')
1423 result
.append('<sub name="%s" link="%s"/>\n' % (c
.title
, c
.filename
))
1427 def create_devhelp2_condition_attribs(node
):
1428 if 'condition' in node
.attrib
:
1429 # condition -> since, deprecated, ... (separated with '|')
1430 cond
= node
.attrib
['condition'].replace('"', '"').split('|')
1434 keywords
.append('{}="{}"'.format(*c
.split(':', 1)))
1436 # deprecated can have no description
1437 keywords
.append('{}="{}"'.format(c
, ''))
1438 return ' ' + ' '.join(keywords
)
1443 def create_devhelp2_refsect2_keyword(node
, base_link
):
1444 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1445 node
.attrib
['role'], xml_get_title({}, node
), base_link
+ node
.attrib
['id'],
1446 create_devhelp2_condition_attribs(node
))
1449 def create_devhelp2_refsect3_keyword(node
, base_link
, title
, name
):
1450 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % (
1451 node
.attrib
['role'], title
, base_link
+ name
,
1452 create_devhelp2_condition_attribs(node
))
1455 def create_devhelp2(out_dir
, module
, xml
, files
):
1456 with
open(os
.path
.join(out_dir
, module
+ '.devhelp2'), 'wt',
1457 newline
='\n', encoding
='utf-8') as idx
:
1458 bookinfo_nodes
= xml
.xpath('/book/bookinfo')
1460 if bookinfo_nodes
is not None:
1461 bookinfo
= bookinfo_nodes
[0]
1462 title
= bookinfo
.xpath('./title/text()')[0]
1463 online_url
= bookinfo
.xpath('./releaseinfo/ulink[@role="online-location"]/@url')[0]
1464 # TODO: support author too (see devhelp2.xsl)
1465 # TODO: fixxref uses '--src-lang' to set the language
1467 """<?xml version="1.0" encoding="utf-8" standalone="no"?>
1468 <book xmlns="http://www.devhelp.net/book" title="%s" link="index.html" author="" name="%s" version="2" language="c" online="%s">
1470 """ % (title
, module
, online_url
)
1473 result
.extend(create_devhelp2_toc(files
[0].root
))
1474 result
.append(""" </chapters>
1477 # keywords from all refsect2 and refsect3
1478 refsect2
= etree
.XPath('//refsect2[@role]')
1479 refsect3_enum
= etree
.XPath('refsect3[@role="enum_members"]/informaltable/tgroup/tbody/row[@role="constant"]')
1480 refsect3_enum_details
= etree
.XPath('entry[@role="enum_member_name"]/para')
1481 refsect3_struct
= etree
.XPath('refsect3[@role="struct_members"]/informaltable/tgroup/tbody/row[@role="member"]')
1482 refsect3_struct_details
= etree
.XPath('entry[@role="struct_member_name"]/para/structfield')
1484 base_link
= node
.filename
+ '#'
1485 refsect2_nodes
= refsect2(node
.xml
)
1486 for refsect2_node
in refsect2_nodes
:
1487 result
.append(create_devhelp2_refsect2_keyword(refsect2_node
, base_link
))
1488 refsect3_nodes
= refsect3_enum(refsect2_node
)
1489 for refsect3_node
in refsect3_nodes
:
1490 details_node
= refsect3_enum_details(refsect3_node
)[0]
1491 name
= details_node
.attrib
['id']
1492 result
.append(create_devhelp2_refsect3_keyword(refsect3_node
, base_link
, details_node
.text
, name
))
1493 refsect3_nodes
= refsect3_struct(refsect2_node
)
1494 for refsect3_node
in refsect3_nodes
:
1495 details_node
= refsect3_struct_details(refsect3_node
)[0]
1496 name
= details_node
.attrib
['id']
1497 result
.append(create_devhelp2_refsect3_keyword(refsect3_node
, base_link
, name
, name
))
1499 result
.append(""" </functions>
1506 def get_dirs(uninstalled
):
1508 # this does not work from buiddir!=srcdir
1509 gtkdocdir
= os
.path
.split(sys
.argv
[0])[0]
1510 if not os
.path
.exists(gtkdocdir
+ '/gtk-doc.xsl'):
1511 # try 'srcdir' (set from makefiles) too
1512 if os
.path
.exists(os
.environ
.get("ABS_TOP_SRCDIR", '') + '/gtk-doc.xsl'):
1513 gtkdocdir
= os
.environ
['ABS_TOP_SRCDIR']
1514 styledir
= gtkdocdir
+ '/style'
1516 gtkdocdir
= os
.path
.join(config
.datadir
, 'gtk-doc/data')
1517 styledir
= gtkdocdir
1518 return (gtkdocdir
, styledir
)
1521 def main(module
, index_file
, out_dir
, uninstalled
):
1522 tree
= etree
.parse(index_file
)
1525 (gtkdocdir
, styledir
) = get_dirs(uninstalled
)
1526 # copy navigation images and stylesheets to html directory ...
1527 css_file
= os
.path
.join(styledir
, 'style.css')
1528 for f
in glob(os
.path
.join(styledir
, '*.png')) + [css_file
]:
1529 shutil
.copy(f
, out_dir
)
1530 css_file
= os
.path
.join(out_dir
, 'style.css')
1531 with
open(css_file
, 'at', newline
='\n', encoding
='utf-8') as css
:
1532 css
.write(HTML_FORMATTER
.get_style_defs())
1534 # TODO: migrate options from fixxref
1535 # TODO: do in parallel with loading the xml above.
1536 fixxref
.LoadIndicies(out_dir
, '/usr/share/gtk-doc/html', [])
1538 # We do multiple passes:
1539 # 1) recursively walk the tree and chunk it into a python tree so that we
1540 # can generate navigation and link tags.
1541 files
= chunk(tree
.getroot(), module
)
1542 files
= [f
for f
in PreOrderIter(files
) if f
.anchor
is None]
1544 # 2) extract tables:
1545 # TODO: use multiprocessing
1546 # - find all 'id' attribs and add them to the link map
1547 add_id_links(files
, fixxref
.Links
)
1548 # - build glossary dict
1549 build_glossary(files
)
1551 # 3) create a xxx.devhelp2 file, do this before 3), since we modify the tree
1552 create_devhelp2(out_dir
, module
, tree
.getroot(), files
)
1553 # 4) iterate the tree and output files
1554 # TODO: use multiprocessing
1556 convert(out_dir
, module
, files
, node
)
1560 logging
.info('options: %s', str(options
.__dict
__))
1561 module
= options
.args
[0]
1562 document
= options
.args
[1]
1564 # TODO: rename to 'html' later on
1565 # - right now in mkhtml, the dir is created by the Makefile and mkhtml
1566 # outputs into the working directory
1567 out_dir
= os
.path
.join(os
.path
.dirname(document
), 'db2html')
1570 except OSError as e
:
1571 if e
.errno
!= errno
.EEXIST
:
1574 sys
.exit(main(module
, document
, out_dir
, options
.uninstalled
))