From af62ebf2b661dac9697ff32616bccb9a0086ad86 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 7 Dec 2015 18:26:48 +0000 Subject: [PATCH 1/3] ttLib: rename 'fileName' arg to 'fileOrPath' in importXML function for consistency with saveXML method --- Lib/fontTools/ttLib/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/fontTools/ttLib/__init__.py b/Lib/fontTools/ttLib/__init__.py index e0bed2e91..e7e8ea868 100644 --- a/Lib/fontTools/ttLib/__init__.py +++ b/Lib/fontTools/ttLib/__init__.py @@ -327,7 +327,7 @@ class TTFont(object): writer.newline() writer.newline() - def importXML(self, file, progress=None, quiet=False): + def importXML(self, fileOrPath, progress=None, quiet=False): """Import a TTX file (an XML-based text format), so as to recreate a font object. """ @@ -340,7 +340,7 @@ class TTFont(object): from fontTools.misc import xmlReader - reader = xmlReader.XMLReader(file, self, progress, quiet) + reader = xmlReader.XMLReader(fileOrPath, self, progress, quiet) reader.read() def isLoaded(self, tag): From 5cf297ca44a76de376f233a7c957d5de2218ce50 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 7 Dec 2015 19:26:04 +0000 Subject: [PATCH 2/3] xmlReader_test: test ProgressPrinter and reading from path vs file object --- Lib/fontTools/misc/xmlReader_test.py | 99 +++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 16 deletions(-) diff --git a/Lib/fontTools/misc/xmlReader_test.py b/Lib/fontTools/misc/xmlReader_test.py index e468f8e70..53de074be 100644 --- a/Lib/fontTools/misc/xmlReader_test.py +++ b/Lib/fontTools/misc/xmlReader_test.py @@ -5,7 +5,7 @@ from fontTools.misc.py23 import * import os import unittest from fontTools.ttLib import TTFont -from .xmlReader import XMLReader +from .xmlReader import XMLReader, ProgressPrinter, BUFSIZE import tempfile @@ -15,9 +15,9 @@ class TestXMLReader(unittest.TestCase): class DebugXMLReader(XMLReader): - def __init__(self, fileName, ttFont, progress=None, quiet=False): + def __init__(self, fileOrPath, ttFont, progress=None, quiet=False): super(DebugXMLReader, self).__init__( - fileName, ttFont, progress, quiet) + fileOrPath, ttFont, progress, quiet) self.contents = [] def _endElementHandler(self, name): @@ -38,21 +38,19 @@ class TestXMLReader(unittest.TestCase): ''' % expected - with tempfile.NamedTemporaryFile(delete=False) as tmp: - tmp.write(data.encode('utf-8')) - reader = DebugXMLReader(tmp.name, TTFont(), quiet=True) - reader.read() - os.remove(tmp.name) - content = strjoin(reader.contents[0]).strip() + with BytesIO(data.encode('utf-8')) as tmp: + reader = DebugXMLReader(tmp, TTFont(), quiet=True) + reader.read() + content = strjoin(reader.contents[0]).strip() self.assertEqual(expected, content) def test_normalise_newlines(self): class DebugXMLReader(XMLReader): - def __init__(self, fileName, ttFont, progress=None, quiet=False): + def __init__(self, fileOrPath, ttFont, progress=None, quiet=False): super(DebugXMLReader, self).__init__( - fileName, ttFont, progress, quiet) + fileOrPath, ttFont, progress, quiet) self.newlines = [] def _characterDataHandler(self, data): @@ -68,14 +66,83 @@ class TestXMLReader(unittest.TestCase): ' escaped CR and windows newline \r\n' # \r\n -> \r\n ' \n' # \n '') - with tempfile.NamedTemporaryFile(delete=False) as tmp: - tmp.write(data.encode('utf-8')) - reader = DebugXMLReader(tmp.name, TTFont(), quiet=True) - reader.read() - os.remove(tmp.name) + + with BytesIO(data.encode('utf-8')) as tmp: + reader = DebugXMLReader(tmp, TTFont(), quiet=True) + reader.read() expected = ['\n'] * 3 + ['\r', '\n'] * 3 + ['\n'] self.assertEqual(expected, reader.newlines) + def test_progress(self): + + class DummyProgressPrinter(ProgressPrinter): + + def __init__(self, title, maxval=100): + self.label = title + self.maxval = maxval + self.pos = 0 + + def set(self, val, maxval=None): + if maxval is not None: + self.maxval = maxval + self.pos = val + + def increment(self, val=1): + self.pos += val + + def setLabel(self, text): + self.label = text + + data = ( + '\n' + ' \n' + ' %s\n' + ' \n' + '\n' + % ("z" * 2 * BUFSIZE) + ).encode('utf-8') + + dataSize = len(data) + progressBar = DummyProgressPrinter('test') + with BytesIO(data) as tmp: + reader = XMLReader(tmp, TTFont(), progress=progressBar) + self.assertEqual(progressBar.pos, 0) + reader.read() + self.assertEqual(progressBar.pos, dataSize // 100) + self.assertEqual(progressBar.maxval, dataSize // 100) + self.assertTrue('test' in progressBar.label) + with BytesIO(b"") as tmp: + reader = XMLReader(tmp, TTFont(), progress=progressBar) + reader.read() + # when data size is less than 100 bytes, 'maxval' is 1 + self.assertEqual(progressBar.maxval, 1) + + def test_close_file_path(self): + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(b'') + reader = XMLReader(tmp.name, TTFont()) + reader.read() + # when reading from path, the file is closed automatically at the end + self.assertTrue(reader.file.closed) + # this does nothing + reader.close() + self.assertTrue(reader.file.closed) + os.remove(tmp.name) + + def test_close_file_obj(self): + with tempfile.NamedTemporaryFile(delete=False) as tmp: + tmp.write(b'"hello"') + with open(tmp.name, "rb") as f: + reader = XMLReader(f, TTFont()) + reader.read() + # when reading from a file or file-like object, the latter is kept open + self.assertFalse(reader.file.closed) + # ... until the user explicitly closes it + reader.close() + self.assertTrue(reader.file.closed) + os.remove(tmp.name) + + if __name__ == '__main__': unittest.main() From 8933062db09ef6d68ee5cdc19521d1ee2eddfb04 Mon Sep 17 00:00:00 2001 From: Cosimo Lupo Date: Mon, 7 Dec 2015 19:31:33 +0000 Subject: [PATCH 3/3] xmlReader: make xmlReader accept file objects Fixes https://github.com/behdad/fonttools/issues/404 Also, fixed typo for ProgressPrinter method call: 'setlabel' -> 'setLabel' --- Lib/fontTools/misc/xmlReader.py | 37 +++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/Lib/fontTools/misc/xmlReader.py b/Lib/fontTools/misc/xmlReader.py index a0e80cc43..f8b43b751 100644 --- a/Lib/fontTools/misc/xmlReader.py +++ b/Lib/fontTools/misc/xmlReader.py @@ -13,9 +13,17 @@ BUFSIZE = 0x4000 class XMLReader(object): - def __init__(self, fileName, ttFont, progress=None, quiet=False): + def __init__(self, fileOrPath, ttFont, progress=None, quiet=False): + if fileOrPath == '-': + fileOrPath = sys.stdin + if not hasattr(fileOrPath, "read"): + self.file = open(fileOrPath, "rb") + self._closeStream = True + else: + # assume readable file object + self.file = fileOrPath + self._closeStream = False self.ttFont = ttFont - self.fileName = fileName self.progress = progress self.quiet = quiet self.root = None @@ -24,11 +32,16 @@ class XMLReader(object): def read(self): if self.progress: - import stat - self.progress.set(0, os.stat(self.fileName)[stat.ST_SIZE] // 100 or 1) - file = open(self.fileName, 'rb') - self._parseFile(file) - file.close() + self.file.seek(0, 2) + fileSize = self.file.tell() + self.progress.set(0, fileSize // 100 or 1) + self.file.seek(0) + self._parseFile(self.file) + if self._closeStream: + self.close() + + def close(self): + self.file.close() def _parseFile(self, file): from xml.parsers.expat import ParserCreate @@ -63,7 +76,13 @@ class XMLReader(object): elif stackSize == 1: subFile = attrs.get("src") if subFile is not None: - subFile = os.path.join(os.path.dirname(self.fileName), subFile) + if hasattr(self.file, 'name'): + # if file has a name, get its parent directory + dirname = os.path.dirname(self.file.name) + else: + # else fall back to using the current working directory + dirname = os.getcwd() + subFile = os.path.join(dirname, subFile) subReader = XMLReader(subFile, self.ttFont, self.progress, self.quiet) subReader.read() self.contentStack.append([]) @@ -71,7 +90,7 @@ class XMLReader(object): tag = ttLib.xmlToTag(name) msg = "Parsing '%s' table..." % tag if self.progress: - self.progress.setlabel(msg) + self.progress.setLabel(msg) elif self.ttFont.verbose: ttLib.debugmsg(msg) else: