diff options
Diffstat (limited to 'Lib/fontTools/misc/xmlWriter.py')
-rw-r--r-- | Lib/fontTools/misc/xmlWriter.py | 358 |
1 files changed, 184 insertions, 174 deletions
diff --git a/Lib/fontTools/misc/xmlWriter.py b/Lib/fontTools/misc/xmlWriter.py index 9e30fa33..9a8dc3e3 100644 --- a/Lib/fontTools/misc/xmlWriter.py +++ b/Lib/fontTools/misc/xmlWriter.py @@ -9,186 +9,196 @@ 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('<?xml version="1.0" encoding="UTF-8"?>') - 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("<![CDATA[" + string + "]]>") - - 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("<!-- " + lines[0]) - for line in lines[1:]: - self.newline() - self._writeraw(" " + line) - 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 = "</%s>" % _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 __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('<?xml version="1.0" encoding="UTF-8"?>') + 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("<![CDATA[" + string + "]]>") + + 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("<!-- " + lines[0]) + for line in lines[1:]: + self.newline() + self._writeraw(" " + line) + 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 = "</%s>" % _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 + 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 + 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'))) + """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 + h = string.hexdigits + r = "" + for c in s: + i = byteord(c) + r = r + h[(i >> 4) & 0xF] + h[i & 0xF] + return r |