blob: b067c2de258d7154db5d4fe329c9bbb01ae239a1 [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""xmlWriter.py -- Simple XML authoring class"""
2
Behdad Esfahbod1ae29592014-01-14 15:07:50 +08003from __future__ import print_function, division, absolute_import
Behdad Esfahbod30e691e2013-11-27 17:27:45 -05004from fontTools.misc.py23 import *
Behdad Esfahbod5cf40082013-11-27 19:51:59 -05005import sys
Just7842e561999-12-16 21:34:53 +00006import string
Just7842e561999-12-16 21:34:53 +00007
8INDENT = " "
9
jvr81b0c2b2002-09-09 18:17:12 +000010
Behdad Esfahbode388db52013-11-28 14:26:58 -050011class XMLWriter(object):
Just7842e561999-12-16 21:34:53 +000012
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050013 def __init__(self, fileOrPath, indentwhite=INDENT, idlefunc=None):
jvr90beb952005-01-17 21:34:06 +000014 if not hasattr(fileOrPath, "write"):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050015 try:
16 # Python3 has encoding support.
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050017 self.file = open(fileOrPath, "w", encoding="utf-8")
Behdad Esfahbodc40e26e2013-12-04 00:20:19 -050018 except TypeError:
19 self.file = open(fileOrPath, "w")
Just7842e561999-12-16 21:34:53 +000020 else:
21 # assume writable file object
jvr90beb952005-01-17 21:34:06 +000022 self.file = fileOrPath
Just7842e561999-12-16 21:34:53 +000023 self.indentwhite = indentwhite
24 self.indentlevel = 0
25 self.stack = []
26 self.needindent = 1
jvr33f33272002-07-23 16:41:08 +000027 self.idlefunc = idlefunc
28 self.idlecounter = 0
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050029 self._writeraw('<?xml version="1.0" encoding="utf-8"?>')
Just7842e561999-12-16 21:34:53 +000030 self.newline()
Just7842e561999-12-16 21:34:53 +000031
32 def close(self):
33 self.file.close()
34
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050035 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
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050043 def write8bit(self, data, strip=False):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050044 """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
Behdad Esfahbodca802082013-11-28 13:41:54 -050047 'latin-1'."""
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050048 self._writeraw(escape8bit(data), strip=strip)
Behdad Esfahbodca802082013-11-28 13:41:54 -050049
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050050 def write16bit(self, data, strip=False):
51 self._writeraw(escape16bit(data), strip=strip)
Just7842e561999-12-16 21:34:53 +000052
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050053 def write_noindent(self, string):
54 """Writes text without indentation."""
55 self._writeraw(escape(string), indent=False)
Just7842e561999-12-16 21:34:53 +000056
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050057 def _writeraw(self, data, indent=True, strip=False):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050058 """Writes bytes, possibly indented."""
59 if indent and self.needindent:
Just7842e561999-12-16 21:34:53 +000060 self.file.write(self.indentlevel * self.indentwhite)
61 self.needindent = 0
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050062 s = tostr(data, encoding="utf-8")
63 if (strip):
64 s = s.strip()
65 self.file.write(s)
Just7842e561999-12-16 21:34:53 +000066
67 def newline(self):
68 self.file.write("\n")
69 self.needindent = 1
jvr33f33272002-07-23 16:41:08 +000070 idlecounter = self.idlecounter
71 if not idlecounter % 100 and self.idlefunc is not None:
72 self.idlefunc()
73 self.idlecounter = idlecounter + 1
Just7842e561999-12-16 21:34:53 +000074
75 def comment(self, data):
76 data = escape(data)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -050077 lines = data.split("\n")
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050078 self._writeraw("<!-- " + lines[0])
Just7842e561999-12-16 21:34:53 +000079 for line in lines[1:]:
80 self.newline()
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050081 self._writeraw(" " + line)
82 self._writeraw(" -->")
Just7842e561999-12-16 21:34:53 +000083
84 def simpletag(self, _TAG_, *args, **kwargs):
Behdad Esfahbod66214cb2013-11-27 02:18:18 -050085 attrdata = self.stringifyattrs(*args, **kwargs)
Just7842e561999-12-16 21:34:53 +000086 data = "<%s%s/>" % (_TAG_, attrdata)
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050087 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +000088
89 def begintag(self, _TAG_, *args, **kwargs):
Behdad Esfahbod66214cb2013-11-27 02:18:18 -050090 attrdata = self.stringifyattrs(*args, **kwargs)
Just7842e561999-12-16 21:34:53 +000091 data = "<%s%s>" % (_TAG_, attrdata)
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050092 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +000093 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_
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500101 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +0000102
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 = " "
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500114 self._writeraw(line)
Just7842e561999-12-16 21:34:53 +0000115 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
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500127 attributes = sorted(kwargs.items())
Just7842e561999-12-16 21:34:53 +0000128 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):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500140 data = tostr(data, 'utf-8')
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500141 data = data.replace("&", "&amp;")
142 data = data.replace("<", "&lt;")
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500143 data = data.replace(">", "&gt;")
Just7842e561999-12-16 21:34:53 +0000144 return data
145
146def escapeattr(data):
Behdad Esfahbod5cf40082013-11-27 19:51:59 -0500147 data = escape(data)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500148 data = data.replace('"', "&quot;")
Just7842e561999-12-16 21:34:53 +0000149 return data
150
151def escape8bit(data):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500152 """Input is Unicode string."""
Just7842e561999-12-16 21:34:53 +0000153 def escapechar(c):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500154 n = ord(c)
155 if 32 <= n <= 127 and c not in "<&>":
Just7842e561999-12-16 21:34:53 +0000156 return c
157 else:
Behdad Esfahboddc7e6f32013-11-27 02:44:56 -0500158 return "&#" + repr(n) + ";"
Behdad Esfahbodca802082013-11-28 13:41:54 -0500159 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
Just7842e561999-12-16 21:34:53 +0000178
Just7842e561999-12-16 21:34:53 +0000179def hexStr(s):
180 h = string.hexdigits
181 r = ''
182 for c in s:
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500183 i = byteord(c)
Just7842e561999-12-16 21:34:53 +0000184 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
185 return r