blob: 74646238216c81a62391dcbcbfca0abeaa75b335 [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
44 def writeutf16be(self, data):
45 """Writes a UTF-16 bytes() sequence into the XML
46 as native Unicode. When this is read in xmlReader,
47 the original bytes can be recovered by encoding to
48 'utf-16-be'."""
49 self._writeraw(escape(data.decode('utf-16-be')))
50
Just7842e561999-12-16 21:34:53 +000051 def write8bit(self, data):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050052 """Writes a bytes() sequence into the XML, escaping
53 non-ASCII bytes. When this is read in xmlReader,
54 the original bytes can be recovered by encoding to
Behdad Esfahbodca802082013-11-28 13:41:54 -050055 'latin-1'."""
56 self._writeraw(escape8bit(data))
57
58 def write16bit(self, data):
59 self._writeraw(escape16bit(data))
Just7842e561999-12-16 21:34:53 +000060
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050061 def write_noindent(self, string):
62 """Writes text without indentation."""
63 self._writeraw(escape(string), indent=False)
Just7842e561999-12-16 21:34:53 +000064
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050065 def _writeraw(self, data, indent=True):
66 """Writes bytes, possibly indented."""
67 if indent and self.needindent:
Just7842e561999-12-16 21:34:53 +000068 self.file.write(self.indentlevel * self.indentwhite)
69 self.needindent = 0
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050070 self.file.write(tostr(data, encoding="utf-8"))
Just7842e561999-12-16 21:34:53 +000071
72 def newline(self):
73 self.file.write("\n")
74 self.needindent = 1
jvr33f33272002-07-23 16:41:08 +000075 idlecounter = self.idlecounter
76 if not idlecounter % 100 and self.idlefunc is not None:
77 self.idlefunc()
78 self.idlecounter = idlecounter + 1
Just7842e561999-12-16 21:34:53 +000079
80 def comment(self, data):
81 data = escape(data)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -050082 lines = data.split("\n")
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050083 self._writeraw("<!-- " + lines[0])
Just7842e561999-12-16 21:34:53 +000084 for line in lines[1:]:
85 self.newline()
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050086 self._writeraw(" " + line)
87 self._writeraw(" -->")
Just7842e561999-12-16 21:34:53 +000088
89 def simpletag(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
94 def begintag(self, _TAG_, *args, **kwargs):
Behdad Esfahbod66214cb2013-11-27 02:18:18 -050095 attrdata = self.stringifyattrs(*args, **kwargs)
Just7842e561999-12-16 21:34:53 +000096 data = "<%s%s>" % (_TAG_, attrdata)
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050097 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +000098 self.stack.append(_TAG_)
99 self.indent()
100
101 def endtag(self, _TAG_):
102 assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
103 del self.stack[-1]
104 self.dedent()
105 data = "</%s>" % _TAG_
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500106 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +0000107
108 def dumphex(self, data):
109 linelength = 16
110 hexlinelength = linelength * 2
111 chunksize = 8
112 for i in range(0, len(data), linelength):
113 hexline = hexStr(data[i:i+linelength])
114 line = ""
115 white = ""
116 for j in range(0, hexlinelength, chunksize):
117 line = line + white + hexline[j:j+chunksize]
118 white = " "
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500119 self._writeraw(line)
Just7842e561999-12-16 21:34:53 +0000120 self.newline()
121
122 def indent(self):
123 self.indentlevel = self.indentlevel + 1
124
125 def dedent(self):
126 assert self.indentlevel > 0
127 self.indentlevel = self.indentlevel - 1
128
129 def stringifyattrs(self, *args, **kwargs):
130 if kwargs:
131 assert not args
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500132 attributes = sorted(kwargs.items())
Just7842e561999-12-16 21:34:53 +0000133 elif args:
134 assert len(args) == 1
135 attributes = args[0]
136 else:
137 return ""
138 data = ""
139 for attr, value in attributes:
140 data = data + ' %s="%s"' % (attr, escapeattr(str(value)))
141 return data
142
143
144def escape(data):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500145 data = tostr(data, 'utf-8')
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500146 data = data.replace("&", "&amp;")
147 data = data.replace("<", "&lt;")
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500148 data = data.replace(">", "&gt;")
Just7842e561999-12-16 21:34:53 +0000149 return data
150
151def escapeattr(data):
Behdad Esfahbod5cf40082013-11-27 19:51:59 -0500152 data = escape(data)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500153 data = data.replace('"', "&quot;")
Just7842e561999-12-16 21:34:53 +0000154 return data
155
156def escape8bit(data):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500157 """Input is Unicode string."""
Just7842e561999-12-16 21:34:53 +0000158 def escapechar(c):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500159 n = ord(c)
160 if 32 <= n <= 127 and c not in "<&>":
Just7842e561999-12-16 21:34:53 +0000161 return c
162 else:
Behdad Esfahboddc7e6f32013-11-27 02:44:56 -0500163 return "&#" + repr(n) + ";"
Behdad Esfahbodca802082013-11-28 13:41:54 -0500164 return strjoin(map(escapechar, data.decode('latin-1')))
165
166def escape16bit(data):
167 import array
168 a = array.array("H")
169 a.fromstring(data)
170 if sys.byteorder != "big":
171 a.byteswap()
172 def escapenum(n, amp=byteord("&"), lt=byteord("<")):
173 if n == amp:
174 return "&amp;"
175 elif n == lt:
176 return "&lt;"
177 elif 32 <= n <= 127:
178 return chr(n)
179 else:
180 return "&#" + repr(n) + ";"
181 return strjoin(map(escapenum, a))
182
Just7842e561999-12-16 21:34:53 +0000183
Just7842e561999-12-16 21:34:53 +0000184def hexStr(s):
185 h = string.hexdigits
186 r = ''
187 for c in s:
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500188 i = byteord(c)
Just7842e561999-12-16 21:34:53 +0000189 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
190 return r