"""xmlWriter.py -- Simple XML authoring class""" from fontTools.misc.textTools import byteord, strjoin, tobytes, tostr import sys import os import string INDENT = " " class XMLWriter(object): def __init__(self, fileOrPath, indentwhite=INDENT, idlefunc=None, encoding="utf_8", newlinestr="\n"): if encoding.lower().replace('-','').replace('_','') != 'utf8': raise Exception('Only UTF-8 encoding is supported.') if fileOrPath == '-': fileOrPath = sys.stdout if not hasattr(fileOrPath, "write"): self.filename = fileOrPath self.file = open(fileOrPath, "wb") self._closeStream = True else: self.filename = None # assume writable file object self.file = fileOrPath self._closeStream = False # Figure out if writer expects bytes or unicodes try: # The bytes check should be first. See: # https://github.com/fonttools/fonttools/pull/233 self.file.write(b'') self.totype = tobytes except TypeError: # This better not fail. self.file.write('') self.totype = tostr self.indentwhite = self.totype(indentwhite) if newlinestr is None: self.newlinestr = self.totype(os.linesep) else: self.newlinestr = self.totype(newlinestr) self.indentlevel = 0 self.stack = [] self.needindent = 1 self.idlefunc = idlefunc self.idlecounter = 0 self._writeraw('') self.newline() def __enter__(self): return self def __exit__(self, exception_type, exception_value, traceback): self.close() def close(self): if self._closeStream: 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 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 = self.totype(data, encoding="utf_8") if (strip): s = s.strip() self.file.write(s) def newline(self): self.file.write(self.newlinestr) 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: if not isinstance(value, (bytes, str)): value = str(value) data = data + ' %s="%s"' % (attr, escapeattr(value)) return data def escape(data): data = tostr(data, 'utf_8') data = data.replace("&", "&") data = data.replace("<", "<") data = data.replace(">", ">") data = data.replace("\r", " ") 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 hexStr(s): h = string.hexdigits r = '' for c in s: i = byteord(c) r = r + h[(i >> 4) & 0xF] + h[i & 0xF] return r