1# -*- coding: utf-8 -*- 2 3from __future__ import print_function, division, absolute_import, unicode_literals 4from fontTools.misc.py23 import * 5import os 6import unittest 7from fontTools.ttLib import TTFont 8from fontTools.misc.xmlReader import XMLReader, ProgressPrinter, BUFSIZE 9import tempfile 10 11 12class TestXMLReader(unittest.TestCase): 13 14 def test_decode_utf8(self): 15 16 class DebugXMLReader(XMLReader): 17 18 def __init__(self, fileOrPath, ttFont, progress=None): 19 super(DebugXMLReader, self).__init__( 20 fileOrPath, ttFont, progress) 21 self.contents = [] 22 23 def _endElementHandler(self, name): 24 if self.stackSize == 3: 25 name, attrs, content = self.root 26 self.contents.append(content) 27 super(DebugXMLReader, self)._endElementHandler(name) 28 29 expected = 'fôôbär' 30 data = '''\ 31<?xml version="1.0" encoding="UTF-8"?> 32<ttFont> 33 <name> 34 <namerecord nameID="1" platformID="3" platEncID="1" langID="0x409"> 35 %s 36 </namerecord> 37 </name> 38</ttFont> 39''' % expected 40 41 with BytesIO(data.encode('utf-8')) as tmp: 42 reader = DebugXMLReader(tmp, TTFont()) 43 reader.read() 44 content = strjoin(reader.contents[0]).strip() 45 self.assertEqual(expected, content) 46 47 def test_normalise_newlines(self): 48 49 class DebugXMLReader(XMLReader): 50 51 def __init__(self, fileOrPath, ttFont, progress=None): 52 super(DebugXMLReader, self).__init__( 53 fileOrPath, ttFont, progress) 54 self.newlines = [] 55 56 def _characterDataHandler(self, data): 57 self.newlines.extend([c for c in data if c in ('\r', '\n')]) 58 59 # notice how when CR is escaped, it is not normalised by the XML parser 60 data = ( 61 '<ttFont>\r' # \r -> \n 62 ' <test>\r\n' # \r\n -> \n 63 ' a line of text\n' # \n 64 ' escaped CR and unix newline \n' # \n -> \r\n 65 ' escaped CR and macintosh newline \r' # \r -> \r\n 66 ' escaped CR and windows newline \r\n' # \r\n -> \r\n 67 ' </test>\n' # \n 68 '</ttFont>') 69 70 with BytesIO(data.encode('utf-8')) as tmp: 71 reader = DebugXMLReader(tmp, TTFont()) 72 reader.read() 73 expected = ['\n'] * 3 + ['\r', '\n'] * 3 + ['\n'] 74 self.assertEqual(expected, reader.newlines) 75 76 def test_progress(self): 77 78 class DummyProgressPrinter(ProgressPrinter): 79 80 def __init__(self, title, maxval=100): 81 self.label = title 82 self.maxval = maxval 83 self.pos = 0 84 85 def set(self, val, maxval=None): 86 if maxval is not None: 87 self.maxval = maxval 88 self.pos = val 89 90 def increment(self, val=1): 91 self.pos += val 92 93 def setLabel(self, text): 94 self.label = text 95 96 data = ( 97 '<ttFont>\n' 98 ' <test>\n' 99 ' %s\n' 100 ' </test>\n' 101 '</ttFont>\n' 102 % ("z" * 2 * BUFSIZE) 103 ).encode('utf-8') 104 105 dataSize = len(data) 106 progressBar = DummyProgressPrinter('test') 107 with BytesIO(data) as tmp: 108 reader = XMLReader(tmp, TTFont(), progress=progressBar) 109 self.assertEqual(progressBar.pos, 0) 110 reader.read() 111 self.assertEqual(progressBar.pos, dataSize // 100) 112 self.assertEqual(progressBar.maxval, dataSize // 100) 113 self.assertTrue('test' in progressBar.label) 114 with BytesIO(b"<ttFont></ttFont>") as tmp: 115 reader = XMLReader(tmp, TTFont(), progress=progressBar) 116 reader.read() 117 # when data size is less than 100 bytes, 'maxval' is 1 118 self.assertEqual(progressBar.maxval, 1) 119 120 def test_close_file_path(self): 121 with tempfile.NamedTemporaryFile(delete=False) as tmp: 122 tmp.write(b'<ttFont></ttFont>') 123 reader = XMLReader(tmp.name, TTFont()) 124 reader.read() 125 # when reading from path, the file is closed automatically at the end 126 self.assertTrue(reader.file.closed) 127 # this does nothing 128 reader.close() 129 self.assertTrue(reader.file.closed) 130 os.remove(tmp.name) 131 132 def test_close_file_obj(self): 133 with tempfile.NamedTemporaryFile(delete=False) as tmp: 134 tmp.write(b'<ttFont>"hello"</ttFont>') 135 with open(tmp.name, "rb") as f: 136 reader = XMLReader(f, TTFont()) 137 reader.read() 138 # when reading from a file or file-like object, the latter is kept open 139 self.assertFalse(reader.file.closed) 140 # ... until the user explicitly closes it 141 reader.close() 142 self.assertTrue(reader.file.closed) 143 os.remove(tmp.name) 144 145 def test_read_sub_file(self): 146 # Verifies that sub-file content is able to be read to a table. 147 expectedContent = 'testContent' 148 expectedNameID = '1' 149 expectedPlatform = '3' 150 expectedLangId = '0x409' 151 152 with tempfile.NamedTemporaryFile(delete=False) as tmp: 153 subFileData = ( 154 '<ttFont ttLibVersion="3.15">' 155 '<name>' 156 '<namerecord nameID="%s" platformID="%s" platEncID="1" langID="%s">' 157 '%s' 158 '</namerecord>' 159 '</name>' 160 '</ttFont>' 161 ) % (expectedNameID, expectedPlatform, expectedLangId, expectedContent) 162 tmp.write(subFileData.encode("utf-8")) 163 164 with tempfile.NamedTemporaryFile(delete=False) as tmp2: 165 fileData = ( 166 '<ttFont ttLibVersion="3.15">' 167 '<name>' 168 '<namerecord src="%s"/>' 169 '</name>' 170 '</ttFont>' 171 ) % tmp.name 172 tmp2.write(fileData.encode('utf-8')) 173 174 ttf = TTFont() 175 with open(tmp2.name, "rb") as f: 176 reader = XMLReader(f, ttf) 177 reader.read() 178 reader.close() 179 nameTable = ttf['name'] 180 self.assertTrue(int(expectedNameID) == nameTable.names[0].nameID) 181 self.assertTrue(int(expectedLangId, 16) == nameTable.names[0].langID) 182 self.assertTrue(int(expectedPlatform) == nameTable.names[0].platformID) 183 self.assertEqual(expectedContent, nameTable.names[0].string.decode(nameTable.names[0].getEncoding())) 184 185 os.remove(tmp.name) 186 os.remove(tmp2.name) 187 188if __name__ == '__main__': 189 import sys 190 sys.exit(unittest.main()) 191