1"""xmlWriter.py -- Simple XML authoring class"""
2
3from __future__ import print_function, division, absolute_import
4from fontTools.misc.py23 import *
5import sys
6import string
7
8INDENT = "  "
9
10
11class XMLWriter(object):
12
13	def __init__(self, fileOrPath, indentwhite=INDENT, idlefunc=None):
14		if not hasattr(fileOrPath, "write"):
15			try:
16				# Python3 has encoding support.
17				self.file = open(fileOrPath, "w", encoding="utf-8")
18			except TypeError:
19				self.file = open(fileOrPath, "w")
20		else:
21			# assume writable file object
22			self.file = fileOrPath
23		self.indentwhite = indentwhite
24		self.indentlevel = 0
25		self.stack = []
26		self.needindent = 1
27		self.idlefunc = idlefunc
28		self.idlecounter = 0
29		self._writeraw('<?xml version="1.0" encoding="utf-8"?>')
30		self.newline()
31
32	def close(self):
33		self.file.close()
34
35	def write(self, string, indent=True):
36		"""Writes text."""
37		self._writeraw(escape(string), indent=indent)
38
39	def writecdata(self, string):
40		"""Writes text in a CDATA section."""
41		self._writeraw("<![CDATA[" + string + "]]>")
42
43	def write8bit(self, data, strip=False):
44		"""Writes a bytes() sequence into the XML, escaping
45		non-ASCII bytes.  When this is read in xmlReader,
46		the original bytes can be recovered by encoding to
47		'latin-1'."""
48		self._writeraw(escape8bit(data), strip=strip)
49
50	def write16bit(self, data, strip=False):
51		self._writeraw(escape16bit(data), strip=strip)
52
53	def write_noindent(self, string):
54		"""Writes text without indentation."""
55		self._writeraw(escape(string), indent=False)
56
57	def _writeraw(self, data, indent=True, strip=False):
58		"""Writes bytes, possibly indented."""
59		if indent and self.needindent:
60			self.file.write(self.indentlevel * self.indentwhite)
61			self.needindent = 0
62		s = tostr(data, encoding="utf-8")
63		if (strip):
64			s = s.strip()
65		self.file.write(s)
66
67	def newline(self):
68		self.file.write("\n")
69		self.needindent = 1
70		idlecounter = self.idlecounter
71		if not idlecounter % 100 and self.idlefunc is not None:
72			self.idlefunc()
73		self.idlecounter = idlecounter + 1
74
75	def comment(self, data):
76		data = escape(data)
77		lines = data.split("\n")
78		self._writeraw("<!-- " + lines[0])
79		for line in lines[1:]:
80			self.newline()
81			self._writeraw("     " + line)
82		self._writeraw(" -->")
83
84	def simpletag(self, _TAG_, *args, **kwargs):
85		attrdata = self.stringifyattrs(*args, **kwargs)
86		data = "<%s%s/>" % (_TAG_, attrdata)
87		self._writeraw(data)
88
89	def begintag(self, _TAG_, *args, **kwargs):
90		attrdata = self.stringifyattrs(*args, **kwargs)
91		data = "<%s%s>" % (_TAG_, attrdata)
92		self._writeraw(data)
93		self.stack.append(_TAG_)
94		self.indent()
95
96	def endtag(self, _TAG_):
97		assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
98		del self.stack[-1]
99		self.dedent()
100		data = "</%s>" % _TAG_
101		self._writeraw(data)
102
103	def dumphex(self, data):
104		linelength = 16
105		hexlinelength = linelength * 2
106		chunksize = 8
107		for i in range(0, len(data), linelength):
108			hexline = hexStr(data[i:i+linelength])
109			line = ""
110			white = ""
111			for j in range(0, hexlinelength, chunksize):
112				line = line + white + hexline[j:j+chunksize]
113				white = " "
114			self._writeraw(line)
115			self.newline()
116
117	def indent(self):
118		self.indentlevel = self.indentlevel + 1
119
120	def dedent(self):
121		assert self.indentlevel > 0
122		self.indentlevel = self.indentlevel - 1
123
124	def stringifyattrs(self, *args, **kwargs):
125		if kwargs:
126			assert not args
127			attributes = sorted(kwargs.items())
128		elif args:
129			assert len(args) == 1
130			attributes = args[0]
131		else:
132			return ""
133		data = ""
134		for attr, value in attributes:
135			data = data + ' %s="%s"' % (attr, escapeattr(str(value)))
136		return data
137
138
139def escape(data):
140	data = tostr(data, 'utf-8')
141	data = data.replace("&", "&amp;")
142	data = data.replace("<", "&lt;")
143	data = data.replace(">", "&gt;")
144	return data
145
146def escapeattr(data):
147	data = escape(data)
148	data = data.replace('"', "&quot;")
149	return data
150
151def escape8bit(data):
152	"""Input is Unicode string."""
153	def escapechar(c):
154		n = ord(c)
155		if 32 <= n <= 127 and c not in "<&>":
156			return c
157		else:
158			return "&#" + repr(n) + ";"
159	return strjoin(map(escapechar, data.decode('latin-1')))
160
161def escape16bit(data):
162	import array
163	a = array.array("H")
164	a.fromstring(data)
165	if sys.byteorder != "big":
166		a.byteswap()
167	def escapenum(n, amp=byteord("&"), lt=byteord("<")):
168		if n == amp:
169			return "&amp;"
170		elif n == lt:
171			return "&lt;"
172		elif 32 <= n <= 127:
173			return chr(n)
174		else:
175			return "&#" + repr(n) + ";"
176	return strjoin(map(escapenum, a))
177
178
179def hexStr(s):
180	h = string.hexdigits
181	r = ''
182	for c in s:
183		i = byteord(c)
184		r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
185	return r
186