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