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