blob: 70a6a852f0878c2927574d8b16456bd8b55b14b2 [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
55 'latin-1'."""
56 self._writeraw(escape8bit(data.decode('latin-1')))
Just7842e561999-12-16 21:34:53 +000057
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050058 def write_noindent(self, string):
59 """Writes text without indentation."""
60 self._writeraw(escape(string), indent=False)
Just7842e561999-12-16 21:34:53 +000061
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050062 def _writeraw(self, data, indent=True):
63 """Writes bytes, possibly indented."""
64 if indent and self.needindent:
Just7842e561999-12-16 21:34:53 +000065 self.file.write(self.indentlevel * self.indentwhite)
66 self.needindent = 0
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050067 self.file.write(tostr(data, encoding="utf-8"))
Just7842e561999-12-16 21:34:53 +000068
69 def newline(self):
70 self.file.write("\n")
71 self.needindent = 1
jvr33f33272002-07-23 16:41:08 +000072 idlecounter = self.idlecounter
73 if not idlecounter % 100 and self.idlefunc is not None:
74 self.idlefunc()
75 self.idlecounter = idlecounter + 1
Just7842e561999-12-16 21:34:53 +000076
77 def comment(self, data):
78 data = escape(data)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -050079 lines = data.split("\n")
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050080 self._writeraw("<!-- " + lines[0])
Just7842e561999-12-16 21:34:53 +000081 for line in lines[1:]:
82 self.newline()
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050083 self._writeraw(" " + line)
84 self._writeraw(" -->")
Just7842e561999-12-16 21:34:53 +000085
86 def simpletag(self, _TAG_, *args, **kwargs):
Behdad Esfahbod66214cb2013-11-27 02:18:18 -050087 attrdata = self.stringifyattrs(*args, **kwargs)
Just7842e561999-12-16 21:34:53 +000088 data = "<%s%s/>" % (_TAG_, attrdata)
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050089 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +000090
91 def begintag(self, _TAG_, *args, **kwargs):
Behdad Esfahbod66214cb2013-11-27 02:18:18 -050092 attrdata = self.stringifyattrs(*args, **kwargs)
Just7842e561999-12-16 21:34:53 +000093 data = "<%s%s>" % (_TAG_, attrdata)
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -050094 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +000095 self.stack.append(_TAG_)
96 self.indent()
97
98 def endtag(self, _TAG_):
99 assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag"
100 del self.stack[-1]
101 self.dedent()
102 data = "</%s>" % _TAG_
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500103 self._writeraw(data)
Just7842e561999-12-16 21:34:53 +0000104
105 def dumphex(self, data):
106 linelength = 16
107 hexlinelength = linelength * 2
108 chunksize = 8
109 for i in range(0, len(data), linelength):
110 hexline = hexStr(data[i:i+linelength])
111 line = ""
112 white = ""
113 for j in range(0, hexlinelength, chunksize):
114 line = line + white + hexline[j:j+chunksize]
115 white = " "
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500116 self._writeraw(line)
Just7842e561999-12-16 21:34:53 +0000117 self.newline()
118
119 def indent(self):
120 self.indentlevel = self.indentlevel + 1
121
122 def dedent(self):
123 assert self.indentlevel > 0
124 self.indentlevel = self.indentlevel - 1
125
126 def stringifyattrs(self, *args, **kwargs):
127 if kwargs:
128 assert not args
Behdad Esfahbodac1b4352013-11-27 04:15:34 -0500129 attributes = sorted(kwargs.items())
Just7842e561999-12-16 21:34:53 +0000130 elif args:
131 assert len(args) == 1
132 attributes = args[0]
133 else:
134 return ""
135 data = ""
136 for attr, value in attributes:
137 data = data + ' %s="%s"' % (attr, escapeattr(str(value)))
138 return data
139
140
141def escape(data):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500142 data = tostr(data, 'utf-8')
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500143 data = data.replace("&", "&amp;")
144 data = data.replace("<", "&lt;")
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500145 data = data.replace(">", "&gt;")
Just7842e561999-12-16 21:34:53 +0000146 return data
147
148def escapeattr(data):
Behdad Esfahbod5cf40082013-11-27 19:51:59 -0500149 data = escape(data)
Behdad Esfahbod14fb0312013-11-27 05:47:34 -0500150 data = data.replace('"', "&quot;")
Just7842e561999-12-16 21:34:53 +0000151 return data
152
153def escape8bit(data):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500154 """Input is Unicode string."""
Just7842e561999-12-16 21:34:53 +0000155 def escapechar(c):
Behdad Esfahbod6962f0c2013-11-27 22:47:35 -0500156 n = ord(c)
157 if 32 <= n <= 127 and c not in "<&>":
Just7842e561999-12-16 21:34:53 +0000158 return c
159 else:
Behdad Esfahboddc7e6f32013-11-27 02:44:56 -0500160 return "&#" + repr(n) + ";"
Behdad Esfahbod18316aa2013-11-27 21:17:35 -0500161 return strjoin(map(escapechar, data))
Just7842e561999-12-16 21:34:53 +0000162
Just7842e561999-12-16 21:34:53 +0000163def hexStr(s):
164 h = string.hexdigits
165 r = ''
166 for c in s:
Behdad Esfahbod319c5fd2013-11-27 18:13:48 -0500167 i = byteord(c)
Just7842e561999-12-16 21:34:53 +0000168 r = r + h[(i >> 4) & 0xF] + h[i & 0xF]
169 return r