Next in thread → Next in month →

Re: [docbook] DB4 to DB5 conversion stylesheet

From
Alexey Neyman <>
Date
2013-04-22T18:05:11+00:00
ID
3798954.fu5rIdndlS@mistral
Thread
Re: [docbook] DB4 to DB5 conversion stylesheet
#!/usr/bin/python3

import sys
import re
import argparse

def do_cloak(ns, old, force):

	def pi(s):
		return '<?' + ns.pi_wrap + ' ' + s + '?>'

	def xml_escape(s):
		escapes = { '<' : 'LT', '>' : 'GT', '&' : 'AMP' }
		return re.sub(r'[<>&]', lambda x: pi('char ' + escapes[x.group()]), s)

	def make_escapes(dtd, s):
		if dtd != '':
			s = s.replace(dtd, xml_escape(dtd))
		s = re.sub(r'\<\?xml(.*?)\?\>', lambda m: pi('xml-pi !' + m.group(1)), s, flags=re.DOTALL)
		s = re.sub(r'\<\!\[CDATA\[(.*?)\]\]\>', lambda m: pi('cdata-start') + xml_escape(m.group(1)) + pi('cdata-end'), s, flags=re.DOTALL)
		# See NameStartChar/NameChar in XML spec; sorry for non-English entity names - add them if you use them.
		s = re.sub(r'\&([:_A-Za-z][-.:_A-Za-z0-9]*);', lambda m: pi('entity ' + m.group(1)), s)
		return s

	def find_dtd(s):
		if ns.no_dtd:
			return ""
		if "<!DOCTYPE" not in s:
			return ""
		# TBD: will fail if internal subset contains closing bracket followed by closing angle bracket (r'\]\s*\>'), e.g. inside an entity
		m = re.search(r'\<\!DOCTYPE\s+\S+\s+(SYSTEM\s+([\'"]).*?\2|PUBLIC\s+([\'"]).*?\3\s+([\'"]).*?\4)?\s*(\[.*?\])?\s*\>', s, flags=re.DOTALL)
		if m:
			return m.group()
		sys.stderr.write("DOCTYPE seen but not understood\n")


	def find_xmldecl(s):
		if ns.no_xmldecl:
			return ""
		m = re.search(r'\<\?xml\s+.*?\?\>', s)
		if m is None:
			return ""
		return m.group(0)

	def tmp_wrap(s):
		return ("<%s:wrapper xmlns:%s='%s'>" % (ns.wrap_pfx, ns.wrap_pfx, ns.wrap_uri)) + s + ("</%s:wrapper>" % ns.wrap_pfx)

	if not(force) and pi('cloak-marker') in old:
		return None
	xmldecl = find_xmldecl(old)
	dtd = find_dtd(old)
	if dtd is None:
		return None
	return xmldecl + dtd + tmp_wrap(make_escapes(dtd, old) + pi('cloak-marker'))

def do_uncloak(ns, old):
	escapes = { 'LT' : '<', 'GT' : '>', 'AMP' : '&' }
	pis = {
			'xml-pi' : lambda s: "<?xml%s?>" % s[1:],
			'char': lambda s: escapes[s],
			'entity': lambda s: "&%s;" % s,
			'cdata-start': lambda s: '<![CDATA[',
			'cdata-end': lambda s: ']]>',
			'cloak-marker': lambda s: ''
	}

	# Find the temporary wrapper
	start = old.find("<%s:wrapper" % ns.wrap_pfx)
	if start == -1:
		return None
	start = old.find(">", start)
	if start == -1:
		return None
	start += 1
	end = old.rfind("</%s:wrapper>" % ns.wrap_pfx)
	if end == -1 or end <= start:
		return None
	new = re.sub(r'\<\?' + ns.pi_wrap + r'\s+(?P<pi>\S+?)(\s+(?P<arg>.*?))?\?\>', lambda m: pis[m.group('pi')](m.group('arg')), old[start:end])
	return new

if __name__ == '__main__':
	parser = argparse.ArgumentParser(description='Cloak/uncloak entities and other elements before XSLT processing')
	parser.add_argument('--cloak', action='store_true', help='Cloak document (default: determine automatically)')
	parser.add_argument('--uncloak', action='store_true', help='Uncloak document (default: determine automatically)')
	parser.add_argument('--pi-wrap', metavar='PI', default='xxx-escape', help='Name of PI used to escape elements')
	parser.add_argument('--wrap-pfx', metavar='PFX', default='tmp', help='Namespace prefix for top-level wrapper element')
	parser.add_argument('--wrap-uri', metavar='URI', default='http://localhost/temporary-wrapper-namespace', help='Namespace URI for top-level wrapper element')
	parser.add_argument('--no-dtd', action='store_true', help='Do not create a copy of DTD declaration')
	parser.add_argument('--no-xmldecl', action='store_true', help='Do not create a copy of XML declaration')
	parser.add_argument('infile', nargs='?', type=argparse.FileType('r'), default=sys.stdin, help='Input (stdin if omitted)')
	parser.add_argument('outfile', nargs='?', type=argparse.FileType('w'), default=sys.stdout, help='Output (stdout if omitted)')
	ns = parser.parse_args()
	old = ns.infile.read()
	if ns.uncloak:
		new = do_uncloak(ns, old)
	else:
		new = do_cloak(ns, old, ns.cloak)
		if new is None:
			new = do_uncloak(ns, old)
	if new is None:
		sys.stderr.write("script cannot handle input\n")
		sys.exit(1)
	ns.outfile.write(new)
Next in thread → Next in month →