blob: 11e8289008e06462b86b3d53f92992d49f06871d [file] [log] [blame]
Just7842e561999-12-16 21:34:53 +00001"""xmlWriter.py -- Simple XML authoring class"""
2
Behdad Esfahbod32c10ee2013-11-27 17:46:17 -05003from __future__ import print_function, division
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
7import struct
8
9INDENT = " "
10
jvr81b0c2b2002-09-09 18:17:12 +000011
Just7842e561999-12-16 21:34:53 +000012class XMLWriter:
13
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050014 def __init__(self, fileOrPath, indentwhite=INDENT, idlefunc=None):
jvr90beb952005-01-17 21:34:06 +000015 if not hasattr(fileOrPath, "write"):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050016 try:
17 # Python3 has encoding support.
18 self.file = open(fileOrPath, "w")
19 except TypeError:
20 self.file = open(fileOrPath, "w", encoding="utf-8")
Just7842e561999-12-16 21:34:53 +000021 else:
22 # assume writable file object
jvr90beb952005-01-17 21:34:06 +000023 self.file = fileOrPath
Just7842e561999-12-16 21:34:53 +000024 self.indentwhite = indentwhite
25 self.indentlevel = 0
26 self.stack = []
27 self.needindent = 1
jvr33f33272002-07-23 16:41:08 +000028 self.idlefunc = idlefunc
29 self.idlecounter = 0
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050030 self._writeraw('<?xml version="1.0" encoding="utf-8"?>')
Just7842e561999-12-16 21:34:53 +000031 self.newline()
Just7842e561999-12-16 21:34:53 +000032
33 def close(self):
34 self.file.close()
35
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050036 def write(self, string, indent=True):
37 """Writes text."""
38 self._writeraw(escape(string), indent=indent)
39
40 def writecdata(self, string):
41 """Writes text in a CDATA section."""
42 self._writeraw("<![CDATA[" + string + "]]>")
43
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050044 def write8bit(self, data, strip=False):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050045 """Writes a bytes() sequence into the XML, escaping
46 non-ASCII bytes. When this is read in xmlReader,
47 the original bytes can be recovered by encoding to
Behdad Esfahbodca802082013-11-28 13:41:54 -050048 'latin-1'."""
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050049 self._writeraw(escape8bit(data), strip=strip)
Behdad Esfahbodca802082013-11-28 13:41:54 -050050
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050051 def write16bit(self, data, strip=False):
52 self._writeraw(escape16bit(data), strip=strip)
Just7842e561999-12-16 21:34:53 +000053
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050054 def write_noindent(self, string):
55 """Writes text without indentation."""
56 self._writeraw(escape(string), indent=False)
Just7842e561999-12-16 21:34:53 +000057
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050058 def _writeraw(self, data, indent=True, strip=False):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050059 """Writes bytes, possibly indented."""
60 if indent and self.needindent:
Just7842e561999-12-16 21:34:53 +000061 self.file.write(self.indentlevel * self.indentwhite)
62 self.needindent = 0
Behdad Esfahbod1edfe572013-11-28 18:48:15 -050063 s = tostr(data, encoding="utf-8")
64 if (strip):
65 s = s.strip()
66 self.file.write(s)
Just7842e561999-12-16 21:34:53 +000067
68 def newline(self):
69 self.file.write("\n")
70 self.needindent = 1
jvr33f33272002-07-23 16:41:08 +000071 idlecounter = self.idlecounter
72 if not idlecounter % 100 and self.idlefunc is not None:
73 self.idlefunc()
74 self.idlecounter = idlecounter + 1
Just7842e561999-12-16 21:34:53 +000075
76 def comment(self, data):
77 data = escape(data)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -050078 lines = data.split("\n")
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050079 self._writeraw("<!-- " + lines[0])
Just7842e561999-12-16 21:34:53 +000080 for line in lines[1:]:
81 self.newline()
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050082 self._writeraw(" " + line)
83 self._writeraw(" -->")
Just7842e561999-12-16 21:34:53 +000084
85 def simpletag(self, _TAG_, *args, **kwargs):
Behdad Esfahbod66214cb2013-11-27 02:18:18 -050086 attrdata = self.stringifyattrs(*args, **kwargs)
Just7842e561999-12-16 21:34:53 +000087 data = "<%s%s/>" % (_TAG_, attrdata)
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050088 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +000089
90 def begintag(self, _TAG_, *args, **kwargs):
Behdad Esfahbod66214cb2013-11-27 02:18:18 -050091 attrdata = self.stringifyattrs(*args, **kwargs)
Just7842e561999-12-16 21:34:53 +000092 data = "<%s%s>" % (_TAG_, attrdata)
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050093 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +000094 self.stack.append(_TAG_)
95 self.indent()
96
97 def endtag(self, _TAG_):
98 assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
99 del self.stack[-1]
100 self.dedent()
101 data = "</%s>" % _TAG_
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500102 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +0000103
104 def dumphex(self, data):
105 linelength = 16
106 hexlinelength = linelength * 2
107 chunksize = 8
108 for i in range(0, len(data), linelength):
109 hexline = hexStr(data[i:i+linelength])
110 line = ""
111 white = ""
112 for j in range(0, hexlinelength, chunksize):
113 line = line + white + hexline[j:j+chunksize]
114 white = " "
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500115 self._writeraw(line)
Just7842e561999-12-16 21:34:53 +0000116 self.newline()
117
118 def indent(self):
119 self.indentlevel = self.indentlevel + 1
120
121 def dedent(self):
122 assert self.indentlevel > 0
123 self.indentlevel = self.indentlevel - 1
124
125 def stringifyattrs(self, *args, **kwargs):
126 if kwargs:
127 assert not args
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500128 attributes = sorted(kwargs.items())
Just7842e561999-12-16 21:34:53 +0000129 elif args:
130 assert len(args) == 1
131 attributes = args[0]
132 else:
133 return ""
134 data = ""
135 for attr, value in attributes:
136 data = data + ' %s="%s"' % (attr, escapeattr(str(value)))
137 return data
138
139
140def escape(data):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500141 data = tostr(data, 'utf-8')
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500142 data = data.replace("&", "&amp;")
143 data = data.replace("<", "&lt;")
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500144 data = data.replace(">", "&gt;")
Just7842e561999-12-16 21:34:53 +0000145 return data
146
147def escapeattr(data):
Behdad Esfahbod5cf40082013-11-27 19:51:59 -0500148 data = escape(data)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500149 data = data.replace('"', "&quot;")
Just7842e561999-12-16 21:34:53 +0000150 return data
151
152def escape8bit(data):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500153 """Input is Unicode string."""
Just7842e561999-12-16 21:34:53 +0000154 def escapechar(c):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500155 n = ord(c)
156 if 32 <= n <= 127 and c not in "<&>":
Just7842e561999-12-16 21:34:53 +0000157 return c
158 else:
Behdad Esfahboddc7e6f32013-11-27 02:44:56 -0500159 return "&#" + repr(n) + ";"
Behdad Esfahbodca802082013-11-28 13:41:54 -0500160 return strjoin(map(escapechar, data.decode('latin-1')))
161
162def escape16bit(data):
163 import array
164 a = array.array("H")
165 a.fromstring(data)
166 if sys.byteorder != "big":
167 a.byteswap()
168 def escapenum(n, amp=byteord("&"), lt=byteord("<")):
169 if n == amp:
170 return "&amp;"
171 elif n == lt:
172 return "&lt;"
173 elif 32 <= n <= 127:
174 return chr(n)
175 else:
176 return "&#" + repr(n) + ";"
177 return strjoin(map(escapenum, a))
178
Just7842e561999-12-16 21:34:53 +0000179
Just7842e561999-12-16 21:34:53 +0000180def hexStr(s):
181 h = string.hexdigits
182 r = ''
183 for c in s:
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500184 i = byteord(c)
Just7842e561999-12-16 21:34:53 +0000185 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
186 return r