xref: /aosp_15_r20/external/fonttools/Lib/fontTools/misc/xmlReader.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1from fontTools import ttLib
2from fontTools.misc.textTools import safeEval
3from fontTools.ttLib.tables.DefaultTable import DefaultTable
4import sys
5import os
6import logging
7
8
9log = logging.getLogger(__name__)
10
11
12class TTXParseError(Exception):
13    pass
14
15
16BUFSIZE = 0x4000
17
18
19class XMLReader(object):
20    def __init__(
21        self, fileOrPath, ttFont, progress=None, quiet=None, contentOnly=False
22    ):
23        if fileOrPath == "-":
24            fileOrPath = sys.stdin
25        if not hasattr(fileOrPath, "read"):
26            self.file = open(fileOrPath, "rb")
27            self._closeStream = True
28        else:
29            # assume readable file object
30            self.file = fileOrPath
31            self._closeStream = False
32        self.ttFont = ttFont
33        self.progress = progress
34        if quiet is not None:
35            from fontTools.misc.loggingTools import deprecateArgument
36
37            deprecateArgument("quiet", "configure logging instead")
38            self.quiet = quiet
39        self.root = None
40        self.contentStack = []
41        self.contentOnly = contentOnly
42        self.stackSize = 0
43
44    def read(self, rootless=False):
45        if rootless:
46            self.stackSize += 1
47        if self.progress:
48            self.file.seek(0, 2)
49            fileSize = self.file.tell()
50            self.progress.set(0, fileSize // 100 or 1)
51            self.file.seek(0)
52        self._parseFile(self.file)
53        if self._closeStream:
54            self.close()
55        if rootless:
56            self.stackSize -= 1
57
58    def close(self):
59        self.file.close()
60
61    def _parseFile(self, file):
62        from xml.parsers.expat import ParserCreate
63
64        parser = ParserCreate()
65        parser.StartElementHandler = self._startElementHandler
66        parser.EndElementHandler = self._endElementHandler
67        parser.CharacterDataHandler = self._characterDataHandler
68
69        pos = 0
70        while True:
71            chunk = file.read(BUFSIZE)
72            if not chunk:
73                parser.Parse(chunk, 1)
74                break
75            pos = pos + len(chunk)
76            if self.progress:
77                self.progress.set(pos // 100)
78            parser.Parse(chunk, 0)
79
80    def _startElementHandler(self, name, attrs):
81        if self.stackSize == 1 and self.contentOnly:
82            # We already know the table we're parsing, skip
83            # parsing the table tag and continue to
84            # stack '2' which begins parsing content
85            self.contentStack.append([])
86            self.stackSize = 2
87            return
88        stackSize = self.stackSize
89        self.stackSize = stackSize + 1
90        subFile = attrs.get("src")
91        if subFile is not None:
92            if hasattr(self.file, "name"):
93                # if file has a name, get its parent directory
94                dirname = os.path.dirname(self.file.name)
95            else:
96                # else fall back to using the current working directory
97                dirname = os.getcwd()
98            subFile = os.path.join(dirname, subFile)
99        if not stackSize:
100            if name != "ttFont":
101                raise TTXParseError("illegal root tag: %s" % name)
102            if self.ttFont.reader is None and not self.ttFont.tables:
103                sfntVersion = attrs.get("sfntVersion")
104                if sfntVersion is not None:
105                    if len(sfntVersion) != 4:
106                        sfntVersion = safeEval('"' + sfntVersion + '"')
107                    self.ttFont.sfntVersion = sfntVersion
108            self.contentStack.append([])
109        elif stackSize == 1:
110            if subFile is not None:
111                subReader = XMLReader(subFile, self.ttFont, self.progress)
112                subReader.read()
113                self.contentStack.append([])
114                return
115            tag = ttLib.xmlToTag(name)
116            msg = "Parsing '%s' table..." % tag
117            if self.progress:
118                self.progress.setLabel(msg)
119            log.info(msg)
120            if tag == "GlyphOrder":
121                tableClass = ttLib.GlyphOrder
122            elif "ERROR" in attrs or ("raw" in attrs and safeEval(attrs["raw"])):
123                tableClass = DefaultTable
124            else:
125                tableClass = ttLib.getTableClass(tag)
126                if tableClass is None:
127                    tableClass = DefaultTable
128            if tag == "loca" and tag in self.ttFont:
129                # Special-case the 'loca' table as we need the
130                #    original if the 'glyf' table isn't recompiled.
131                self.currentTable = self.ttFont[tag]
132            else:
133                self.currentTable = tableClass(tag)
134                self.ttFont[tag] = self.currentTable
135            self.contentStack.append([])
136        elif stackSize == 2 and subFile is not None:
137            subReader = XMLReader(subFile, self.ttFont, self.progress, contentOnly=True)
138            subReader.read()
139            self.contentStack.append([])
140            self.root = subReader.root
141        elif stackSize == 2:
142            self.contentStack.append([])
143            self.root = (name, attrs, self.contentStack[-1])
144        else:
145            l = []
146            self.contentStack[-1].append((name, attrs, l))
147            self.contentStack.append(l)
148
149    def _characterDataHandler(self, data):
150        if self.stackSize > 1:
151            # parser parses in chunks, so we may get multiple calls
152            # for the same text node; thus we need to append the data
153            # to the last item in the content stack:
154            # https://github.com/fonttools/fonttools/issues/2614
155            if (
156                data != "\n"
157                and self.contentStack[-1]
158                and isinstance(self.contentStack[-1][-1], str)
159                and self.contentStack[-1][-1] != "\n"
160            ):
161                self.contentStack[-1][-1] += data
162            else:
163                self.contentStack[-1].append(data)
164
165    def _endElementHandler(self, name):
166        self.stackSize = self.stackSize - 1
167        del self.contentStack[-1]
168        if not self.contentOnly:
169            if self.stackSize == 1:
170                self.root = None
171            elif self.stackSize == 2:
172                name, attrs, content = self.root
173                self.currentTable.fromXML(name, attrs, content, self.ttFont)
174                self.root = None
175
176
177class ProgressPrinter(object):
178    def __init__(self, title, maxval=100):
179        print(title)
180
181    def set(self, val, maxval=None):
182        pass
183
184    def increment(self, val=1):
185        pass
186
187    def setLabel(self, text):
188        print(text)
189