Merge pull request #425 from anthrotype/xmlreader-fileobj
make XMLReader accept file objects
This commit is contained in:
commit
9049d5f866
@ -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:
|
||||
|
@ -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):
|
||||
</ttFont>
|
||||
''' % 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
|
||||
' </test>\n' # \n
|
||||
'</ttFont>')
|
||||
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 = (
|
||||
'<ttFont>\n'
|
||||
' <test>\n'
|
||||
' %s\n'
|
||||
' </test>\n'
|
||||
'</ttFont>\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"<ttFont></ttFont>") 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'<ttFont></ttFont>')
|
||||
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'<ttFont>"hello"</ttFont>')
|
||||
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()
|
||||
|
@ -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):
|
||||
|
Loading…
x
Reference in New Issue
Block a user