"""xmlWriter.py -- Simple XML authoring class""" from __future__ import print_function, division, absolute_import from fontTools.misc.py23 import * import sys import string INDENT = " " class XMLWriter(object): def __init__(self, fileOrPath, indentwhite=INDENT, idlefunc=None): if not hasattr(fileOrPath, "write"): try: # Python3 has encoding support. self.file = open(fileOrPath, "w", encoding="utf-8") except TypeError: self.file = open(fileOrPath, "w") else: # assume writable file object self.file = fileOrPath self.indentwhite = indentwhite self.indentlevel = 0 self.stack = [] self.needindent = 1 self.idlefunc = idlefunc self.idlecounter = 0 self._writeraw('') self.newline() def close(self): self.file.close() def write(self, string, indent=True): """Writes text.""" self._writeraw(escape(string), indent=indent) def writecdata(self, string): """Writes text in a CDATA section.""" self._writeraw("") def write8bit(self, data, strip=False): """Writes a bytes() sequence into the XML, escaping non-ASCII bytes. When this is read in xmlReader, the original bytes can be recovered by encoding to 'latin-1'.""" self._writeraw(escape8bit(data), strip=strip) def write16bit(self, data, strip=False): self._writeraw(escape16bit(data), strip=strip) def write_noindent(self, string): """Writes text without indentation.""" self._writeraw(escape(string), indent=False) def _writeraw(self, data, indent=True, strip=False): """Writes bytes, possibly indented.""" if indent and self.needindent: self.file.write(self.indentlevel * self.indentwhite) self.needindent = 0 s = tostr(data, encoding="utf-8") if (strip): s = s.strip() self.file.write(s) def newline(self): self.file.write("\n") self.needindent = 1 idlecounter = self.idlecounter if not idlecounter % 100 and self.idlefunc is not None: self.idlefunc() self.idlecounter = idlecounter + 1 def comment(self, data): data = escape(data) lines = data.split("\n") self._writeraw("") def simpletag(self, _TAG_, *args, **kwargs): attrdata = self.stringifyattrs(*args, **kwargs) data = "<%s%s/>" % (_TAG_, attrdata) self._writeraw(data) def begintag(self, _TAG_, *args, **kwargs): attrdata = self.stringifyattrs(*args, **kwargs) data = "<%s%s>" % (_TAG_, attrdata) self._writeraw(data) self.stack.append(_TAG_) self.indent() def endtag(self, _TAG_): assert self.stack and self.stack[-1] == _TAG_, "nonmatching endtag" del self.stack[-1] self.dedent() data = "" % _TAG_ self._writeraw(data) def dumphex(self, data): linelength = 16 hexlinelength = linelength * 2 chunksize = 8 for i in range(0, len(data), linelength): hexline = hexStr(data[i:i+linelength]) line = "" white = "" for j in range(0, hexlinelength, chunksize): line = line + white + hexline[j:j+chunksize] white = " " self._writeraw(line) self.newline() def indent(self): self.indentlevel = self.indentlevel + 1 def dedent(self): assert self.indentlevel > 0 self.indentlevel = self.indentlevel - 1 def stringifyattrs(self, *args, **kwargs): if kwargs: assert not args attributes = sorted(kwargs.items()) elif args: assert len(args) == 1 attributes = args[0] else: return "" data = "" for attr, value in attributes: data = data + ' %s="%s"' % (attr, escapeattr(str(value))) return data def escape(data): data = tostr(data, 'utf-8') data = data.replace("&", "&") data = data.replace("<", "<") data = data.replace(">", ">") return data def escapeattr(data): data = escape(data) data = data.replace('"', """) return data def escape8bit(data): """Input is Unicode string.""" def escapechar(c): n = ord(c) if 32 <= n <= 127 and c not in "<&>": return c else: return "&#" + repr(n) + ";" return strjoin(map(escapechar, data.decode('latin-1'))) def escape16bit(data): import array a = array.array("H") a.fromstring(data) if sys.byteorder != "big": a.byteswap() def escapenum(n, amp=byteord("&"), lt=byteord("<")): if n == amp: return "&" elif n == lt: return "<" elif 32 <= n <= 127: return chr(n) else: return "&#" + repr(n) + ";" return strjoin(map(escapenum, a)) def hexStr(s): h = string.hexdigits r = '' for c in s: i = byteord(c) r = r + h[(i >> 4) & 0xF] + h[i & 0xF] return r