Coverage for /builds/ahmed.baizid.0/gtk-doc/gtkdoc/mkhtml2.py: 16%

1025 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2024-11-05 19:25 +0000

1#!/usr/bin/env python3 

2# -*- python; coding: utf-8 -*- 

3# 

4# gtk-doc - GTK DocBook documentation generator. 

5# Copyright (C) 2018 Stefan Sauer 

6# 

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. 

11# 

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. 

16# 

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. 

20# 

21 

22"""Generate html from docbook 

23 

24The tool loads the main xml document (<module>-docs.xml) and chunks it 

25like the xsl-stylesheets would do. For that it resolves all the xml-includes. 

26Each chunk is converted to html using python functions. 

27 

28In contrast to our previous approach of running gtkdoc-mkhtml + gtkdoc-fixxref, 

29this tools will replace both without relying on external tools such as xsltproc 

30and source-highlight. 

31 

32Please note, that we're not aiming for complete docbook-xml support. All tags 

33used in the generated xml are of course handled. More tags used in handwritten 

34xml can be easily supported, but for some combinations of tags we prefer 

35simplicity. 

36 

37TODO: 

38- tag converters: 

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- 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 

48- consolidate title handling: 

49 - always use the titles-dict 

50 - convert_title(): uses titles.get(tid)['title'] 

51 - convert_xref(): uses titles[tid]['tag'], ['title'] and ['xml'] 

52 - create_devhelp2_refsect2_keyword(): uses titles[tid]['title'] 

53 - there only store what we have (xml, tag, ...) 

54 - when chunking generate 'id's and add entries to titles-dict 

55 - add accessors for title and raw_title that lazily get them 

56 - see if any of the other ~10 places that call convert_title() could use this 

57 cache 

58- performance 

59 - consider some perf-warnings flag 

60 - see 'No "id" attribute on' 

61 - xinclude processing in libxml2 is slow 

62 - if we disable it, we get '{http://www.w3.org/2003/XInclude}include' tags 

63 and we could try handling them ourself, in some cases those are subtrees 

64 that we extract for chunking anyway 

65 

66DIFFERENCES: 

67- titles 

68 - we add the chunk label to the title in toc, on the page and in nav tooltips 

69 - docbook xsl only sometimes adds the label to the titles and when it does it 

70 adds name chunk type too (e.g. 'Part I.' instead of 'I.') 

71- navigation 

72 - we always add an up-link except on the first page 

73- footer 

74 - we're nov omitting the footer 

75- tocs 

76 - we always add "Table of Contents' before a toc 

77 - docbook does that for some pages, it is configurable 

78 

79OPTIONAL: 

80- minify html: https://pypi.python.org/pypi/htmlmin/ 

81 

82Requirements: 

83sudo pip3 install anytree lxml pygments 

84 

85Example invocation: 

86cd tests/bugs/docs/ 

87mkdir -p db2html 

88cd dbhtml 

89../../../../gtkdoc-mkhtml2 tester ../tester-docs.xml 

90cd .. 

91xdg-open db2html/index.html 

92meld html db2html 

93 

94Benchmarking: 

95cd tests/bugs/docs/; 

96rm html-build.stamp; time make html-build.stamp 

97""" 

98 

99import argparse 

100import logging 

101import os 

102import shutil 

103import sys 

104 

105from copy import deepcopy 

106from glob import glob 

107from lxml import etree 

108from timeit import default_timer as timer 

109 

110from . import config, highlight, fixxref 

111 

112 

113class ChunkParams(object): 

114 def __init__(self, prefix, parent=None, min_idx=0): 

115 self.prefix = prefix 

116 self.parent = parent 

117 self.min_idx = min_idx 

118 self.idx = 1 

119 

120 

121DONT_CHUNK = float('inf') 

122# docbook-xsl defines the chunk tags here. 

123# http://www.sagehill.net/docbookxsl/Chunking.html#GeneratedFilenames 

124# https://github.com/oreillymedia/HTMLBook/blob/master/htmlbook-xsl/chunk.xsl#L33 

125# If not defined, we can just create an example without an 'id' attr and see 

126# docbook xsl does. 

127# 

128# For toc levels see http://www.sagehill.net/docbookxsl/TOCcontrol.html 

129# TODO: this list has also a flag that controls whether we add the 

130# 'Table of Contents' heading in convert_chunk_with_toc() 

131CHUNK_PARAMS = { 

132 'appendix': ChunkParams('app', 'book'), 

133 'book': ChunkParams('bk'), 

134 'chapter': ChunkParams('ch', 'book'), 

135 'glossary': ChunkParams('go', 'book'), 

136 'index': ChunkParams('ix', 'book'), 

137 'part': ChunkParams('pt', 'book'), 

138 'preface': ChunkParams('pr', 'book'), 

139 'refentry': ChunkParams('re', 'book'), 

140 'reference': ChunkParams('rn', 'book'), 

141 'sect1': ChunkParams('s', 'chapter', 1), 

142 'section': ChunkParams('s', 'chapter', 1), 

143 'sect2': ChunkParams('s', 'sect1', DONT_CHUNK), 

144 'sect3': ChunkParams('s', 'sect2', DONT_CHUNK), 

145 'sect4': ChunkParams('s', 'sect3', DONT_CHUNK), 

146 'sect5': ChunkParams('s', 'sect4', DONT_CHUNK), 

147} 

148# TAGS we don't support: 

149# 'article', 'bibliography', 'colophon', 'set', 'setindex' 

150 

151TITLE_XPATHS = { 

152 '_': (etree.XPath('./title'), None), 

153 'book': (etree.XPath('./bookinfo/title'), None), 

154 'refentry': ( 

155 etree.XPath('./refmeta/refentrytitle'), 

156 etree.XPath('./refnamediv/refpurpose') 

157 ), 

158} 

159 

160ID_XPATH = etree.XPath('//*[@id]') 

161 

162GLOSSENTRY_XPATH = etree.XPath('//glossentry') 

163glossary = {} 

164 

165footnote_idx = 1 

166 

167# nested dict with subkeys: 

168# title: textual title 

169# tag: chunk tag 

170# xml: title xml node 

171titles = {} 

172 

173# files to copy 

174assets = set() 

175 

176 

177def encode_entities(text): 

178 return text.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;') 

179 

180 

181def raw_text(xml): 

182 return etree.tostring(xml, method="text", encoding=str).strip() 

183 

184 

185def gen_chunk_name(node, chunk_params): 

186 """Generate a chunk file name 

187 

188 This is either based on the id or on the position in the doc. In the latter 

189 case it uses a prefix from CHUNK_PARAMS and a sequence number for each chunk 

190 type. 

191 """ 

192 idval = node.attrib.get('id') 

193 if idval is not None: 

194 return idval 

195 

196 name = ('%s%02d' % (chunk_params.prefix, chunk_params.idx)) 

197 chunk_params.idx += 1 

198 

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: 

207 # break; 

208 # chunk_params = CHUNK_PARAMS[parent] 

209 # name = ('%s%02d' % (chunk_params.prefix, chunk_params.idx)) + name 

210 

211 logging.info('Gen chunk name: "%s"', name) 

212 return name 

213 

214 

215def get_chunk_titles(module, node, tree_node): 

216 tag = node.tag 

217 (title, subtitle) = TITLE_XPATHS.get(tag, TITLE_XPATHS['_']) 

218 

219 ctx = { 

220 'module': module, 

221 'files': [], 

222 'node': tree_node, 

223 } 

224 result = { 

225 'title': None, 

226 'title_tag': None, 

227 'subtitle': None, 

228 'subtitle_tag': None 

229 } 

230 res = title(node) 

231 if res: 

232 # handle chunk label for tocs 

233 label = node.attrib.get('label') 

234 if label: 

235 label += '. ' 

236 else: 

237 label = '' 

238 

239 xml = res[0] 

240 # TODO: consider to eval 'title'/'raw_title' lazily 

241 result['title'] = label + ''.join(convert_title(ctx, xml)) 

242 result['raw_title'] = encode_entities(raw_text(xml)) 

243 if xml.tag != 'title': 

244 result['title_tag'] = xml.tag 

245 else: 

246 result['title_tag'] = tag 

247 

248 if subtitle: 

249 res = subtitle(node) 

250 if res: 

251 xml = res[0] 

252 result['subtitle'] = ''.join(convert_title(ctx, xml)) 

253 result['subtitle_tag'] = xml.tag 

254 return result 

255 

256 

257class PreOrderIter: 

258 def __init__(self, node): 

259 self.__node = node 

260 

261 def __iter__(self): 

262 yield self.__node 

263 

264 for child in self.__node.descendants: 

265 yield child 

266 

267 

268class Node: 

269 def __init__(self, name, parent=None, **kwargs): 

270 self.name = name 

271 self.__root = None 

272 self.__attrs = kwargs 

273 self.children = [] 

274 

275 assert parent is None or isinstance(parent, Node) 

276 self.parent = parent 

277 if parent is not None: 

278 self.__root = parent.root 

279 parent.children.append(self) 

280 

281 @property 

282 def root(self): 

283 return self.__root or self 

284 

285 @property 

286 def descendants(self): 

287 ret = [] 

288 

289 for child in self.children: 

290 ret.append(child) 

291 

292 for other in child.descendants: 

293 ret.append(other) 

294 

295 return ret 

296 

297 def __iter__(self): 

298 for child in self.children: 

299 yield child 

300 

301 def __getattr__(self, name): 

302 try: 

303 return self.__attrs[name] 

304 except KeyError as e: 

305 raise AttributeError(str(e)) 

306 

307 

308def chunk(xml_node, module, depth=0, idx=0, parent=None): 

309 """Chunk the tree. 

310 

311 The first time, we're called with parent=None and in that case we return 

312 the new_node as the root of the tree. For each tree-node we generate a 

313 filename and process the children. 

314 """ 

315 tag = xml_node.tag 

316 chunk_params = CHUNK_PARAMS.get(tag) 

317 if chunk_params: 

318 title_args = get_chunk_titles(module, xml_node, parent) 

319 chunk_name = gen_chunk_name(xml_node, chunk_params) 

320 

321 # check idx to handle 'sect1'/'section' special casing and title-only 

322 # segments 

323 if idx >= chunk_params.min_idx: 

324 logging.info('chunk tag: "%s"[%d]', tag, idx) 

325 if parent: 

326 # remove the xml-node from the parent 

327 sub_tree = etree.ElementTree(deepcopy(xml_node)).getroot() 

328 xml_node.getparent().remove(xml_node) 

329 xml_node = sub_tree 

330 

331 parent = Node(tag, parent=parent, xml=xml_node, depth=depth, 

332 idx=idx, 

333 filename=chunk_name + '.html', anchor=None, 

334 **title_args) 

335 else: 

336 parent = Node(tag, parent=parent, xml=xml_node, depth=depth, 

337 idx=idx, 

338 filename=parent.filename, anchor='#' + chunk_name, 

339 **title_args) 

340 

341 depth += 1 

342 idx = 0 

343 for child in xml_node: 

344 chunk(child, module, depth, idx, parent) 

345 if child.tag in CHUNK_PARAMS: 

346 idx += 1 

347 

348 return parent 

349 

350 

351def add_id_links_and_titles(files, links): 

352 for node in files: 

353 chunk_name = node.filename[:-5] 

354 chunk_base = node.filename + '#' 

355 for elem in ID_XPATH(node.xml): 

356 attr = elem.attrib['id'] 

357 if attr == chunk_name: 

358 links[attr] = node.filename 

359 else: 

360 links[attr] = chunk_base + attr 

361 

362 title = TITLE_XPATHS.get(elem.tag, TITLE_XPATHS['_'])[0] 

363 res = title(elem) 

364 if res: 

365 xml = res[0] 

366 # TODO: consider to eval 'title' lazily 

367 titles[attr] = { 

368 'title': encode_entities(raw_text(xml)), 

369 'xml': xml, 

370 'tag': elem.tag, 

371 } 

372 

373 

374def build_glossary(files): 

375 for node in files: 

376 if node.xml.tag != 'glossary': 

377 continue 

378 for term in GLOSSENTRY_XPATH(node.xml): 

379 # TODO: there can be all kind of things in a glossary. This only supports 

380 # what we commonly use, glossterm is mandatory 

381 key_node = term.find('glossterm') 

382 val_node = term.find('glossdef') 

383 if key_node is not None and val_node is not None: 

384 glossary[raw_text(key_node)] = raw_text(val_node) 

385 else: 

386 debug = [] 

387 if key_node is None: 

388 debug.append('missing key') 

389 if val_node is None: 

390 debug.append('missing val') 

391 logging.warning('Broken glossentry "%s": %s', 

392 term.attrib['id'], ','.join(debug)) 

393 

394 

395# conversion helpers 

396 

397 

398def convert_inner(ctx, xml, result): 

399 for child in xml: 

400 result.extend(convert_tags.get(child.tag, convert__unknown)(ctx, child)) 

401 

402 

403def convert_ignore(ctx, xml): 

404 result = [] 

405 convert_inner(ctx, xml, result) 

406 return result 

407 

408 

409def convert_skip(ctx, xml): 

410 return [] 

411 

412 

413def append_idref(attrib, result): 

414 idval = attrib.get('id') 

415 if idval is not None: 

416 result.append('<a name="%s"></a>' % idval) 

417 

418 

419def append_text(ctx, text, result): 

420 if text and ('no-strip' in ctx or text.strip()): 

421 result.append(encode_entities(text)) 

422 

423 

424missing_tags = {} 

425 

426 

427def convert__unknown(ctx, xml): 

428 # don't recurse on subchunks 

429 if xml.tag in CHUNK_PARAMS: 

430 return [] 

431 if isinstance(xml, etree._Comment): 

432 return ['<!-- ' + xml.text + '-->\n'] 

433 else: 

434 # warn only once 

435 if xml.tag not in missing_tags: 

436 logging.warning('Add tag converter for "%s"', xml.tag) 

437 missing_tags[xml.tag] = True 

438 result = ['<!-- ' + xml.tag + '-->\n'] 

439 convert_inner(ctx, xml, result) 

440 result.append('<!-- /' + xml.tag + '-->\n') 

441 return result 

442 

443 

444def convert_mediaobject_children(ctx, xml, result): 

445 # look for textobject/phrase 

446 alt_text = '' 

447 textobject = xml.find('textobject') 

448 if textobject is not None: 

449 phrase = textobject.findtext('phrase') 

450 if phrase: 

451 alt_text = ' alt="%s"' % phrase 

452 

453 # look for imageobject/imagedata 

454 imageobject = xml.find('imageobject') 

455 if imageobject is not None: 

456 imagedata = imageobject.find('imagedata') 

457 if imagedata is not None: 

458 # TODO(ensonic): warn on missing fileref attr? 

459 fileref = imagedata.attrib.get('fileref', '') 

460 if fileref: 

461 assets.add(fileref) 

462 result.append('<img src="%s"%s>' % (fileref, alt_text)) 

463 

464 

465def convert_sect(ctx, xml, h_tag, inner_func=convert_inner): 

466 result = ['<div class="%s">\n' % xml.tag] 

467 title_tag = xml.find('title') 

468 if title_tag is not None: 

469 append_idref(xml.attrib, result) 

470 result.append('<%s>%s</%s>' % ( 

471 h_tag, ''.join(convert_title(ctx, title_tag)), h_tag)) 

472 append_text(ctx, xml.text, result) 

473 inner_func(ctx, xml, result) 

474 result.append('</div>') 

475 append_text(ctx, xml.tail, result) 

476 return result 

477 

478 

479def xml_get_title(ctx, xml): 

480 title_tag = xml.find('title') 

481 if title_tag is not None: 

482 return ''.join(convert_title(ctx, title_tag)) 

483 else: 

484 logging.warning('%s: Expected title tag under "%s %s"', xml.sourceline, xml.tag, str(xml.attrib)) 

485 return '' 

486 

487 

488# docbook tags 

489 

490 

491def convert_abstract(ctx, xml): 

492 result = ["""<div class="abstract"> 

493 <p class="title"><b>Abstract</b></p>"""] 

494 append_text(ctx, xml.text, result) 

495 convert_inner(ctx, xml, result) 

496 result.append('</div>') 

497 append_text(ctx, xml.tail, result) 

498 return result 

499 

500 

501def convert_acronym(ctx, xml): 

502 key = xml.text 

503 title = glossary.get(key, '') 

504 # TODO: print a sensible warning if missing 

505 result = ['<acronym title="%s"><span class="acronym">%s</span></acronym>' % (title, key)] 

506 if xml.tail: 

507 result.append(xml.tail) 

508 return result 

509 

510 

511def convert_anchor(ctx, xml): 

512 return ['<a name="%s"></a>' % xml.attrib['id']] 

513 

514 

515def convert_bookinfo(ctx, xml): 

516 result = ['<div class="titlepage">'] 

517 convert_inner(ctx, xml, result) 

518 result.append("""<hr> 

519</div>""") 

520 if xml.tail: 

521 result.append(xml.tail) 

522 return result 

523 

524 

525def convert_blockquote(ctx, xml): 

526 result = ['<div class="blockquote">\n<blockquote class="blockquote">'] 

527 append_text(ctx, xml.text, result) 

528 convert_inner(ctx, xml, result) 

529 result.append('</blockquote>\n</div>') 

530 append_text(ctx, xml.tail, result) 

531 return result 

532 

533 

534def convert_code(ctx, xml): 

535 result = ['<code class="%s">' % xml.tag] 

536 append_text(ctx, xml.text, result) 

537 convert_inner(ctx, xml, result) 

538 result.append('</code>') 

539 append_text(ctx, xml.tail, result) 

540 return result 

541 

542 

543def convert_colspec(ctx, xml): 

544 result = ['<col'] 

545 colname = xml.attrib.get('colname') 

546 if colname is not None: 

547 result.append(' class="%s"' % colname) 

548 colwidth = xml.attrib.get('colwidth') 

549 if colwidth is not None: 

550 result.append(' width="%s"' % colwidth) 

551 result.append('>\n') 

552 # is in tgroup and there can be no 'text' 

553 return result 

554 

555 

556def convert_command(ctx, xml): 

557 result = ['<strong class="userinput"><code>'] 

558 append_text(ctx, xml.text, result) 

559 convert_inner(ctx, xml, result) 

560 result.append('</code></strong>') 

561 append_text(ctx, xml.tail, result) 

562 return result 

563 

564 

565def convert_corpauthor(ctx, xml): 

566 result = ['<div><h3 class="corpauthor">\n'] 

567 append_text(ctx, xml.text, result) 

568 convert_inner(ctx, xml, result) 

569 result.append('</h3></div>\n') 

570 append_text(ctx, xml.tail, result) 

571 return result 

572 

573 

574def convert_div(ctx, xml): 

575 result = ['<div class="%s">\n' % xml.tag] 

576 append_text(ctx, xml.text, result) 

577 convert_inner(ctx, xml, result) 

578 result.append('</div>') 

579 append_text(ctx, xml.tail, result) 

580 return result 

581 

582 

583def convert_emphasis(ctx, xml): 

584 role = xml.attrib.get('role') 

585 if role is not None: 

586 result = ['<span class="%s">' % role] 

587 end = '</span>' 

588 else: 

589 result = ['<span class="emphasis"><em>'] 

590 end = '</em></span>' 

591 append_text(ctx, xml.text, result) 

592 convert_inner(ctx, xml, result) 

593 result.append(end) 

594 append_text(ctx, xml.tail, result) 

595 return result 

596 

597 

598def convert_em(ctx, xml): 

599 result = ['<em class="%s">' % xml.tag] 

600 append_text(ctx, xml.text, result) 

601 convert_inner(ctx, xml, result) 

602 result.append('</em>') 

603 append_text(ctx, xml.tail, result) 

604 return result 

605 

606 

607def convert_em_code(ctx, xml): 

608 result = ['<em class="%s"><code>' % xml.tag] 

609 append_idref(xml.attrib, result) 

610 append_text(ctx, xml.text, result) 

611 convert_inner(ctx, xml, result) 

612 result.append('</code></em>') 

613 append_text(ctx, xml.tail, result) 

614 return result 

615 

616 

617def convert_entry(ctx, xml): 

618 entry_type = ctx['table.entry'] 

619 result = ['<' + entry_type] 

620 role = xml.attrib.get('role') 

621 if role is not None: 

622 result.append(' class="%s"' % role) 

623 morerows = xml.attrib.get('morerows') 

624 if morerows is not None: 

625 result.append(' rowspan="%s"' % (1 + int(morerows))) 

626 result.append('>') 

627 append_text(ctx, xml.text, result) 

628 convert_inner(ctx, xml, result) 

629 result.append('</' + entry_type + '>') 

630 append_text(ctx, xml.tail, result) 

631 return result 

632 

633 

634def convert_figure(ctx, xml): 

635 result = ['<div class="figure">\n'] 

636 append_idref(xml.attrib, result) 

637 title_tag = xml.find('title') 

638 if title_tag is not None: 

639 # TODO(ensonic): Add a 'Figure X. ' prefix, needs a figure counter 

640 result.append('<p><b>%s</b></p>' % ''.join(convert_title(ctx, title_tag))) 

641 result.append('<div class="figure-contents">') 

642 # TODO(ensonic): title can become alt on inner 'graphic' element 

643 convert_inner(ctx, xml, result) 

644 result.append('</div></div><br class="figure-break"/>') 

645 append_text(ctx, xml.tail, result) 

646 return result 

647 

648 

649def convert_footnote(ctx, xml): 

650 footnotes = ctx.get('footnotes', []) 

651 # footnotes idx is not per page, but per doc 

652 global footnote_idx 

653 idx = footnote_idx 

654 footnote_idx += 1 

655 

656 # need a pair of ids for each footnote (docbook generates different ids) 

657 this_id = 'footnote-%d' % idx 

658 that_id = 'ftn.' + this_id 

659 

660 inner = ['<div id="%s" class="footnote">' % that_id] 

661 inner.append('<p><a href="#%s" class="para"><sup class="para">[%d] </sup></a>' % ( 

662 this_id, idx)) 

663 # TODO(ensonic): this can contain all kind of tags, if we convert them we'll 

664 # get double nested paras :/. 

665 # convert_inner(ctx, xml, inner) 

666 para = xml.find('para') 

667 if para is None: 

668 para = xml.find('simpara') 

669 if para is not None: 

670 inner.append(para.text) 

671 else: 

672 logging.warning('%s: Unhandled footnote content: %s', xml.sourceline, raw_text(xml)) 

673 inner.append('</p></div>') 

674 footnotes.append(inner) 

675 ctx['footnotes'] = footnotes 

676 return ['<a href="#%s" class="footnote" name="%s"><sup class="footnote">[%s]</sup></a>' % ( 

677 that_id, this_id, idx)] 

678 

679 

680def convert_formalpara(ctx, xml): 

681 result = None 

682 title_tag = xml.find('title') 

683 result = ['<p><b>%s</b>' % ''.join(convert_title(ctx, title_tag))] 

684 para_tag = xml.find('para') 

685 append_text(ctx, para_tag.text, result) 

686 convert_inner(ctx, para_tag, result) 

687 append_text(ctx, para_tag.tail, result) 

688 result.append('</p>') 

689 append_text(ctx, xml.tail, result) 

690 return result 

691 

692 

693def convert_glossdef(ctx, xml): 

694 result = ['<dd class="glossdef">'] 

695 convert_inner(ctx, xml, result) 

696 result.append('</dd>\n') 

697 return result 

698 

699 

700def convert_glossdiv(ctx, xml): 

701 title_tag = xml.find('title') 

702 title = title_tag.text 

703 xml.remove(title_tag) 

704 result = [ 

705 '<a name="gls%s"></a><h3 class="title">%s</h3>' % (title, title) 

706 ] 

707 convert_inner(ctx, xml, result) 

708 return result 

709 

710 

711def convert_glossentry(ctx, xml): 

712 result = [] 

713 convert_inner(ctx, xml, result) 

714 return result 

715 

716 

717def convert_glossterm(ctx, xml): 

718 glossid = '' 

719 text = '' 

720 anchor = xml.find('anchor') 

721 if anchor is not None: 

722 glossid = anchor.attrib.get('id', '') 

723 text += anchor.tail or '' 

724 text += xml.text or '' 

725 if glossid == '': 

726 glossid = 'glossterm-' + text 

727 return [ 

728 '<dt><span class="glossterm"><a name="%s"></a>%s</span></dt>' % ( 

729 glossid, text) 

730 ] 

731 

732 

733def convert_graphic(ctx, xml): 

734 # TODO(ensonic): warn on missing fileref attr? 

735 fileref = xml.attrib.get('fileref', '') 

736 if fileref: 

737 assets.add(fileref) 

738 return ['<div><img src="%s"></div>' % fileref] 

739 

740 

741def convert_indexdiv(ctx, xml): 

742 title_tag = xml.find('title') 

743 title = title_tag.text 

744 xml.remove(title_tag) 

745 result = [ 

746 '<a name="idx%s"></a><h3 class="title">%s</h3>' % (title, title) 

747 ] 

748 convert_inner(ctx, xml, result) 

749 return result 

750 

751 

752def convert_informaltable(ctx, xml): 

753 result = ['<div class="informaltable"><table class="informaltable"'] 

754 if xml.attrib.get('pgwide') == '1': 

755 result.append(' width="100%"') 

756 if xml.attrib.get('frame') == 'none': 

757 result.append(' border="0"') 

758 result.append('>\n') 

759 convert_inner(ctx, xml, result) 

760 result.append('</table></div>') 

761 if xml.tail: 

762 result.append(xml.tail) 

763 return result 

764 

765 

766def convert_inlinegraphic(ctx, xml): 

767 # TODO(ensonic): warn on missing fileref attr? 

768 fileref = xml.attrib.get('fileref', '') 

769 if fileref: 

770 assets.add(fileref) 

771 return ['<img src="%s">' % fileref] 

772 

773 

774def convert_inlinemediaobject(ctx, xml): 

775 result = ['<span class="inlinemediaobject">'] 

776 # no PCDATA allowed here 

777 convert_mediaobject_children(ctx, xml, result) 

778 result.append('</span>') 

779 append_text(ctx, xml.tail, result) 

780 return result 

781 

782 

783def convert_itemizedlist(ctx, xml): 

784 result = ['<div class="itemizedlist"><ul class="itemizedlist" style="list-style-type: disc; ">'] 

785 convert_inner(ctx, xml, result) 

786 result.append('</ul></div>') 

787 if xml.tail: 

788 result.append(xml.tail) 

789 return result 

790 

791 

792def convert_link(ctx, xml): 

793 linkend = xml.attrib['linkend'] 

794 result = [] 

795 if linkend: 

796 link_text = [] 

797 append_text(ctx, xml.text, link_text) 

798 convert_inner(ctx, xml, link_text) 

799 text = ''.join(link_text) 

800 

801 (tid, href) = fixxref.GetXRef(linkend) 

802 if href: 

803 title_attr = '' 

804 title = titles.get(tid) 

805 if title: 

806 title_attr = ' title="%s"' % title['title'] 

807 

808 href = fixxref.MakeRelativeXRef(ctx['module'], href) 

809 result = ['<a href="%s"%s>%s</a>' % (href, title_attr, text)] 

810 else: 

811 # TODO: filename is for the output and xml.sourceline is on the masterdoc ... 

812 fixxref.ReportBadXRef(ctx['node'].filename, 0, linkend, text) 

813 result = [text] 

814 else: 

815 append_text(ctx, xml.text, result) 

816 convert_inner(ctx, xml, result) 

817 append_text(ctx, xml.tail, result) 

818 return result 

819 

820 

821def convert_listitem(ctx, xml): 

822 result = ['<li class="listitem">'] 

823 convert_inner(ctx, xml, result) 

824 result.append('</li>') 

825 # no PCDATA allowed here, is in itemizedlist 

826 return result 

827 

828 

829def convert_literallayout(ctx, xml): 

830 result = ['<div class="literallayout"><p><br>\n'] 

831 append_text(ctx, xml.text, result) 

832 convert_inner(ctx, xml, result) 

833 result.append('</p></div>') 

834 append_text(ctx, xml.tail, result) 

835 return result 

836 

837 

838def convert_mediaobject(ctx, xml): 

839 result = ['<div class="mediaobject">\n'] 

840 # no PCDATA allowed here 

841 convert_mediaobject_children(ctx, xml, result) 

842 result.append('</div>') 

843 append_text(ctx, xml.tail, result) 

844 return result 

845 

846 

847def convert_orderedlist(ctx, xml): 

848 result = ['<div class="orderedlist"><ol class="orderedlist" type="1">'] 

849 convert_inner(ctx, xml, result) 

850 result.append('</ol></div>') 

851 append_text(ctx, xml.tail, result) 

852 return result 

853 

854 

855def convert_para(ctx, xml): 

856 result = [] 

857 role = xml.attrib.get('role') 

858 if role is not None: 

859 result.append('<p class="%s">' % role) 

860 else: 

861 result.append('<p>') 

862 append_idref(xml.attrib, result) 

863 append_text(ctx, xml.text, result) 

864 convert_inner(ctx, xml, result) 

865 result.append('</p>') 

866 append_text(ctx, xml.tail, result) 

867 return result 

868 

869 

870def convert_para_like(ctx, xml): 

871 result = [] 

872 append_idref(xml.attrib, result) 

873 result.append('<p class="%s">' % xml.tag) 

874 append_text(ctx, xml.text, result) 

875 convert_inner(ctx, xml, result) 

876 result.append('</p>') 

877 append_text(ctx, xml.tail, result) 

878 return result 

879 

880 

881def convert_phrase(ctx, xml): 

882 result = ['<span'] 

883 role = xml.attrib.get('role') 

884 if role is not None: 

885 result.append(' class="%s">' % role) 

886 else: 

887 result.append('>') 

888 append_text(ctx, xml.text, result) 

889 convert_inner(ctx, xml, result) 

890 result.append('</span>') 

891 append_text(ctx, xml.tail, result) 

892 return result 

893 

894 

895def convert_primaryie(ctx, xml): 

896 result = ['<dt>\n'] 

897 convert_inner(ctx, xml, result) 

898 result.append('\n</dt>\n<dd></dd>\n') 

899 return result 

900 

901 

902def convert_pre(ctx, xml): 

903 # Since we're inside <pre> don't skip newlines 

904 ctx['no-strip'] = True 

905 result = ['<pre class="%s">' % xml.tag] 

906 append_text(ctx, xml.text, result) 

907 convert_inner(ctx, xml, result) 

908 result.append('</pre>') 

909 del ctx['no-strip'] 

910 append_text(ctx, xml.tail, result) 

911 return result 

912 

913 

914def convert_programlisting(ctx, xml): 

915 result = [] 

916 if xml.attrib.get('role', '') == 'example': 

917 if xml.text: 

918 lang = xml.attrib.get('language', ctx['src-lang']).lower() 

919 highlighted = highlight.highlight_code(xml.text, lang) 

920 if highlighted: 

921 # we do own line-numbering 

922 line_count = highlighted.count('\n') 

923 source_lines = '\n'.join([str(i) for i in range(1, line_count + 1)]) 

924 result.append("""<table class="listing_frame" border="0" cellpadding="0" cellspacing="0"> 

925 <tbody> 

926 <tr> 

927 <td class="listing_lines" align="right"><pre>%s</pre></td> 

928 <td class="listing_code"><pre class="programlisting">%s</pre></td> 

929 </tr> 

930 </tbody> 

931</table> 

932""" % (source_lines, highlighted)) 

933 else: 

934 logging.warn('No pygments lexer for language="%s"', lang) 

935 result.append('<pre class="programlisting">') 

936 result.append(xml.text) 

937 result.append('</pre>') 

938 else: 

939 result.append('<pre class="programlisting">') 

940 append_text(ctx, xml.text, result) 

941 convert_inner(ctx, xml, result) 

942 result.append('</pre>') 

943 append_text(ctx, xml.tail, result) 

944 return result 

945 

946 

947def convert_quote(ctx, xml): 

948 result = ['<span class="quote">"<span class="quote">'] 

949 append_text(ctx, xml.text, result) 

950 convert_inner(ctx, xml, result) 

951 result.append('</span>"</span>') 

952 append_text(ctx, xml.tail, result) 

953 return result 

954 

955 

956def convert_refsect1(ctx, xml): 

957 # Add a divider between two consequitive refsect2 

958 def convert_inner(ctx, xml, result): 

959 prev = None 

960 for child in xml: 

961 if child.tag == 'refsect2' and prev is not None and prev.tag == child.tag: 

962 result.append('<hr>\n') 

963 result.extend(convert_tags.get(child.tag, convert__unknown)(ctx, child)) 

964 prev = child 

965 return convert_sect(ctx, xml, 'h2', convert_inner) 

966 

967 

968def convert_refsect2(ctx, xml): 

969 return convert_sect(ctx, xml, 'h3') 

970 

971 

972def convert_refsect3(ctx, xml): 

973 return convert_sect(ctx, xml, 'h4') 

974 

975 

976def convert_row(ctx, xml): 

977 result = ['<tr>\n'] 

978 convert_inner(ctx, xml, result) 

979 result.append('</tr>\n') 

980 return result 

981 

982 

983def convert_sbr(ctx, xml): 

984 return ['<br>'] 

985 

986 

987def convert_sect1_tag(ctx, xml): 

988 return convert_sect(ctx, xml, 'h2') 

989 

990 

991def convert_sect2(ctx, xml): 

992 return convert_sect(ctx, xml, 'h3') 

993 

994 

995def convert_sect3(ctx, xml): 

996 return convert_sect(ctx, xml, 'h4') 

997 

998 

999def convert_simpara(ctx, xml): 

1000 result = ['<p>'] 

1001 append_text(ctx, xml.text, result) 

1002 convert_inner(ctx, xml, result) 

1003 result.append('</p>') 

1004 append_text(ctx, xml.tail, result) 

1005 return result 

1006 

1007 

1008def convert_span(ctx, xml): 

1009 result = ['<span class="%s">' % xml.tag] 

1010 append_text(ctx, xml.text, result) 

1011 convert_inner(ctx, xml, result) 

1012 result.append('</span>') 

1013 append_text(ctx, xml.tail, result) 

1014 return result 

1015 

1016 

1017def convert_table(ctx, xml): 

1018 result = ['<div class="table">'] 

1019 append_idref(xml.attrib, result) 

1020 title_tag = xml.find('title') 

1021 if title_tag is not None: 

1022 result.append('<p class="title"><b>') 

1023 # TODO(ensonic): Add a 'Table X. ' prefix, needs a table counter 

1024 result.extend(convert_title(ctx, title_tag)) 

1025 result.append('</b></p>') 

1026 result.append('<div class="table-contents"><table class="table" summary="g_object_new" border="1">') 

1027 

1028 convert_inner(ctx, xml, result) 

1029 

1030 result.append('</table></div></div>') 

1031 append_text(ctx, xml.tail, result) 

1032 return result 

1033 

1034 

1035def convert_tag(ctx, xml): 

1036 classval = xml.attrib.get('class') 

1037 if classval is not None: 

1038 result = ['<code class="sgmltag-%s">' % classval] 

1039 else: 

1040 result = ['<code>'] 

1041 append_text(ctx, xml.text, result) 

1042 result.append('</code>') 

1043 append_text(ctx, xml.tail, result) 

1044 return result 

1045 

1046 

1047def convert_tbody(ctx, xml): 

1048 result = ['<tbody>'] 

1049 ctx['table.entry'] = 'td' 

1050 convert_inner(ctx, xml, result) 

1051 result.append('</tbody>') 

1052 # is in tgroup and there can be no 'text' 

1053 return result 

1054 

1055 

1056def convert_tgroup(ctx, xml): 

1057 # tgroup does not expand to anything, but the nested colspecs need to 

1058 # be put into a colgroup 

1059 cols = xml.findall('colspec') 

1060 result = [] 

1061 if cols: 

1062 result.append('<colgroup>\n') 

1063 for col in cols: 

1064 result.extend(convert_colspec(ctx, col)) 

1065 xml.remove(col) 

1066 result.append('</colgroup>\n') 

1067 convert_inner(ctx, xml, result) 

1068 # is in informaltable and there can be no 'text' 

1069 return result 

1070 

1071 

1072def convert_thead(ctx, xml): 

1073 result = ['<thead>'] 

1074 ctx['table.entry'] = 'th' 

1075 convert_inner(ctx, xml, result) 

1076 result.append('</thead>') 

1077 # is in tgroup and there can be no 'text' 

1078 return result 

1079 

1080 

1081def convert_title(ctx, xml): 

1082 # This is always explicitly called from some context 

1083 result = [] 

1084 append_text(ctx, xml.text, result) 

1085 convert_inner(ctx, xml, result) 

1086 append_text(ctx, xml.tail, result) 

1087 return result 

1088 

1089 

1090def convert_ulink(ctx, xml): 

1091 if xml.text: 

1092 result = ['<a class="%s" href="%s">%s</a>' % (xml.tag, xml.attrib['url'], xml.text)] 

1093 else: 

1094 url = xml.attrib['url'] 

1095 result = ['<a class="%s" href="%s">%s</a>' % (xml.tag, url, url)] 

1096 append_text(ctx, xml.tail, result) 

1097 return result 

1098 

1099 

1100def convert_userinput(ctx, xml): 

1101 result = ['<span class="command"><strong>'] 

1102 append_text(ctx, xml.text, result) 

1103 convert_inner(ctx, xml, result) 

1104 result.append('</strong></span>') 

1105 append_text(ctx, xml.tail, result) 

1106 return result 

1107 

1108 

1109def convert_variablelist(ctx, xml): 

1110 result = ["""<div class="variablelist"><table border="0" class="variablelist"> 

1111<colgroup> 

1112<col align="left" valign="top"> 

1113<col> 

1114</colgroup> 

1115<tbody>"""] 

1116 convert_inner(ctx, xml, result) 

1117 result.append("""</tbody> 

1118</table></div>""") 

1119 return result 

1120 

1121 

1122def convert_varlistentry(ctx, xml): 

1123 result = ['<tr>'] 

1124 

1125 result.append('<td><p>') 

1126 term = xml.find('term') 

1127 result.extend(convert_span(ctx, term)) 

1128 result.append('</p></td>') 

1129 

1130 result.append('<td>') 

1131 listitem = xml.find('listitem') 

1132 convert_inner(ctx, listitem, result) 

1133 result.append('</td>') 

1134 

1135 result.append('<tr>') 

1136 return result 

1137 

1138 

1139def convert_xref(ctx, xml): 

1140 result = [] 

1141 linkend = xml.attrib['linkend'] 

1142 (tid, href) = fixxref.GetXRef(linkend) 

1143 try: 

1144 title = titles[tid] 

1145 # all sectN need to become 'section 

1146 tag = title['tag'] 

1147 tag = { 

1148 'sect1': 'section', 

1149 'sect2': 'section', 

1150 'sect3': 'section', 

1151 'sect4': 'section', 

1152 'sect5': 'section', 

1153 }.get(tag, tag) 

1154 result = [ 

1155 '<a class="xref" href="%s" title="%s">the %s called “%s”</a>' % 

1156 (href, title['title'], tag, ''.join(convert_title(ctx, title['xml']))) 

1157 ] 

1158 except KeyError: 

1159 logging.warning('invalid linkend "%s"', tid) 

1160 

1161 append_text(ctx, xml.tail, result) 

1162 return result 

1163 

1164 

1165# TODO(ensonic): turn into class with converters as functions and ctx as self 

1166convert_tags = { 

1167 'abstract': convert_abstract, 

1168 'acronym': convert_acronym, 

1169 'anchor': convert_anchor, 

1170 'application': convert_span, 

1171 'bookinfo': convert_bookinfo, 

1172 'blockquote': convert_blockquote, 

1173 'classname': convert_code, 

1174 'caption': convert_div, 

1175 'code': convert_code, 

1176 'colspec': convert_colspec, 

1177 'constant': convert_code, 

1178 'command': convert_command, 

1179 'corpauthor': convert_corpauthor, 

1180 'emphasis': convert_emphasis, 

1181 'entry': convert_entry, 

1182 'envar': convert_code, 

1183 'footnote': convert_footnote, 

1184 'figure': convert_figure, 

1185 'filename': convert_code, 

1186 'firstterm': convert_em, 

1187 'formalpara': convert_formalpara, 

1188 'function': convert_code, 

1189 'glossdef': convert_glossdef, 

1190 'glossdiv': convert_glossdiv, 

1191 'glossentry': convert_glossentry, 

1192 'glossterm': convert_glossterm, 

1193 'graphic': convert_graphic, 

1194 'indexdiv': convert_indexdiv, 

1195 'indexentry': convert_ignore, 

1196 'indexterm': convert_skip, 

1197 'informalexample': convert_div, 

1198 'informaltable': convert_informaltable, 

1199 'inlinegraphic': convert_inlinegraphic, 

1200 'inlinemediaobject': convert_inlinemediaobject, 

1201 'interfacename': convert_code, 

1202 'itemizedlist': convert_itemizedlist, 

1203 'legalnotice': convert_div, 

1204 'link': convert_link, 

1205 'listitem': convert_listitem, 

1206 'literal': convert_code, 

1207 'literallayout': convert_literallayout, 

1208 'mediaobject': convert_mediaobject, 

1209 'note': convert_div, 

1210 'option': convert_code, 

1211 'orderedlist': convert_orderedlist, 

1212 'para': convert_para, 

1213 'partintro': convert_div, 

1214 'parameter': convert_em_code, 

1215 'phrase': convert_phrase, 

1216 'primaryie': convert_primaryie, 

1217 'programlisting': convert_programlisting, 

1218 'quote': convert_quote, 

1219 'releaseinfo': convert_para_like, 

1220 'refsect1': convert_refsect1, 

1221 'refsect2': convert_refsect2, 

1222 'refsect3': convert_refsect3, 

1223 'replaceable': convert_em_code, 

1224 'returnvalue': convert_span, 

1225 'row': convert_row, 

1226 'sbr': convert_sbr, 

1227 'screen': convert_pre, 

1228 'section': convert_sect2, # FIXME: need tracking of nesting 

1229 'sect1': convert_sect1_tag, 

1230 'sect2': convert_sect2, 

1231 'sect3': convert_sect3, 

1232 'simpara': convert_simpara, 

1233 'simplesect': convert_sect2, # FIXME: need tracking of nesting 

1234 'structfield': convert_em_code, 

1235 'structname': convert_span, 

1236 'synopsis': convert_pre, 

1237 'symbol': convert_span, 

1238 'table': convert_table, 

1239 'tag': convert_tag, 

1240 'tbody': convert_tbody, 

1241 'term': convert_span, 

1242 'tgroup': convert_tgroup, 

1243 'thead': convert_thead, 

1244 'title': convert_skip, 

1245 'type': convert_span, 

1246 'ulink': convert_ulink, 

1247 'userinput': convert_userinput, 

1248 'varname': convert_code, 

1249 'variablelist': convert_variablelist, 

1250 'varlistentry': convert_varlistentry, 

1251 'warning': convert_div, 

1252 'xref': convert_xref, 

1253} 

1254 

1255# conversion helpers 

1256 

1257HTML_HEADER = """<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 

1258<html> 

1259<head> 

1260<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 

1261<title>%s</title> 

1262%s<link rel="stylesheet" href="style.css" type="text/css"> 

1263</head> 

1264<body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"> 

1265""" 

1266 

1267 

1268def generate_head_links(ctx): 

1269 n = ctx['nav_home'] 

1270 result = [ 

1271 '<link rel="home" href="%s" title="%s">\n' % (n.filename, n.raw_title) 

1272 ] 

1273 

1274 n = ctx.get('nav_up') 

1275 if n is not None: 

1276 result.append('<link rel="up" href="%s" title="%s">\n' % (n.filename, n.raw_title)) 

1277 

1278 n = ctx.get('nav_prev') 

1279 if n is not None: 

1280 result.append('<link rel="prev" href="%s" title="%s">\n' % (n.filename, n.raw_title)) 

1281 

1282 n = ctx.get('nav_next') 

1283 if n is not None: 

1284 result.append('<link rel="next" href="%s" title="%s">\n' % (n.filename, n.raw_title)) 

1285 

1286 return ''.join(result) 

1287 

1288 

1289def generate_nav_links(ctx): 

1290 n = ctx['nav_home'] 

1291 result = [ 

1292 '<td><a accesskey="h" href="%s"><img src="home.png" width="16" height="16" border="0" alt="Home"></a></td>' % n.filename 

1293 ] 

1294 

1295 n = ctx.get('nav_up') 

1296 if n is not None: 

1297 result.append( 

1298 '<td><a accesskey="u" href="%s"><img src="up.png" width="16" height="16" border="0" alt="Up"></a></td>' % n.filename) 

1299 else: 

1300 result.append('<td><img src="up-insensitive.png" width="16" height="16" border="0"></td>') 

1301 

1302 n = ctx.get('nav_prev') 

1303 if n is not None: 

1304 result.append( 

1305 '<td><a accesskey="p" href="%s"><img src="left.png" width="16" height="16" border="0" alt="Prev"></a></td>' % n.filename) 

1306 else: 

1307 result.append('<td><img src="left-insensitive.png" width="16" height="16" border="0"></td>') 

1308 

1309 n = ctx.get('nav_next') 

1310 if n is not None: 

1311 result.append( 

1312 '<td><a accesskey="n" href="%s"><img src="right.png" width="16" height="16" border="0" alt="Next"></a></td>' % n.filename) 

1313 else: 

1314 result.append('<td><img src="right-insensitive.png" width="16" height="16" border="0"></td>') 

1315 

1316 return ''.join(result) 

1317 

1318 

1319def generate_toc(ctx, node): 

1320 result = [] 

1321 for c in node.children: 

1322 # TODO: urlencode the filename: urllib.parse.quote_plus() 

1323 link = c.filename 

1324 if c.anchor: 

1325 link += c.anchor 

1326 result.append('<dt><span class="%s"><a href="%s">%s</a></span>\n' % ( 

1327 c.title_tag, link, c.title)) 

1328 if c.subtitle: 

1329 result.append('<span class="%s"> — %s</span>' % (c.subtitle_tag, c.subtitle)) 

1330 result.append('</dt>\n') 

1331 if c.children: 

1332 result.append('<dd><dl>') 

1333 result.extend(generate_toc(ctx, c)) 

1334 result.append('</dl></dd>') 

1335 return result 

1336 

1337 

1338def generate_basic_nav(ctx): 

1339 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5"> 

1340 <tr valign="middle"> 

1341 <td width="100%%" align="left" class="shortcuts"></td> 

1342 %s 

1343 </tr> 

1344</table> 

1345 """ % generate_nav_links(ctx) 

1346 

1347 

1348def generate_alpha_nav(ctx, divs, prefix, span_id): 

1349 ix_nav = [] 

1350 for s in divs: 

1351 title = xml_get_title(ctx, s) 

1352 ix_nav.append('<a class="shortcut" href="#%s%s">%s</a>' % (prefix, title, title)) 

1353 

1354 return """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="5"> 

1355 <tr valign="middle"> 

1356 <td width="100%%" align="left" class="shortcuts"> 

1357 <span id="nav_%s"> 

1358 %s 

1359 </span> 

1360 </td> 

1361 %s 

1362 </tr> 

1363</table> 

1364 """ % (span_id, '\n<span class="dim">|</span>\n'.join(ix_nav), generate_nav_links(ctx)) 

1365 

1366 

1367def generate_refentry_nav(ctx, refsect1s, result): 

1368 result.append("""<table class="navigation" id="top" width="100%" cellpadding="2" cellspacing="5"> 

1369 <tr valign="middle"> 

1370 <td width="100%" align="left" class="shortcuts"> 

1371 <a href="#" class="shortcut">Top</a>""") 

1372 

1373 for s in refsect1s: 

1374 # don't list TOC sections (role="xxx_proto") 

1375 if s.attrib.get('role', '').endswith("_proto"): 

1376 continue 

1377 # skip section without 'id' attrs 

1378 ref_id = s.attrib.get('id') 

1379 if ref_id is None: 

1380 continue 

1381 

1382 # skip foreign sections 

1383 if '.' not in ref_id: 

1384 continue 

1385 

1386 title = xml_get_title(ctx, s) 

1387 span_id = ref_id.split('.')[1].replace('-', '_') 

1388 

1389 result.append(""" 

1390 <span id="nav_%s"> 

1391    <span class="dim">|</span>  

1392 <a href="#%s" class="shortcut">%s</a> 

1393 </span> 

1394 """ % (span_id, ref_id, title)) 

1395 result.append(""" 

1396 </td> 

1397 %s 

1398 </tr> 

1399</table> 

1400""" % generate_nav_links(ctx)) 

1401 

1402 

1403def generate_footer(ctx): 

1404 footnotes = ctx.get('footnotes') 

1405 if footnotes is None: 

1406 return [] 

1407 

1408 result = ["""<div class="footnotes">\n 

1409<br><hr style="width:100; text-align:left;margin-left: 0"> 

1410"""] 

1411 for f in footnotes: 

1412 result.extend(f) 

1413 result.append('</div>\n') 

1414 return result 

1415 

1416 

1417def get_id_path(node): 

1418 """ Generate the 'id'. 

1419 We need to walk up the xml-tree and check the positions for each sibling. 

1420 When reaching the top of the tree we collect remaining index entries from 

1421 the chunked-tree. 

1422 """ 

1423 ix = [] 

1424 xml = node.xml 

1425 parent = xml.getparent() 

1426 while parent is not None: 

1427 children = parent.getchildren() 

1428 ix.insert(0, str(children.index(xml) + 1)) 

1429 xml = parent 

1430 parent = xml.getparent() 

1431 while node is not None: 

1432 ix.insert(0, str(node.idx + 1)) 

1433 node = node.parent 

1434 

1435 return ix 

1436 

1437 

1438def get_id(node): 

1439 xml = node.xml 

1440 node_id = xml.attrib.get('id', None) 

1441 if node_id: 

1442 return node_id 

1443 

1444 # TODO: this is moot if nothing links to it, we could also consider to omit 

1445 # the <a name="$id"></a> tag. 

1446 logging.info('%d: No "id" attribute on "%s", generating one', 

1447 xml.sourceline, xml.tag) 

1448 ix = get_id_path(node) 

1449 # logging.warning('%s: id indexes: %s', node.filename, str(ix)) 

1450 return 'id-' + '.'.join(ix) 

1451 

1452 

1453def convert_chunk_with_toc(ctx, div_class, title_tag): 

1454 node = ctx['node'] 

1455 result = [ 

1456 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)), 

1457 generate_basic_nav(ctx), 

1458 '<div class="%s">' % div_class, 

1459 ] 

1460 if node.title: 

1461 result.append(""" 

1462<div class="titlepage"> 

1463<%s class="title"><a name="%s"></a>%s</%s> 

1464</div>""" % ( 

1465 title_tag, get_id(node), node.title, title_tag)) 

1466 

1467 toc = generate_toc(ctx, node) 

1468 if toc: 

1469 # TODO: not all docbook page types use this extra heading 

1470 result.append("""<p><b>Table of Contents</b></p> 

1471 <div class="toc"> 

1472 <dl class="toc"> 

1473 """) 

1474 result.extend(toc) 

1475 result.append("""</dl> 

1476 </div> 

1477 """) 

1478 convert_inner(ctx, node.xml, result) 

1479 result.extend(generate_footer(ctx)) 

1480 result.append("""</div> 

1481</body> 

1482</html>""") 

1483 return result 

1484 

1485 

1486# docbook chunks 

1487 

1488 

1489def convert_book(ctx): 

1490 node = ctx['node'] 

1491 result = [ 

1492 HTML_HEADER % (node.title, generate_head_links(ctx)), 

1493 """<table class="navigation" id="top" width="100%%" cellpadding="2" cellspacing="0"> 

1494 <tr><th valign="middle"><p class="title">%s</p></th></tr> 

1495</table> 

1496<div class="book"> 

1497""" % node.title 

1498 ] 

1499 bookinfo = node.xml.findall('bookinfo')[0] 

1500 result.extend(convert_bookinfo(ctx, bookinfo)) 

1501 result.append("""<div class="toc"> 

1502 <dl class="toc"> 

1503""") 

1504 result.extend(generate_toc(ctx, node.root)) 

1505 result.append("""</dl> 

1506</div> 

1507""") 

1508 result.extend(generate_footer(ctx)) 

1509 result.append("""</div> 

1510</body> 

1511</html>""") 

1512 return result 

1513 

1514 

1515def convert_chapter(ctx): 

1516 return convert_chunk_with_toc(ctx, 'chapter', 'h2') 

1517 

1518 

1519def convert_glossary(ctx): 

1520 node = ctx['node'] 

1521 glossdivs = node.xml.findall('glossdiv') 

1522 

1523 result = [ 

1524 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)), 

1525 generate_alpha_nav(ctx, glossdivs, 'gls', 'glossary'), 

1526 """<div class="glossary"> 

1527<div class="titlepage"><h%1d class="title"> 

1528<a name="%s"></a>%s</h%1d> 

1529</div>""" % (node.depth, get_id(node), node.title, node.depth) 

1530 ] 

1531 for i in glossdivs: 

1532 result.extend(convert_glossdiv(ctx, i)) 

1533 result.extend(generate_footer(ctx)) 

1534 result.append("""</div> 

1535</body> 

1536</html>""") 

1537 return result 

1538 

1539 

1540def convert_index(ctx): 

1541 node = ctx['node'] 

1542 # Get all indexdivs under indexdiv 

1543 indexdivs = [] 

1544 indexdiv = node.xml.find('indexdiv') 

1545 if indexdiv is not None: 

1546 indexdivs = indexdiv.findall('indexdiv') 

1547 

1548 result = [ 

1549 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)), 

1550 generate_alpha_nav(ctx, indexdivs, 'idx', 'index'), 

1551 """<div class="index"> 

1552<div class="titlepage"><h%1d class="title"> 

1553<a name="%s"></a>%s</h%1d> 

1554</div>""" % (node.depth, get_id(node), node.title, node.depth) 

1555 ] 

1556 for i in indexdivs: 

1557 result.extend(convert_indexdiv(ctx, i)) 

1558 result.extend(generate_footer(ctx)) 

1559 result.append("""</div> 

1560</body> 

1561</html>""") 

1562 return result 

1563 

1564 

1565def convert_part(ctx): 

1566 return convert_chunk_with_toc(ctx, 'part', 'h1') 

1567 

1568 

1569def convert_preface(ctx): 

1570 node = ctx['node'] 

1571 result = [ 

1572 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)), 

1573 generate_basic_nav(ctx), 

1574 '<div class="preface">' 

1575 ] 

1576 if node.title: 

1577 result.append(""" 

1578<div class="titlepage"> 

1579<h2 class="title"><a name="%s"></a>%s</h2> 

1580</div>""" % (get_id(node), node.title)) 

1581 convert_inner(ctx, node.xml, result) 

1582 result.extend(generate_footer(ctx)) 

1583 result.append("""</div> 

1584</body> 

1585</html>""") 

1586 return result 

1587 

1588 

1589def convert_reference(ctx): 

1590 return convert_chunk_with_toc(ctx, 'reference', 'h1') 

1591 

1592 

1593def convert_refentry(ctx): 

1594 node = ctx['node'] 

1595 node_id = get_id(node) 

1596 refsect1s = node.xml.findall('refsect1') 

1597 

1598 gallery = '' 

1599 refmeta = node.xml.find('refmeta') 

1600 if refmeta is not None: 

1601 refmiscinfo = refmeta.find('refmiscinfo') 

1602 if refmiscinfo is not None: 

1603 inlinegraphic = refmiscinfo.find('inlinegraphic') 

1604 if inlinegraphic is not None: 

1605 gallery = ''.join(convert_inlinegraphic(ctx, inlinegraphic)) 

1606 

1607 result = [ 

1608 HTML_HEADER % (node.title + ": " + node.root.title, generate_head_links(ctx)) 

1609 ] 

1610 generate_refentry_nav(ctx, refsect1s, result) 

1611 result.append(""" 

1612<div class="refentry"> 

1613<a name="%s"></a> 

1614<div class="refnamediv"> 

1615 <table width="100%%"><tr> 

1616 <td valign="top"> 

1617 <h2><span class="refentrytitle"><a name="%s.top_of_page"></a>%s</span></h2> 

1618 <p>%s — %s</p> 

1619 </td> 

1620 <td class="gallery_image" valign="top" align="right">%s</td> 

1621 </tr></table> 

1622</div> 

1623""" % (node_id, node_id, node.title, node.title, node.subtitle, gallery)) 

1624 

1625 for s in refsect1s: 

1626 result.extend(convert_refsect1(ctx, s)) 

1627 result.extend(generate_footer(ctx)) 

1628 result.append("""</div> 

1629</body> 

1630</html>""") 

1631 return result 

1632 

1633 

1634def convert_section(ctx): 

1635 return convert_chunk_with_toc(ctx, 'section', 'h2') 

1636 

1637 

1638def convert_sect1(ctx): 

1639 return convert_chunk_with_toc(ctx, 'sect1', 'h2') 

1640 

1641 

1642# TODO(ensonic): turn into class with converters as functions and ctx as self 

1643convert_chunks = { 

1644 'book': convert_book, 

1645 'chapter': convert_chapter, 

1646 'glossary': convert_glossary, 

1647 'index': convert_index, 

1648 'part': convert_part, 

1649 'preface': convert_preface, 

1650 'reference': convert_reference, 

1651 'refentry': convert_refentry, 

1652 'section': convert_section, 

1653 'sect1': convert_sect1, 

1654} 

1655 

1656 

1657def generate_nav_nodes(files, node): 

1658 nav = { 

1659 'nav_home': node.root, 

1660 } 

1661 # nav params: up, prev, next 

1662 if node.parent: 

1663 nav['nav_up'] = node.parent 

1664 ix = files.index(node) 

1665 if ix > 0: 

1666 nav['nav_prev'] = files[ix - 1] 

1667 if ix < len(files) - 1: 

1668 nav['nav_next'] = files[ix + 1] 

1669 return nav 

1670 

1671 

1672def convert_content(module, files, node, src_lang): 

1673 converter = convert_chunks.get(node.name) 

1674 if converter is None: 

1675 logging.warning('Add chunk converter for "%s"', node.name) 

1676 return [] 

1677 

1678 ctx = { 

1679 'module': module, 

1680 'files': files, 

1681 'node': node, 

1682 'src-lang': src_lang, 

1683 } 

1684 ctx.update(generate_nav_nodes(files, node)) 

1685 return converter(ctx) 

1686 

1687 

1688def convert(out_dir, module, files, node, src_lang): 

1689 """Convert the docbook chunks to a html file. 

1690 

1691 Args: 

1692 out_dir: already created output dir 

1693 files: list of nodes in the tree in pre-order 

1694 node: current tree node 

1695 """ 

1696 

1697 logging.info('Writing: %s', node.filename) 

1698 with open(os.path.join(out_dir, node.filename), 'wt', 

1699 newline='\n', encoding='utf-8') as html: 

1700 for line in convert_content(module, files, node, src_lang): 

1701 html.write(line) 

1702 

1703 

1704def create_devhelp2_toc(node): 

1705 result = [] 

1706 for c in node.children: 

1707 if c.children: 

1708 result.append('<sub name="%s" link="%s">\n' % (c.raw_title, c.filename)) 

1709 result.extend(create_devhelp2_toc(c)) 

1710 result.append('</sub>\n') 

1711 else: 

1712 result.append('<sub name="%s" link="%s"/>\n' % (c.raw_title, c.filename)) 

1713 return result 

1714 

1715 

1716def create_devhelp2_condition_attribs(node): 

1717 condition = node.attrib.get('condition') 

1718 if condition is not None: 

1719 # condition -> since, deprecated, ... (separated with '|') 

1720 cond = condition.replace('"', '&quot;').split('|') 

1721 keywords = [] 

1722 for c in cond: 

1723 if ':' in c: 

1724 keywords.append('{}="{}"'.format(*c.split(':', 1))) 

1725 else: 

1726 # deprecated can have no description 

1727 keywords.append('{}="{}"'.format(c, '')) 

1728 return ' ' + ' '.join(keywords) 

1729 else: 

1730 return '' 

1731 

1732 

1733def create_devhelp2_refsect2_keyword(node, base_link): 

1734 node_id = node.attrib['id'] 

1735 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % ( 

1736 node.attrib['role'], titles[node_id]['title'], base_link + node_id, 

1737 create_devhelp2_condition_attribs(node)) 

1738 

1739 

1740def create_devhelp2_refsect3_keyword(node, base_link, title, name): 

1741 return' <keyword type="%s" name="%s" link="%s"%s/>\n' % ( 

1742 node.attrib['role'], title, base_link + name, 

1743 create_devhelp2_condition_attribs(node)) 

1744 

1745 

1746def create_devhelp2_content(module, xml, files): 

1747 title = '' 

1748 online_attr = '' 

1749 bookinfo_nodes = xml.xpath('/book/bookinfo') 

1750 if len(bookinfo_nodes): 

1751 bookinfo = bookinfo_nodes[0] 

1752 title = bookinfo.xpath('./title/text()')[0] 

1753 online_url = bookinfo.xpath('./releaseinfo/ulink[@role="online-location"]/@url') 

1754 if len(online_url): 

1755 online_attr = ' online="' + online_url[0] + '"' 

1756 # TODO: support author too (see devhelp2.xsl), it is hardly used though 

1757 # locate *.devhelp2 | xargs grep -Hn --color ' author="[^"]' 

1758 # TODO: fixxref uses '--src-lang' to set the language, we have this in options too 

1759 result = [ 

1760 """<?xml version="1.0" encoding="utf-8" standalone="no"?> 

1761<book xmlns="http://www.devhelp.net/book" title="%s" link="index.html" author="" name="%s" version="2" language="c"%s> 

1762 <chapters> 

1763""" % (title, module, online_attr) 

1764 ] 

1765 # toc 

1766 result.extend(create_devhelp2_toc(files[0].root)) 

1767 result.append(""" </chapters> 

1768 <functions> 

1769""") 

1770 # keywords from all refsect2 and refsect3 

1771 refsect2 = etree.XPath('//refsect2[@role]') 

1772 refsect3_enum = etree.XPath('refsect3[@role="enum_members"]/informaltable/tgroup/tbody/row[@role="constant"]') 

1773 refsect3_enum_details = etree.XPath('entry[@role="enum_member_name"]/para') 

1774 refsect3_struct = etree.XPath('refsect3[@role="struct_members"]/informaltable/tgroup/tbody/row[@role="member"]') 

1775 refsect3_struct_details = etree.XPath('entry[@role="struct_member_name"]/para/structfield') 

1776 for node in files: 

1777 base_link = node.filename + '#' 

1778 refsect2_nodes = refsect2(node.xml) 

1779 for refsect2_node in refsect2_nodes: 

1780 result.append(create_devhelp2_refsect2_keyword(refsect2_node, base_link)) 

1781 refsect3_nodes = refsect3_enum(refsect2_node) 

1782 for refsect3_node in refsect3_nodes: 

1783 details_node = refsect3_enum_details(refsect3_node)[0] 

1784 name = details_node.attrib['id'] 

1785 result.append(create_devhelp2_refsect3_keyword(refsect3_node, base_link, details_node.text, name)) 

1786 refsect3_nodes = refsect3_struct(refsect2_node) 

1787 for refsect3_node in refsect3_nodes: 

1788 details_node = refsect3_struct_details(refsect3_node)[0] 

1789 name = details_node.attrib['id'] 

1790 result.append(create_devhelp2_refsect3_keyword(refsect3_node, base_link, name, name)) 

1791 

1792 result.append(""" </functions> 

1793</book> 

1794""") 

1795 return result 

1796 

1797 

1798def create_devhelp2(out_dir, module, xml, files): 

1799 with open(os.path.join(out_dir, module + '.devhelp2'), 'wt', 

1800 newline='\n', encoding='utf-8') as idx: 

1801 for line in create_devhelp2_content(module, xml, files): 

1802 idx.write(line) 

1803 

1804 

1805class FakeDTDResolver(etree.Resolver): 

1806 """Don't load the docbookx.dtd since we disable the validation anyway. 

1807 

1808 libxsml2 does not cache DTDs. If we produce a docbook file with 100 chunks 

1809 loading such a doc with xincluding will load and parse the docbook DTD 100 

1810 times. This cases tons of memory allocations and is slow. 

1811 """ 

1812 

1813 def resolve(self, url, id, context): 

1814 if not url.endswith('.dtd'): 

1815 return None 

1816 # http://www.oasis-open.org/docbook/xml/4.5/docbookx.dtd 

1817 return self.resolve_string('', context) 

1818 

1819 

1820def main(module, index_file, out_dir, uninstalled, src_lang, paths): 

1821 

1822 # == Loading phase == 

1823 # the next 3 steps could be done in parallel 

1824 

1825 # 1) load the docuemnt 

1826 _t = timer() 

1827 parser = etree.XMLParser(dtd_validation=False, collect_ids=False) 

1828 parser.resolvers.add(FakeDTDResolver()) 

1829 tree = etree.parse(index_file, parser) 

1830 logging.warning("1a: %7.3lf: load doc", timer() - _t) 

1831 _t = timer() 

1832 tree.xinclude() 

1833 logging.warning("1b: %7.3lf: xinclude doc", timer() - _t) 

1834 

1835 # 2) copy datafiles 

1836 _t = timer() 

1837 # TODO: handle additional images 

1838 (gtkdocdir, styledir) = config.get_dirs(uninstalled) 

1839 # copy navigation images and stylesheets to html directory ... 

1840 css_file = os.path.join(styledir, 'style.css') 

1841 for f in glob(os.path.join(styledir, '*.png')) + [css_file]: 

1842 shutil.copy(f, out_dir) 

1843 highlight.append_style_defs(os.path.join(out_dir, 'style.css')) 

1844 logging.warning("2: %7.3lf: copy datafiles", timer() - _t) 

1845 

1846 # 3) load xref targets 

1847 _t = timer() 

1848 # TODO: migrate options from fixxref 

1849 # TODO: ideally explicitly specify the files we need, this will save us the 

1850 # globbing and we'll load less files. 

1851 fixxref.LoadIndicies(out_dir, '/usr/share/gtk-doc/html', []) 

1852 logging.warning("3: %7.3lf: load xrefs", timer() - _t) 

1853 

1854 # == Processing phase == 

1855 

1856 # 4) recursively walk the tree and chunk it into a python tree so that we 

1857 # can generate navigation and link tags. 

1858 _t = timer() 

1859 files = chunk(tree.getroot(), module) 

1860 files = [f for f in PreOrderIter(files) if f.anchor is None] 

1861 logging.warning("4: %7.3lf: chunk doc", timer() - _t) 

1862 

1863 # 5) extract tables: 

1864 _t = timer() 

1865 # TODO: can be done in parallel 

1866 # - find all 'id' attribs and add them to the link map 

1867 # - .. get their titles and store them into the titles map 

1868 add_id_links_and_titles(files, fixxref.Links) 

1869 # - build glossary dict 

1870 build_glossary(files) 

1871 logging.warning("5: %7.3lf: extract tables", timer() - _t) 

1872 

1873 # == Output phase == 

1874 # the next two step could be done in parllel 

1875 

1876 # 6) create a xxx.devhelp2 file 

1877 _t = timer() 

1878 create_devhelp2(out_dir, module, tree.getroot(), files) 

1879 logging.warning("6: %7.3lf: create devhelp2", timer() - _t) 

1880 

1881 # 7) iterate the tree and output files 

1882 _t = timer() 

1883 # TODO: can be done in parallel, figure out why this is not faster 

1884 # from multiprocessing.pool import Pool 

1885 # with Pool(4) as p: 

1886 # p.apply_async(convert, args=(out_dir, module, files)) 

1887 # from multiprocessing.pool import ThreadPool 

1888 # with ThreadPool(4) as p: 

1889 # p.apply_async(convert, args=(out_dir, module, files)) 

1890 for node in files: 

1891 convert(out_dir, module, files, node, src_lang) 

1892 logging.warning("7: %7.3lf: create html", timer() - _t) 

1893 

1894 # 8) copy assets over 

1895 _t = timer() 

1896 paths = set(paths + [os.getcwd()]) 

1897 for a in assets: 

1898 logging.info('trying %s in %s', a, str(paths)) 

1899 copied = False 

1900 for p in paths: 

1901 try: 

1902 shutil.copy(os.path.join(p, a), out_dir) 

1903 copied = True 

1904 except FileNotFoundError: 

1905 pass 

1906 if not copied: 

1907 logging.warning('file %s not found in path (did you add --path?)', a) 

1908 logging.warning("8: %7.3lf: copy assets", timer() - _t) 

1909 

1910 

1911def run(options): 

1912 logging.info('options: %s', str(options.__dict__)) 

1913 module = options.args[0] 

1914 document = options.args[1] 

1915 

1916 output_dir = options.output_dir or os.getcwd() 

1917 if options.output_dir and not os.path.isdir(output_dir): 

1918 os.mkdir(output_dir) 

1919 

1920 # TODO: pass options.extra_dir 

1921 sys.exit(main(module, document, output_dir, options.uninstalled, options.src_lang, 

1922 options.path)) 

1923 

1924 

1925def new_args_parser(): 

1926 parser = argparse.ArgumentParser( 

1927 description=f"gtkdoc-mkhtml version {config.version} - generate documentation in html format" 

1928 ) 

1929 parser.add_argument("--version", action="version", version=config.version) 

1930 parser.add_argument( 

1931 "--output-dir", default=".", help="The directory where the results are stored" 

1932 ) 

1933 parser.add_argument( 

1934 "--path", default=[], action="append", help="Extra source directories" 

1935 ) 

1936 parser.add_argument( 

1937 "--extra-dir", 

1938 default=[], 

1939 action="append", 

1940 help="Directories to recursively scan for indices (*.devhelp2)" 

1941 "in addition to HTML_DIR", 

1942 ) 

1943 parser.add_argument( 

1944 "--src-lang", 

1945 default="c", 

1946 help="Programing language used for syntax highlighting. This" 

1947 "can be any language pygments supports.", 

1948 ) 

1949 parser.add_argument("args", nargs="*", help="MODULE DRIVER_FILE") 

1950 # TODO: only for testing, replace with env-var 

1951 parser.add_argument("--uninstalled", action="store_true", default=False, help="???") 

1952 return parser