# HG changeset patch # User paulb # Date 1174863335 0 # Node ID 539f94ca11aa7c57ca14ec1a53d52637671c264d # Parent 726ae810cb37d0f3772e5ec2993724ed00298d78 [project @ 2007-03-25 22:55:35 by paulb] Defined the Document class functionality in terms of an inheritable _Document class and inherited Node functionality. Introduced traits support along with more complete rectangle, path and matrix classes. Added convenience functions for SVG document creation. diff -r 726ae810cb37 -r 539f94ca11aa libxml2dom/__init__.py --- a/libxml2dom/__init__.py Sun Mar 25 01:59:26 2007 +0000 +++ b/libxml2dom/__init__.py Sun Mar 25 22:55:35 2007 +0000 @@ -459,9 +459,12 @@ # Document housekeeping mechanisms. -class Document(Node): +class _Document: - "A class providing document-level housekeeping." + """ + An abstract class providing document-level housekeeping and distinct + functionality. + """ def __init__(self, node, impl): self._node = node @@ -480,6 +483,15 @@ documentElement = property(_documentElement) ownerDocument = property(_ownerDocument) +class Document(_Document, Node): + + """ + A generic document class. Specialised document classes should inherit from + the _Document class and their own variation of Node. + """ + + pass + class DocumentType(object): "A class providing a container for document type information." diff -r 726ae810cb37 -r 539f94ca11aa libxml2dom/svg.py --- a/libxml2dom/svg.py Sun Mar 25 01:59:26 2007 +0000 +++ b/libxml2dom/svg.py Sun Mar 25 22:55:35 2007 +0000 @@ -25,8 +25,11 @@ import libxml2dom from libxml2dom.events import * from libxml2dom.macrolib import * +from libxml2dom.macrolib import \ + createDocument as Node_createDocument import xml.dom import math +import re SVG_NAMESPACE = "http://www.w3.org/2000/svg" @@ -77,6 +80,14 @@ else: return libxml2dom.Implementation.get_node(self, _node, context_node) + # Convenience functions. + + def createSVGDocument(self): + + "Create a new SVG document." + + return SVGDocument(Node_createDocument(SVG_NAMESPACE, "svg", None), self) + # Interfaces and helper classes. class AsyncStatusCallback: @@ -93,6 +104,43 @@ def __init__(self, success, contentType, content): self.success, self.contentType, self.content = success, contentType, content +class ElementTraversal: + + "An interface for element traversal." + + def _firstElementChild(self): + l = self.xpath("*") + if l: + return l[0] + else: + return None + + def _lastElementChild(self): + l = self.xpath("*") + if l: + return l[-1] + else: + return None + + def _nextElementSibling(self): + l = self.xpath("following-sibling::*") + if l: + return l[0] + else: + return None + + def _previousElementSibling(self): + l = self.xpath("preceding-sibling::*") + if l: + return l[0] + else: + return None + + firstElementChild = property(_firstElementChild) + lastElementChild = property(_lastElementChild) + nextElementSibling = property(_nextElementSibling) + previousElementSibling = property(_previousElementSibling) + class SVGGlobal: # Global, EventListenerInitializer2 "An SVG global." @@ -155,9 +203,134 @@ See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGMatrix """ - def __init__(self, a, b, c, d, e, f): + translate_regexp = re.compile("translate\((.*)\)$") + scale_regexp = re.compile("scale\((.*)\)$") + rotate_regexp = re.compile("rotate\((.*)\)$") + skewX_regexp = re.compile("skewX\((.*)\)$") + skewY_regexp = re.compile("skewY\((.*)\)$") + matrix_regexp = re.compile("matrix\((.*)\)$") + + def __init__(self, a=0, b=0, c=0, d=0, e=0, f=0): self.matrix = a, b, c, d, e, f + def __eq__(self, other): + return self.matrix == other.matrix + + def __ne__(self, other): + return not (self == other) + + def _get_params(self, param_string): + return map(float, map(lambda s: s.strip(), param_string.split(","))) + + def fromNode(self, node, name): + + """ + Initialise this object from the trait on the 'node' having the given + 'name'. + """ + + value = node.getAttribute(name) + if value is None: + raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR) + + value = value.strip() + + # Translation. + + m = self.translate_regexp.match(value) + if m: + a, b, c, d = 1, 0, 0, 1 + e, f = self._get_params(m.group(1)) + self.matrix = a, b, c, d, e, f + return + + # Scaling. + + m = self.scale_regexp.match(value) + if m: + b, c, e, f = 0, 0, 0, 0 + a, d = self._get_params(m.group(1)) + self.matrix = a, b, c, d, e, f + return + + # Rotation. + + m = self.rotate_regexp.match(value) + if m: + e, f = 0, 0 + angle = float(m.group(1).strip()) + a = d = math.cos(math.radians(angle)) + b = math.sin(math.radians(angle)) + c = -b + self.matrix = a, b, c, d, e, f + return + + # Skew. + + m = self.skewX_regexp.match(value) + if m: + a, b, d, e, f = 1, 0, 1, 0, 0 + angle = float(m.group(1).strip()) + c = math.tan(math.radians(angle)) + self.matrix = a, b, c, d, e, f + return + + m = self.skewY_regexp.match(value) + if m: + a, c, d, e, f = 1, 0, 1, 0, 0 + angle = float(m.group(1).strip()) + b = math.tan(math.radians(angle)) + self.matrix = a, b, c, d, e, f + return + + # Generic. + + m = self.matrix_regexp.match(value) + if m: + self.matrix = self._get_params(m.group(1)) + return + + # Otherwise, complain. + + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR) + + def toNode(self, node, name): + + """ + Set the trait on the given 'node' using the given 'name' according to + this object's attributes. + """ + + a, b, c, d, e, f = self.matrix + + # Translation. + + if (a, b, c, d) == (1, 0, 0, 1): + node.setAttribute(name, "translate(%f, %f)" % (e, f)) + + # Scaling. + + elif (b, c, e, f) == (0, 0, 0, 0): + node.setAttribute(name, "scale(%f, %f)" % (a, d)) + + # Rotation. + + elif a == d and b == -c and (e, f) == (0, 0) and math.degrees(math.acos(a)) == math.degrees(math.asin(b)): + node.setAttribute(name, "rotate(%f)" % math.degrees(math.acos(a))) + + # Skew. + + elif (a, b, d, e, f) == (1, 0, 1, 0, 0) and c != 0: + node.setAttribute(name, "skewX(%f)" % math.degrees(math.atan(c))) + + elif (a, c, d, e, f) == (1, 0, 1, 0, 0) and b != 0: + node.setAttribute(name, "skewX(%f)" % math.degrees(math.atan(b))) + + # Generic matrix. + + else: + node.setAttribute(name, "matrix(%f, %f, %f, %f, %f, %f)" % (a, b, c, d, e, f)) + def getComponent(self, index): """ @@ -238,7 +411,15 @@ [ 0 0 1 ] """ - return self.mMultiply(SVGMatrix(math.cos(angle), math.sin(angle), -math.sin(angle), math.cos(angle), 0, 0)) + return self.mMultiply( + SVGMatrix( + math.cos(math.radians(angle)), + math.sin(math.radians(angle)), + -math.sin(math.radians(angle)), + math.cos(math.radians(angle)), + 0, 0 + ) + ) class SVGPath: @@ -253,10 +434,74 @@ CURVE_TO = 67 QUAD_TO = 81 CLOSE = 90 + _CLOSE = 122 # More baggage (name not standard). + + nparams = { + MOVE_TO : 2, + LINE_TO : 2, + CURVE_TO : 6, + QUAD_TO : 4, + CLOSE : 0, + _CLOSE : 0 + } def __init__(self): self.segments = [] + def __eq__(self, other): + return self.segments == other.segments + + def __ne__(self, other): + return not (self == other) + + def fromNode(self, node, name): + + """ + Initialise this object from the trait on the 'node' having the given + 'name'. + """ + + value = node.getAttribute(name) + if value is None: + raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR) + + # Try and unpack the attribute value. + + data = value.split() + self.segments = [] + try: + i = 0 + while i < len(data): + cmd = ord(data[i]) + if cmd == self._CLOSE: + cmd = self.CLOSE + i += 1 + n = self.nparams[cmd] + params = map(float, data[i:i+n]) + self.segments.append((cmd, params)) + i += n + except (IndexError, ValueError): + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR) + + def toNode(self, node, name): + + """ + Set the trait on the given 'node' using the given 'name' according to + this object's attributes. + """ + + try: + l = [] + for cmd, params in self.segments: + l.append(unichr(cmd)) + for param in params: + l.append(str(param)) + node.setAttribute(name, " ".join(l)) + except (IndexError, ValueError): + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR) + + # Interface methods. + def _numberOfSegments(self): return len(self.segments) @@ -291,7 +536,7 @@ class SVGPoint: - "A point." + "A point used to provide currentTranslate." def __init__(self, x, y): self.x = x @@ -301,9 +546,44 @@ "A rectangle." - def __init__(self, x, y, width, height): + def __init__(self, x=0, y=0, width=0, height=0): self.x, self.y, self.width, self.height = x, y, width, height + def __eq__(self, other): + return (self.x, self.y, self.width, self.height) == (other.x, other.y, other.width, other.height) + + def __ne__(self, other): + return not (self == other) + + def fromNode(self, node, name): + + """ + Initialise this object from the trait on the 'node' having the given + 'name'. + """ + + value = node.getAttribute(name) + if value is None: + raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR) + try: + values = map(float, value.split()) + self.x, self.y, self.width, self.height = values + except (IndexError, ValueError): + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR) + + def toNode(self, node, name): + + """ + Set the trait on the given 'node' using the given 'name' according to + this object's attributes. + """ + + try: + values = map(str, [self.x, self.y, self.width, self.height]) + node.setAttribute(name, " ".join(values)) + except (IndexError, ValueError): + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR) + class SVGRGBColor: "A colour." @@ -311,9 +591,57 @@ def __init__(self, red, green, blue): self.red, self.green, self.blue = red, green, blue +class TraitAccess: + + """ + Access to traits stored on elements. + See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__TraitAccess + """ + + def getPathTrait(self, name): + path = SVGPath() + path.fromNode(self, name) + return path + + def setPathTrait(self, name, path): + path.toNode(self, name) + + def getRectTrait(self, name): + rect = SVGRect() + rect.fromNode(self, name) + return rect + + def setRectTrait(self, name, rect): + rect.toNode(self, name) + + def getMatrixTrait(self, name): + matrix = SVGMatrix() + matrix.fromNode(self, name) + return matrix + + def setMatrixTrait(self, name, matrix): + matrix.toNode(self, name) + # Node classes. -class SVGDocument(libxml2dom.Document, DocumentEvent, EventTarget): +class SVGNode(libxml2dom.Node): + + "Convenience modifications to nodes specific to libxml2dom.svg." + + def xpath(self, expr, variables=None, namespaces=None): + + """ + Evaluate the given 'expr' using the optional 'variables' and + 'namespaces'. If not otherwise specified, the "svg" prefix will be bound + to SVG_NAMESPACE as defined in this module. + """ + + namespaces = namespaces or {} + if not namespaces.has_key("svg"): + namespaces["svg"] = SVG_NAMESPACE + return libxml2dom.Node.xpath(self, expr, variables, namespaces) + +class SVGDocument(libxml2dom._Document, SVGNode, DocumentEvent, EventTarget): "An SVG-specific document node." @@ -324,10 +652,10 @@ and global (SVGGlobal) details. """ - libxml2dom.Document.__init__(self, node, impl) + libxml2dom._Document.__init__(self, node, impl) self.global_ = SVGGlobal(self) # parent -class SVGElement(libxml2dom.Node, EventTarget): # (Element), TraitAccess, ElementTraversal +class SVGElement(SVGNode, EventTarget, TraitAccess, ElementTraversal): # NOTE: SVGNode instead of Element. "An SVG-specific element." @@ -401,11 +729,7 @@ return self.translate def _viewport(self): - attr = self.getAttribute("viewBox") - if attr is None: - return attr - l = map(int, attr.split()) - return SVGRect(*l) + return self.getRectTrait("viewBox") def getCurrentTime(self): return self.document_time @@ -417,7 +741,7 @@ return SVGMatrix(a, b, c, d, e, f) def createSVGRect(self): - return SVGRect(0, 0, 0, 0) + return SVGRect() def createSVGPath(self): return SVGPath() @@ -439,7 +763,13 @@ currentTranslate = property(_currentTranslate) viewport = property(_viewport) -# Convenience functions. +# Utility functions. + +createDocument = libxml2dom.createDocument +createDocumentType = libxml2dom.createDocumentType + +def createSVGDocument(): + return default_impl.createSVGDocument() def parse(stream_or_string, html=0, htmlencoding=None): return libxml2dom.parse(stream_or_string, html, htmlencoding, default_impl)