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("&", "&") 142 data = data.replace("<", "<") 143 data = data.replace(">", ">") 144 return data 145 146def escapeattr(data): 147 data = escape(data) 148 data = data.replace('"', """) 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 "&" 170 elif n == lt: 171 return "<" 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