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