class ClipExtractor(ContentHandler): def __init__(self, output, clipId): self._sink = XMLGenerator(output, 'UTF-8', True) self._locator = None self._id = clipId def setDocumentLocator(self, locator): self._locator = locator self._skip = None self._stopAt = None return self._sink.setDocumentLocator(locator) def startDocument(self): # self._prefixes = {} self.scale = self.units = None self._context = [] self._outerSvgRendered = False return self._sink.startDocument() def endDocument(self): # self._prefixes.clear() return self._sink.endDocument() def startPrefixMapping(self, prefix, uri): self._context.append(('xmlns', None, prefix, uri)) # mappings = self._prefixes.get(prefix) # if mappings is None: # self._prefixes[prefix] = mappings = [] # mappings.append(uri) return self._sink.startPrefixMapping(prefix, uri) def endPrefixMapping(self, prefix): context = self._context.pop() assert ('xmlns', None, prefix) == context[:-1] # mappings = self._prefixes.get(prefix) # assert mappings is not None # mappings.pop() return self._sink.endPrefixMapping(prefix) def startElement(self, qname, attrs): raise xml.sax.SAXNotSupportedException( 'This handler must be used with feature "%s"' ' turned on' % xml.sax.handler.feature_namespaces) # if 'xmlns' in attrs: # if self._context: # raise xml.sax.SAXNotSupportedException( # 'This document must be parsed with feature "%s"' # ' turned on' # % xml.sax.handler.feature_namespaces # ) # else: # self.startPrefixMapping('', attrs.get('xmlns')) # ns, name = self._splitNS(qname) # handler = self.ELEMENTS.get((ns, name)) # if handler and handler[0]: # update = handler[0](self, attrs, ns, name) # if not update: # if self._skip is None: # self._skip = len(self._context) + 1 # elif isinstance(update, collections.Sequence): # attrs = update[0] # if len(update) > 1: # ns, name, qname = update[1:] # self._context.append((ns, name, qname, attrs.copy())) # if self._skip is None: # return self._sink.startElement(qname, attrs) def endElement(self, qname): raise xml.sax.SAXNotSupportedException( 'This handler must be used with feature "%s"' ' turned on' % xml.sax.handler.feature_namespaces) # ns, name = self._splitNS(qname) # handler = self.ELEMENTS.get((ns, name)) # if handler and handler[1]: # handler[1](self, ns, name) # context = self._context.pop() # assert (ns, name) == context[:2] # if self._skip is None: # self._sink.endElement(qname) # elif len(self._context) < self._skip: # self._skip = None # if len(self._context) == self._stopAt: # raise self.Done('extraction complete', # self._locator.getLineNumber(), # self._locator.getColumnNumber() # ) def startElementNS(self, name, qname, attrs): if (None, 'xmlns') in attrs: self.startPrefixMapping(None, attrs.get('xmlns')) handler = self.ELEMENTS.get(name) if handler and handler[0]: update = handler[0](self, attrs, *name, qname) if not update: if self._skip is None: self._skip = len(self._context) + 1 elif isinstance(update, collections.Sequence): attrs = update[0] if len(update) > 1: name = tuple(update[1:3]) if len(update) > 3: qname = update[3] self._context.append(name + (qname, attrs.copy())) if self._skip is None: return self._sink.startElementNS(name, qname, attrs) def endElementNS(self, name, qname): handler = self.ELEMENTS.get(name) if handler and handler[1]: handler[1](self, *name, qname) context = self._context.pop() assert name == context[:2] if self._skip is None: self._sink.endElementNS(name, qname) elif len(self._context) < self._skip: self._skip = None if len(self._context) == self._stopAt: while self._context: toClose = self._context.pop() if 'xmlns' == toClose[0]: self._sink.endPrefixMapping(toClose[2]) else: self._sink.ignorableWhitespace('\n') self._sink.endElementNS(toClose[:2], toClose[2]) self._sink.endDocument() raise self.Done('extraction complete', (self._locator.getLineNumber(), self._locator.getColumnNumber())) def elementSvgEnd(self, *args): attrs = self._context[-1][3] if self.ATTR_ID in attrs and \ self._id == attrs.get(self.ATTR_ID): self._stopAt = len(self._context) - 1 def elementSvgStart(self, attrs, *args): if any((CardBackView.SVG_NAMESPACE, 'svg') == e[:2] for e in self._context): return self.ATTR_ID in attrs and \ self._id == attrs.get(self.ATTR_ID) elif not 'viewBox' in attrs and not (None, 'viewBox') in attrs: self._error(None, 'Main <svg> element has no viewBox attribute') else: try: internalSize = attrs.get((None, 'viewBox')) if internalSize is None: internalSize = attrs.get('viewBox') internalSize = self.parseBox(internalSize)[2:] externalSize = list(internalSize) units = [''] * 2 i = -1 for attr in CardBackView.ATTRS_SVG_DIMENSION: i += 1 value, unit = self._distanceAttr(attrs, attr) if unit.strip() == '%': value = None if value is not None: externalSize[i] = value units[i] = unit self.units = tuple(units) try: self.scale = tuple( e / i for e, i in zip(externalSize, internalSize)) except ZeroDivisionError as error: self._error( None, 'Found <svg> with zero dimension(s), viewBox="%s"' % attrs.get((None, 'viewBox'))) return False except: self._error() def elementUseStart(self, attrs, ns, name, qname): target = attrs.get(CardBackView.XLINK_HREF_ATTR) if not target: return False target = target.strip() if '#' == target[0] and self._id == target[1:] \ and not self._outerSvgRendered: try: outerSvg = next(e for e in self._context if (CardBackView.SVG_NAMESPACE, 'svg') == e[:2]) except StopIteration: self._error(None, 'Found <use> element outside of an <svg>') size = [] for attr in CardBackView.ATTRS_SVG_DIMENSION: value, unit = self._distanceAttr(attrs, attr) if unit.strip(): self._error( None, 'Attribute <use %s="%s" ...> is not valid' ' in this context, expected a unit-free number.' % (attr, attrs.get(None, attr))) if value is None: self._error( None, 'Attribute `%s` is missing from <use> element,' ' but is expected in this context.' % attr) size.append(value) sattrs = ('%.6f%s' % (s * v, u) for s, v, u in zip(self.scale, size, self.units)) qnames = { name: outerSvg[3].getQNameByName(name) for name in outerSvg[3].getNames() } attrMap = dict(outerSvg[3].items()) attrMap[(None, 'viewBox')] = self.boxValue((0, 0) + tuple(size)) for attr in zip(CardBackView.ATTRS_SVG_DIMENSION, sattrs): attrMap[(None, attr[0])] = attr[1] self._sink.startElementNS(outerSvg[:2], outerSvg[2], AttributesNSImpl(attrMap, qnames)) self._sink.ignorableWhitespace('\n') self._outerSvgRendered = True qnames = { name: attrs.getQNameByName(name) for name in attrs.getNames() } attrMap = dict(attrs.items()) for attr in ('x', 'y') + CardBackView.ATTRS_SVG_DIMENSION: qnames.pop((None, attr), None) attrMap.pop((None, attr), None) self._sink.startElementNS((ns, name), qname, AttributesNSImpl(attrMap, qnames)) self._sink.endElementNS((ns, name), qname) self._sink.ignorableWhitespace('\n') return False def elementDefsStart(self, attrs, *args): if not self._outerSvgRendered: self._error( None, 'Element <use xlink:href="#%s" ...> must precede' ' the <defs> element in this context.' % self._id) self._skip = None return True ATTR_ID = (None, 'id') ELEMENTS = { (CardBackView.SVG_NAMESPACE, 'svg'): (elementSvgStart, elementSvgEnd), (CardBackView.SVG_NAMESPACE, 'use'): (elementUseStart, None), (CardBackView.SVG_NAMESPACE, 'defs'): (elementDefsStart, None), } def characters(self, content): if self._skip is None: return self._sink.characters(content) def ignorableWhitespace(self, whitespace): if self._skip is None: return self._sink.ignorableWhitespace(whitespace) def processingInstruction(self, target, data): if self._skip is None: return self._sink.processingInstruction(target, data) def skippedEntity(self, name): if self._skip is None: return self._sink.skippedEntity(name) def _error(self, error=True, message=''): if error == True: error = sys.exc_info()[1] if error is None: assert message error = SAXParseException(message, None, self._locator) elif not isinstance(error, SAXParseException): msg = error.args[0] if error.args else '' if msg: msg += ' ' if message: msg += message + ' ' error.args = (msg + 'at line %d, column %d.' % (self._locator.getLineNumber(), self._locator.getColumnNumber()), ) + error.args[1:] raise error class Done(xml.sax.SAXException): pass
def setDocumentLocator(self, locator): XMLGenerator.setDocumentLocator(self, locator) self.location = Location(self._locator)
def setDocumentLocator(self, locator): XMLGenerator.setDocumentLocator(self, locator) self.location = Location(self._locator)