update-error-constants.py 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. #!/usr/bin/env python
  2. import sys, os, os.path, re, codecs
  3. BUILD_SOURCE_FILE = os.path.join("src", "lxml", "xmlerror.pxi")
  4. BUILD_DEF_FILE = os.path.join("src", "lxml", "includes", "xmlerror.pxd")
  5. if len(sys.argv) < 2 or sys.argv[1].lower() in ('-h', '--help'):
  6. print("This script generates the constants in file %s" % BUILD_SOURCE_FILE)
  7. print("Call as")
  8. print(sys.argv[0], "/path/to/libxml2-doc-dir")
  9. sys.exit(len(sys.argv) > 1)
  10. HTML_DIR = os.path.join(sys.argv[1], 'html')
  11. os.stat(HTML_DIR) # raise an error if we can't find it
  12. sys.path.insert(0, 'src')
  13. from lxml import etree
  14. # map enum name to Python variable name and alignment for constant name
  15. ENUM_MAP = {
  16. 'xmlErrorLevel' : ('__ERROR_LEVELS', 'XML_ERR_'),
  17. 'xmlErrorDomain' : ('__ERROR_DOMAINS', 'XML_FROM_'),
  18. 'xmlParserErrors' : ('__PARSER_ERROR_TYPES', 'XML_'),
  19. # 'xmlXPathError' : ('__XPATH_ERROR_TYPES', ''),
  20. # 'xmlSchemaValidError' : ('__XMLSCHEMA_ERROR_TYPES', 'XML_'),
  21. 'xmlRelaxNGValidErr' : ('__RELAXNG_ERROR_TYPES', 'XML_'),
  22. }
  23. ENUM_ORDER = (
  24. 'xmlErrorLevel',
  25. 'xmlErrorDomain',
  26. 'xmlParserErrors',
  27. # 'xmlXPathError',
  28. # 'xmlSchemaValidError',
  29. 'xmlRelaxNGValidErr')
  30. COMMENT = """
  31. # This section is generated by the script '%s'.
  32. """ % os.path.basename(sys.argv[0])
  33. def split(lines):
  34. lines = iter(lines)
  35. pre = []
  36. for line in lines:
  37. pre.append(line)
  38. if line.startswith('#') and "BEGIN: GENERATED CONSTANTS" in line:
  39. break
  40. pre.append('')
  41. for line in lines:
  42. if line.startswith('#') and "END: GENERATED CONSTANTS" in line:
  43. break
  44. post = ['', line]
  45. post.extend(lines)
  46. post.append('')
  47. return pre, post
  48. def regenerate_file(filename, result):
  49. # read .pxi source file
  50. f = codecs.open(filename, 'r', encoding="utf-8")
  51. pre, post = split(f)
  52. f.close()
  53. # write .pxi source file
  54. f = codecs.open(filename, 'w', encoding="utf-8")
  55. f.write(''.join(pre))
  56. f.write(COMMENT)
  57. f.write('\n'.join(result))
  58. f.write(''.join(post))
  59. f.close()
  60. collect_text = etree.XPath("string()")
  61. find_enums = etree.XPath(
  62. "//html:pre[@class = 'programlisting' and contains(text(), 'Enum')]",
  63. namespaces = {'html' : 'http://www.w3.org/1999/xhtml'})
  64. def parse_enums(html_dir, html_filename, enum_dict):
  65. PARSE_ENUM_NAME = re.compile('\s*enum\s+(\w+)\s*{', re.I).match
  66. PARSE_ENUM_VALUE = re.compile('\s*=\s+([0-9]+)\s*(?::\s*(.*))?').match
  67. tree = etree.parse(os.path.join(html_dir, html_filename))
  68. enums = find_enums(tree)
  69. for enum in enums:
  70. enum_name = PARSE_ENUM_NAME(collect_text(enum))
  71. if not enum_name:
  72. continue
  73. enum_name = enum_name.group(1)
  74. if enum_name not in ENUM_MAP:
  75. continue
  76. print("Found enum", enum_name)
  77. entries = []
  78. for child in enum:
  79. name = child.text
  80. match = PARSE_ENUM_VALUE(child.tail)
  81. if not match:
  82. print("Ignoring enum %s (failed to parse field '%s')" % (
  83. enum_name, name))
  84. break
  85. value, descr = match.groups()
  86. entries.append((name, int(value), descr))
  87. else:
  88. enum_dict[enum_name] = entries
  89. return enum_dict
  90. enum_dict = {}
  91. parse_enums(HTML_DIR, 'libxml-xmlerror.html', enum_dict)
  92. #parse_enums(HTML_DIR, 'libxml-xpath.html', enum_dict)
  93. #parse_enums(HTML_DIR, 'libxml-xmlschemas.html', enum_dict)
  94. parse_enums(HTML_DIR, 'libxml-relaxng.html', enum_dict)
  95. # regenerate source files
  96. pxi_result = []
  97. append_pxi = pxi_result.append
  98. pxd_result = []
  99. append_pxd = pxd_result.append
  100. append_pxd('cdef extern from "libxml/xmlerror.h":')
  101. append_pxi('''\
  102. # Constants are stored in tuples of strings, for which Cython generates very
  103. # efficient setup code. To parse them, iterate over the tuples and parse each
  104. # line in each string independently. Tuples of strings (instead of a plain
  105. # string) are required as some C-compilers of a certain well-known OS vendor
  106. # cannot handle strings that are a few thousand bytes in length.
  107. ''')
  108. ctypedef_indent = ' '*4
  109. constant_indent = ctypedef_indent*2
  110. for enum_name in ENUM_ORDER:
  111. constants = enum_dict[enum_name]
  112. pxi_name, prefix = ENUM_MAP[enum_name]
  113. append_pxd(ctypedef_indent + 'ctypedef enum %s:' % enum_name)
  114. append_pxi('cdef object %s = (u"""\\' % pxi_name)
  115. prefix_len = len(prefix)
  116. length = 2 # each string ends with '\n\0'
  117. for name, val, descr in constants:
  118. if descr and descr != str(val):
  119. line = '%-50s = %7d # %s' % (name, val, descr)
  120. else:
  121. line = '%-50s = %7d' % (name, val)
  122. append_pxd(constant_indent + line)
  123. if name[:prefix_len] == prefix and len(name) > prefix_len:
  124. name = name[prefix_len:]
  125. line = '%s=%d' % (name, val)
  126. if length + len(line) >= 2040: # max string length in MSVC is 2048
  127. append_pxi('""",')
  128. append_pxi('u"""\\')
  129. length = 2 # each string ends with '\n\0'
  130. append_pxi(line)
  131. length += len(line) + 2 # + '\n\0'
  132. append_pxd('')
  133. append_pxi('""",)')
  134. append_pxi('')
  135. # write source files
  136. print("Updating file %s" % BUILD_SOURCE_FILE)
  137. regenerate_file(BUILD_SOURCE_FILE, pxi_result)
  138. print("Updating file %s" % BUILD_DEF_FILE)
  139. regenerate_file(BUILD_DEF_FILE, pxd_result)
  140. print("Done")