>b's YML 2
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

249 lines
9.3 KiB

6 years ago
2 years ago
6 years ago
6 years ago
6 years ago
2 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. #!/usr/bin/env python3
  2. # vim: set fileencoding=utf-8 :
  3. """\
  4. YML/YSLT 2 processor version 6.2
  5. Copyleft (c), 2009-2020 Volker Birk http://fdik.org/yml/
  6. """
  7. import sys, os, codecs, locale
  8. import fileinput, unicodedata
  9. from optparse import OptionParser
  10. try:
  11. from lxml import etree
  12. except:
  13. sys.stderr.write("This program needs lxml, see http://codespeak.net/lxml/\n")
  14. sys.exit(1)
  15. from yml2 import ymlCStyle, comment, oldSyntax
  16. from yml2.pyPEG import parse, u
  17. import yml2.backend as backend
  18. YML_DEFAULT_PATH = [os.path.dirname(backend.__file__)]
  19. def printInfo(option, opt_str, value, parser):
  20. sys.stdout.write(__doc__)
  21. sys.exit(0)
  22. class YMLAssert(Exception): pass
  23. def w(msg):
  24. if isinstance(msg, BaseException):
  25. try:
  26. msg = str(msg) + "\n"
  27. except:
  28. msg = u(msg) + "\n"
  29. sys.stderr.write(msg)
  30. def main():
  31. optParser = OptionParser()
  32. optParser.add_option("-C", "--old-syntax", action="store_true", dest="old_syntax",
  33. help="syntax of YML 2 version 1.x (compatibility mode)", default=False)
  34. optParser.add_option("-D", "--emit-linenumbers", action="store_true", dest="emitlinenumbers",
  35. help="emit line numbers into the resulting XML for debugging purposes", default=False)
  36. optParser.add_option("--debug", action="store_true", dest="trace",
  37. help="switch on tracing to stderr", default=False)
  38. optParser.add_option("-d", "--paramdict", dest="params", metavar="PARAMS",
  39. help="call X/YSLT script with dictionary PARAMS as parameters")
  40. optParser.add_option("-e", "--xpath", dest="xpath", metavar="XPATH",
  41. help="execute XPath expression XPATH and print result")
  42. optParser.add_option("-E", "--encoding", dest="encoding", metavar="ENCODING", default=locale.getdefaultlocale()[1],
  43. help="encoding of input files (default to locale)")
  44. optParser.add_option("-I", "--include", dest="includePathText", metavar="INCLUDE_PATH",
  45. help="precede YML_PATH by a colon separated INCLUDE_PATH to search for include files")
  46. optParser.add_option("-m", "--omit-empty-parm-tags", action="store_true", dest="omitemptyparm",
  47. help="does nothing (only there for compatibility reasons)", default=False)
  48. optParser.add_option("-M", "--empty-input-document", action="store_true", dest="emptyinput",
  49. help="use an empty input document", default=False)
  50. optParser.add_option("-n", "--normalization", dest="normalization", metavar="NORMALIZATION", default="NFC",
  51. help="Unicode normalization (none, NFD, NFKD, NFC, NFKC, FCD, default is NFC)")
  52. optParser.add_option("-o", "--output", dest="outputFile", metavar="FILE",
  53. help="place output in file FILE")
  54. optParser.add_option("-p", "--parse-only", action="store_true", dest="parseonly",
  55. help="parse only, then output pyAST as text to stdout", default=False)
  56. optParser.add_option("-P", "--pretty", action="store_true", default=False,
  57. help="pretty print output adding whitespace")
  58. optParser.add_option("-s", "--stringparamdict", dest="stringparams", metavar="STRINGPARAMS",
  59. help="call X/YSLT script with dictionary STRINGPARAMS as string parameters")
  60. optParser.add_option("-x", "--xml", action="store_true", default=False,
  61. help="input document is XML already")
  62. optParser.add_option("-X", "--xslt", dest="xslt", metavar="XSLTSCRIPT",
  63. help="execute XSLT script XSLTSCRIPT")
  64. optParser.add_option("-y", "--yslt", dest="yslt", metavar="YSLTSCRIPT",
  65. help="execute YSLT script YSLTSCRIPT")
  66. optParser.add_option("-Y", "--xml2yml", action="store_true", default=False,
  67. help="convert XML to normalized YML code")
  68. optParser.add_option("-V", "--version", action="callback", callback=printInfo, help="show version info and exit")
  69. (options, args) = optParser.parse_args()
  70. if options.old_syntax:
  71. oldSyntax()
  72. if options.trace:
  73. backend.enable_tracing = True
  74. if options.emitlinenumbers:
  75. backend.emitlinenumbers = True
  76. if options.includePathText:
  77. backend.includePath = options.includePathText.split(':')
  78. backend.encoding = options.encoding
  79. dirs = os.environ.get('YML_PATH', '.').split(':') + YML_DEFAULT_PATH
  80. backend.includePath.extend(dirs)
  81. if options.xml2yml:
  82. for directory in backend.includePath:
  83. name = os.path.join(directory, "xml2yml.ysl2")
  84. if os.path.isfile(name):
  85. options.yslt = name
  86. options.xml = True
  87. break
  88. else:
  89. sys.stderr.write("Error: Stylesheet xml2yml.ysl2 required for --xml2yml not found\n")
  90. sys.stderr.write("Please check your YML_PATH\n")
  91. sys.exit(1)
  92. if (options.xslt and options.yslt) or (options.xslt and options.xpath) or (options.yslt and options.xpath):
  93. sys.stderr.write("Cannot combine --xpath, --xslt and --yslt params\n")
  94. sys.exit(1)
  95. try:
  96. ymlC = ymlCStyle()
  97. rtext = ""
  98. if not options.emptyinput:
  99. files = fileinput.input(args, mode="rU", openhook=fileinput.hook_encoded(options.encoding))
  100. if options.xml:
  101. rtext = ""
  102. for line in files:
  103. rtext += line
  104. else:
  105. result = parse(ymlC, files, True, comment)
  106. if options.parseonly:
  107. print(result)
  108. sys.exit(0)
  109. else:
  110. rtext = backend.finish(result)
  111. if not rtext:
  112. rtext = "<empty/>"
  113. def ymldebug(context, text):
  114. if options.trace:
  115. sys.stderr.write("Debug: " + codecs.encode(u(text), options.encoding) + "\n")
  116. return ""
  117. def ymlassert(context, value, msg):
  118. if options.trace:
  119. if not value:
  120. raise YMLAssert(msg)
  121. return ""
  122. ymlns = etree.FunctionNamespace("http://fdik.org/yml")
  123. ymlns.prefix = "yml"
  124. ymlns['debug'] = ymldebug
  125. ymlns['assert'] = ymlassert
  126. if options.xpath:
  127. tree = etree.fromstring(rtext)
  128. ltree = tree.xpath(codecs.decode(options.xpath, options.encoding))
  129. rtext = ""
  130. try:
  131. for rtree in ltree:
  132. rtext += etree.tostring(rtree, pretty_print=options.pretty, encoding=unicode)
  133. except:
  134. rtext = ltree
  135. elif options.yslt or options.xslt:
  136. params = {}
  137. if options.yslt:
  138. backend.clearAll()
  139. yscript = fileinput.input(options.yslt, mode="rU", openhook=fileinput.hook_encoded(options.encoding))
  140. yresult = parse(ymlC, yscript, True, comment)
  141. ytext = backend.finish(yresult)
  142. else:
  143. yscript = fileinput.input(options.xslt, mode="rU")
  144. ytext = ""
  145. for line in yscript:
  146. ytext += line
  147. doc = etree.fromstring(rtext)
  148. xsltree = etree.XML(ytext, base_url=os.path.abspath(yscript.filename()))
  149. transform = etree.XSLT(xsltree)
  150. if options.params:
  151. params = eval(options.params)
  152. for key, value in params.iteritems():
  153. if type(value) is not str:
  154. params[key] = u(value)
  155. if options.stringparams:
  156. for key, value in eval(options.stringparams).iteritems():
  157. params[key] = "'" + u(value) + "'"
  158. rresult = transform(doc, **params)
  159. # lxml is somewhat buggy
  160. try:
  161. rtext = u(rresult)
  162. except:
  163. rtext = etree.tostring(rresult, encoding=unicode)
  164. if not rtext:
  165. rtext = codecs.decode(str(rresult), "utf-8")
  166. if options.normalization != "none":
  167. rtext = unicodedata.normalize(options.normalization, rtext)
  168. if options.pretty:
  169. plaintext = etree.tostring(etree.fromstring(rtext), pretty_print=True, xml_declaration=True, encoding=options.encoding)
  170. else:
  171. if isinstance(rtext, str):
  172. plaintext = codecs.encode(rtext, options.encoding)
  173. else:
  174. plaintext = rtext
  175. try:
  176. if plaintext[-1] == "\n":
  177. plaintext = plaintext[:-1]
  178. except: pass
  179. if options.outputFile and options.outputFile != "-":
  180. outfile = open(options.outputFile, "wb")
  181. outfile.write(plaintext)
  182. outfile.close()
  183. else:
  184. sys.stdout.buffer.write(plaintext)
  185. if not options.pretty:
  186. print()
  187. except KeyboardInterrupt:
  188. w("\n")
  189. sys.exit(1)
  190. except YMLAssert as msg:
  191. w("YML Assertion failed: " + u(msg) + "\n")
  192. sys.exit(2)
  193. except KeyError as msg:
  194. w("not found: " + u(msg) + "\n")
  195. sys.exit(4)
  196. except LookupError as msg:
  197. w("not found: " + u(msg) + "\n")
  198. sys.exit(4)
  199. except etree.XMLSyntaxError as e:
  200. log = e.error_log.filter_from_level(etree.ErrorLevels.FATAL)
  201. for entry in log:
  202. w("XML error: " + u(entry.message) + "\n")
  203. sys.exit(5)
  204. except Exception as msg:
  205. w(msg)
  206. sys.exit(5)
  207. if __name__ == "__main__":
  208. main()