xmlReader.py 6.4 KB

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