mkhtml.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. from docstructure import SITE_STRUCTURE, HREF_MAP, BASENAME_MAP
  2. from lxml.etree import (parse, fromstring, ElementTree,
  3. Element, SubElement, XPath)
  4. import os, shutil, re, sys, copy, time
  5. RST2HTML_OPTIONS = " ".join([
  6. '--no-toc-backlinks',
  7. '--strip-comments',
  8. '--language en',
  9. '--date',
  10. ])
  11. htmlnsmap = {"h" : "http://www.w3.org/1999/xhtml"}
  12. find_title = XPath("/h:html/h:head/h:title/text()", namespaces=htmlnsmap)
  13. find_title_tag = XPath("/h:html/h:head/h:title", namespaces=htmlnsmap)
  14. find_headings = XPath("//h:h1[not(@class)]//text()", namespaces=htmlnsmap)
  15. find_menu = XPath("//h:ul[@id=$name]", namespaces=htmlnsmap)
  16. find_page_end = XPath("/h:html/h:body/h:div[last()]", namespaces=htmlnsmap)
  17. find_words = re.compile('(\w+)').findall
  18. replace_invalid = re.compile(r'[-_/.\s\\]').sub
  19. def make_menu_section_head(section, menuroot):
  20. section_id = section + '-section'
  21. section_head = menuroot.xpath("//ul[@id=$section]/li", section=section_id)
  22. if not section_head:
  23. ul = SubElement(menuroot, "ul", id=section_id)
  24. section_head = SubElement(ul, "li")
  25. title = SubElement(section_head, "span", {"class":"section title"})
  26. title.text = section
  27. else:
  28. section_head = section_head[0]
  29. return section_head
  30. def build_menu(tree, basename, section_head):
  31. page_title = find_title(tree)
  32. if page_title:
  33. page_title = page_title[0]
  34. else:
  35. page_title = replace_invalid('', basename.capitalize())
  36. build_menu_entry(page_title, basename+".html", section_head,
  37. headings=find_headings(tree))
  38. def build_menu_entry(page_title, url, section_head, headings=None):
  39. page_id = replace_invalid(' ', os.path.splitext(url)[0]) + '-menu'
  40. ul = SubElement(section_head, "ul", {"class":"menu foreign", "id":page_id})
  41. title = SubElement(ul, "li", {"class":"menu title"})
  42. a = SubElement(title, "a", href=url)
  43. a.text = page_title
  44. if headings:
  45. subul = SubElement(title, "ul", {"class":"submenu"})
  46. for heading in headings:
  47. li = SubElement(subul, "li", {"class":"menu item"})
  48. ref = '-'.join(find_words(replace_invalid(' ', heading.lower())))
  49. a = SubElement(li, "a", href=url+'#'+ref)
  50. a.text = heading
  51. def merge_menu(tree, menu, name):
  52. menu_root = copy.deepcopy(menu)
  53. tree.getroot()[1][0].insert(0, menu_root) # html->body->div[class=document]
  54. for el in menu_root.iter():
  55. tag = el.tag
  56. if tag[0] != '{':
  57. el.tag = "{http://www.w3.org/1999/xhtml}" + tag
  58. current_menu = find_menu(
  59. menu_root, name=replace_invalid(' ', name + '-menu'))
  60. if not current_menu:
  61. current_menu = find_menu(
  62. menu_root, name=replace_invalid('-', name + '-menu'))
  63. if current_menu:
  64. for submenu in current_menu:
  65. submenu.set("class", submenu.get("class", "").
  66. replace("foreign", "current"))
  67. return tree
  68. def rest2html(script, source_path, dest_path, stylesheet_url):
  69. command = ('%s %s %s --stylesheet=%s --link-stylesheet %s > %s' %
  70. (sys.executable, script, RST2HTML_OPTIONS,
  71. stylesheet_url, source_path, dest_path))
  72. os.system(command)
  73. def publish(dirname, lxml_path, release):
  74. if not os.path.exists(dirname):
  75. os.mkdir(dirname)
  76. doc_dir = os.path.join(lxml_path, 'doc')
  77. script = os.path.join(doc_dir, 'rest2html.py')
  78. pubkey = os.path.join(doc_dir, 'pubkey.asc')
  79. stylesheet_url = 'style.css'
  80. shutil.copy(pubkey, dirname)
  81. href_map = HREF_MAP.copy()
  82. changelog_basename = 'changes-%s' % release
  83. href_map['Release Changelog'] = changelog_basename + '.html'
  84. trees = {}
  85. menu = Element("div", {"class":"sidemenu"})
  86. # build HTML pages and parse them back
  87. for section, text_files in SITE_STRUCTURE:
  88. section_head = make_menu_section_head(section, menu)
  89. for filename in text_files:
  90. if filename.startswith('@'):
  91. # special menu entry
  92. page_title = filename[1:]
  93. url = href_map[page_title]
  94. build_menu_entry(page_title, url, section_head)
  95. else:
  96. path = os.path.join(doc_dir, filename)
  97. basename = os.path.splitext(os.path.basename(filename))[0]
  98. basename = BASENAME_MAP.get(basename, basename)
  99. outname = basename + '.html'
  100. outpath = os.path.join(dirname, outname)
  101. rest2html(script, path, outpath, stylesheet_url)
  102. tree = parse(outpath)
  103. trees[filename] = (tree, basename, outpath)
  104. build_menu(tree, basename, section_head)
  105. # also convert CHANGES.txt
  106. rest2html(script,
  107. os.path.join(lxml_path, 'CHANGES.txt'),
  108. os.path.join(dirname, 'changes-%s.html' % release),
  109. '')
  110. # integrate menu
  111. for tree, basename, outpath in trees.itervalues():
  112. new_tree = merge_menu(tree, menu, basename)
  113. title = find_title_tag(new_tree)
  114. if title and title[0].text == 'lxml':
  115. title[0].text = "lxml - Processing XML and HTML with Python"
  116. new_tree.write(outpath)
  117. if __name__ == '__main__':
  118. publish(sys.argv[1], sys.argv[2], sys.argv[3])