17842e56b97ce677b83bdab09cda48bc2d89ac75aJust"""xmlWriter.py -- Simple XML authoring class"""
27842e56b97ce677b83bdab09cda48bc2d89ac75aJust
31ae29591efbb29492ce05378909ccf4028d7c1eeBehdad Esfahbodfrom __future__ import print_function, division, absolute_import
430e691edd056ba22fa8970280e986747817bec3dBehdad Esfahbodfrom fontTools.misc.py23 import *
55cf40083364e1d2dce119de25cb42ce69d2fb53cBehdad Esfahbodimport sys
67842e56b97ce677b83bdab09cda48bc2d89ac75aJustimport string
77842e56b97ce677b83bdab09cda48bc2d89ac75aJust
87842e56b97ce677b83bdab09cda48bc2d89ac75aJustINDENT = "  "
97842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1081b0c2b659aec8df184d09c0d7ab069956b87b28jvr
11e388db566b9ba42669c7e353db4293cf27bc2a5bBehdad Esfahbodclass XMLWriter(object):
127842e56b97ce677b83bdab09cda48bc2d89ac75aJust
136962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod	def __init__(self, fileOrPath, indentwhite=INDENT, idlefunc=None):
1490beb95b77fdef99cad25354f36bf615d2042197jvr		if not hasattr(fileOrPath, "write"):
156962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod			try:
166962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod				# Python3 has encoding support.
176962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod				self.file = open(fileOrPath, "w", encoding="utf-8")
18c40e26ec4c40f3e9c60248d1663b8bedd7d706f2Behdad Esfahbod			except TypeError:
19c40e26ec4c40f3e9c60248d1663b8bedd7d706f2Behdad Esfahbod				self.file = open(fileOrPath, "w")
207842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
217842e56b97ce677b83bdab09cda48bc2d89ac75aJust			# assume writable file object
2290beb95b77fdef99cad25354f36bf615d2042197jvr			self.file = fileOrPath
237842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.indentwhite = indentwhite
247842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.indentlevel = 0
257842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.stack = []
267842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.needindent = 1
2733f3327ad74d4550ee48ce85ce52a7374e55b997jvr		self.idlefunc = idlefunc
2833f3327ad74d4550ee48ce85ce52a7374e55b997jvr		self.idlecounter = 0
296962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw('<?xml version="1.0" encoding="utf-8"?>')
307842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.newline()
317842e56b97ce677b83bdab09cda48bc2d89ac75aJust
327842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def close(self):
337842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.close()
347842e56b97ce677b83bdab09cda48bc2d89ac75aJust
356962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod	def write(self, string, indent=True):
366962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		"""Writes text."""
376962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw(escape(string), indent=indent)
386962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod
396962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod	def writecdata(self, string):
406962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		"""Writes text in a CDATA section."""
416962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw("<![CDATA[" + string + "]]>")
426962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod
431edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod	def write8bit(self, data, strip=False):
446962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		"""Writes a bytes() sequence into the XML, escaping
456962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		non-ASCII bytes.  When this is read in xmlReader,
466962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		the original bytes can be recovered by encoding to
47ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod		'latin-1'."""
481edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod		self._writeraw(escape8bit(data), strip=strip)
49ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod
501edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod	def write16bit(self, data, strip=False):
511edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod		self._writeraw(escape16bit(data), strip=strip)
526962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod
536962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod	def write_noindent(self, string):
546962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		"""Writes text without indentation."""
556962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw(escape(string), indent=False)
566962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod
571edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod	def _writeraw(self, data, indent=True, strip=False):
586962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		"""Writes bytes, possibly indented."""
596962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		if indent and self.needindent:
607842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.file.write(self.indentlevel * self.indentwhite)
617842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.needindent = 0
621edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod		s = tostr(data, encoding="utf-8")
631edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod		if (strip):
641edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod			s = s.strip()
651edfe576563d4e260a958301eeeb956adc7d0610Behdad Esfahbod		self.file.write(s)
667842e56b97ce677b83bdab09cda48bc2d89ac75aJust
677842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def newline(self):
687842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.file.write("\n")
697842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.needindent = 1
7033f3327ad74d4550ee48ce85ce52a7374e55b997jvr		idlecounter = self.idlecounter
7133f3327ad74d4550ee48ce85ce52a7374e55b997jvr		if not idlecounter % 100 and self.idlefunc is not None:
7233f3327ad74d4550ee48ce85ce52a7374e55b997jvr			self.idlefunc()
7333f3327ad74d4550ee48ce85ce52a7374e55b997jvr		self.idlecounter = idlecounter + 1
747842e56b97ce677b83bdab09cda48bc2d89ac75aJust
757842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def comment(self, data):
767842e56b97ce677b83bdab09cda48bc2d89ac75aJust		data = escape(data)
7714fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod		lines = data.split("\n")
786962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw("<!-- " + lines[0])
797842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for line in lines[1:]:
807842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.newline()
816962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod			self._writeraw("     " + line)
826962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw(" -->")
837842e56b97ce677b83bdab09cda48bc2d89ac75aJust
847842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def simpletag(self, _TAG_, *args, **kwargs):
8566214cbe8c220625e61a85f386756c6de4ec82b2Behdad Esfahbod		attrdata = self.stringifyattrs(*args, **kwargs)
867842e56b97ce677b83bdab09cda48bc2d89ac75aJust		data = "<%s%s/>" % (_TAG_, attrdata)
876962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw(data)
887842e56b97ce677b83bdab09cda48bc2d89ac75aJust
897842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def begintag(self, _TAG_, *args, **kwargs):
9066214cbe8c220625e61a85f386756c6de4ec82b2Behdad Esfahbod		attrdata = self.stringifyattrs(*args, **kwargs)
917842e56b97ce677b83bdab09cda48bc2d89ac75aJust		data = "<%s%s>" % (_TAG_, attrdata)
926962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw(data)
937842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.stack.append(_TAG_)
947842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.indent()
957842e56b97ce677b83bdab09cda48bc2d89ac75aJust
967842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def endtag(self, _TAG_):
977842e56b97ce677b83bdab09cda48bc2d89ac75aJust		assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
987842e56b97ce677b83bdab09cda48bc2d89ac75aJust		del self.stack[-1]
997842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.dedent()
1007842e56b97ce677b83bdab09cda48bc2d89ac75aJust		data = "</%s>" % _TAG_
1016962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		self._writeraw(data)
1027842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1037842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def dumphex(self, data):
1047842e56b97ce677b83bdab09cda48bc2d89ac75aJust		linelength = 16
1057842e56b97ce677b83bdab09cda48bc2d89ac75aJust		hexlinelength = linelength * 2
1067842e56b97ce677b83bdab09cda48bc2d89ac75aJust		chunksize = 8
1077842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for i in range(0, len(data), linelength):
1087842e56b97ce677b83bdab09cda48bc2d89ac75aJust			hexline = hexStr(data[i:i+linelength])
1097842e56b97ce677b83bdab09cda48bc2d89ac75aJust			line = ""
1107842e56b97ce677b83bdab09cda48bc2d89ac75aJust			white = ""
1117842e56b97ce677b83bdab09cda48bc2d89ac75aJust			for j in range(0, hexlinelength, chunksize):
1127842e56b97ce677b83bdab09cda48bc2d89ac75aJust				line = line + white + hexline[j:j+chunksize]
1137842e56b97ce677b83bdab09cda48bc2d89ac75aJust				white = " "
1146962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod			self._writeraw(line)
1157842e56b97ce677b83bdab09cda48bc2d89ac75aJust			self.newline()
1167842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1177842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def indent(self):
1187842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.indentlevel = self.indentlevel + 1
1197842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1207842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def dedent(self):
1217842e56b97ce677b83bdab09cda48bc2d89ac75aJust		assert self.indentlevel > 0
1227842e56b97ce677b83bdab09cda48bc2d89ac75aJust		self.indentlevel = self.indentlevel - 1
1237842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1247842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def stringifyattrs(self, *args, **kwargs):
1257842e56b97ce677b83bdab09cda48bc2d89ac75aJust		if kwargs:
1267842e56b97ce677b83bdab09cda48bc2d89ac75aJust			assert not args
127ac1b4359467ca3deab03186a15eae1d55eb35567Behdad Esfahbod			attributes = sorted(kwargs.items())
1287842e56b97ce677b83bdab09cda48bc2d89ac75aJust		elif args:
1297842e56b97ce677b83bdab09cda48bc2d89ac75aJust			assert len(args) == 1
1307842e56b97ce677b83bdab09cda48bc2d89ac75aJust			attributes = args[0]
1317842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
1327842e56b97ce677b83bdab09cda48bc2d89ac75aJust			return ""
1337842e56b97ce677b83bdab09cda48bc2d89ac75aJust		data = ""
1347842e56b97ce677b83bdab09cda48bc2d89ac75aJust		for attr, value in attributes:
1357842e56b97ce677b83bdab09cda48bc2d89ac75aJust			data = data + ' %s="%s"' % (attr, escapeattr(str(value)))
1367842e56b97ce677b83bdab09cda48bc2d89ac75aJust		return data
1377842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1387842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1397842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef escape(data):
1406962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod	data = tostr(data, 'utf-8')
14114fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod	data = data.replace("&", "&amp;")
14214fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod	data = data.replace("<", "&lt;")
1436962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod	data = data.replace(">", "&gt;")
1447842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
1457842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1467842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef escapeattr(data):
1475cf40083364e1d2dce119de25cb42ce69d2fb53cBehdad Esfahbod	data = escape(data)
14814fb031125b773f0a15eb19be4f02ed8540b2db6Behdad Esfahbod	data = data.replace('"', "&quot;")
1497842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return data
1507842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1517842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef escape8bit(data):
1526962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod	"""Input is Unicode string."""
1537842e56b97ce677b83bdab09cda48bc2d89ac75aJust	def escapechar(c):
1546962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		n = ord(c)
1556962f0cfb2b7edfcfa1ff291e99fec756f06c6f8Behdad Esfahbod		if 32 <= n <= 127 and c not in "<&>":
1567842e56b97ce677b83bdab09cda48bc2d89ac75aJust			return c
1577842e56b97ce677b83bdab09cda48bc2d89ac75aJust		else:
158dc7e6f3e5563a853477ebe26166b002c158dbe8bBehdad Esfahbod			return "&#" + repr(n) + ";"
159ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod	return strjoin(map(escapechar, data.decode('latin-1')))
160ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod
161ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahboddef escape16bit(data):
162ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod	import array
163ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod	a = array.array("H")
164ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod	a.fromstring(data)
165ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod	if sys.byteorder != "big":
166ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod		a.byteswap()
167ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod	def escapenum(n, amp=byteord("&"), lt=byteord("<")):
168ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod		if n == amp:
169ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod			return "&amp;"
170ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod		elif n == lt:
171ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod			return "&lt;"
172ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod		elif 32 <= n <= 127:
173ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod			return chr(n)
174ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod		else:
175ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod			return "&#" + repr(n) + ";"
176ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod	return strjoin(map(escapenum, a))
177ca80208a1514ea4d7c55114ae1979f0ba6b41d16Behdad Esfahbod
1787842e56b97ce677b83bdab09cda48bc2d89ac75aJust
1797842e56b97ce677b83bdab09cda48bc2d89ac75aJustdef hexStr(s):
1807842e56b97ce677b83bdab09cda48bc2d89ac75aJust	h = string.hexdigits
1817842e56b97ce677b83bdab09cda48bc2d89ac75aJust	r = ''
1827842e56b97ce677b83bdab09cda48bc2d89ac75aJust	for c in s:
183319c5fd10e2ea84304bd299b7483e05b5b0d5480Behdad Esfahbod		i = byteord(c)
1847842e56b97ce677b83bdab09cda48bc2d89ac75aJust		r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
1857842e56b97ce677b83bdab09cda48bc2d89ac75aJust	return r
186