1.1 --- a/libxml2dom/svg.py Sun Mar 25 01:59:26 2007 +0000
1.2 +++ b/libxml2dom/svg.py Sun Mar 25 22:55:35 2007 +0000
1.3 @@ -25,8 +25,11 @@
1.4 import libxml2dom
1.5 from libxml2dom.events import *
1.6 from libxml2dom.macrolib import *
1.7 +from libxml2dom.macrolib import \
1.8 + createDocument as Node_createDocument
1.9 import xml.dom
1.10 import math
1.11 +import re
1.12
1.13 SVG_NAMESPACE = "http://www.w3.org/2000/svg"
1.14
1.15 @@ -77,6 +80,14 @@
1.16 else:
1.17 return libxml2dom.Implementation.get_node(self, _node, context_node)
1.18
1.19 + # Convenience functions.
1.20 +
1.21 + def createSVGDocument(self):
1.22 +
1.23 + "Create a new SVG document."
1.24 +
1.25 + return SVGDocument(Node_createDocument(SVG_NAMESPACE, "svg", None), self)
1.26 +
1.27 # Interfaces and helper classes.
1.28
1.29 class AsyncStatusCallback:
1.30 @@ -93,6 +104,43 @@
1.31 def __init__(self, success, contentType, content):
1.32 self.success, self.contentType, self.content = success, contentType, content
1.33
1.34 +class ElementTraversal:
1.35 +
1.36 + "An interface for element traversal."
1.37 +
1.38 + def _firstElementChild(self):
1.39 + l = self.xpath("*")
1.40 + if l:
1.41 + return l[0]
1.42 + else:
1.43 + return None
1.44 +
1.45 + def _lastElementChild(self):
1.46 + l = self.xpath("*")
1.47 + if l:
1.48 + return l[-1]
1.49 + else:
1.50 + return None
1.51 +
1.52 + def _nextElementSibling(self):
1.53 + l = self.xpath("following-sibling::*")
1.54 + if l:
1.55 + return l[0]
1.56 + else:
1.57 + return None
1.58 +
1.59 + def _previousElementSibling(self):
1.60 + l = self.xpath("preceding-sibling::*")
1.61 + if l:
1.62 + return l[0]
1.63 + else:
1.64 + return None
1.65 +
1.66 + firstElementChild = property(_firstElementChild)
1.67 + lastElementChild = property(_lastElementChild)
1.68 + nextElementSibling = property(_nextElementSibling)
1.69 + previousElementSibling = property(_previousElementSibling)
1.70 +
1.71 class SVGGlobal: # Global, EventListenerInitializer2
1.72
1.73 "An SVG global."
1.74 @@ -155,9 +203,134 @@
1.75 See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGMatrix
1.76 """
1.77
1.78 - def __init__(self, a, b, c, d, e, f):
1.79 + translate_regexp = re.compile("translate\((.*)\)$")
1.80 + scale_regexp = re.compile("scale\((.*)\)$")
1.81 + rotate_regexp = re.compile("rotate\((.*)\)$")
1.82 + skewX_regexp = re.compile("skewX\((.*)\)$")
1.83 + skewY_regexp = re.compile("skewY\((.*)\)$")
1.84 + matrix_regexp = re.compile("matrix\((.*)\)$")
1.85 +
1.86 + def __init__(self, a=0, b=0, c=0, d=0, e=0, f=0):
1.87 self.matrix = a, b, c, d, e, f
1.88
1.89 + def __eq__(self, other):
1.90 + return self.matrix == other.matrix
1.91 +
1.92 + def __ne__(self, other):
1.93 + return not (self == other)
1.94 +
1.95 + def _get_params(self, param_string):
1.96 + return map(float, map(lambda s: s.strip(), param_string.split(",")))
1.97 +
1.98 + def fromNode(self, node, name):
1.99 +
1.100 + """
1.101 + Initialise this object from the trait on the 'node' having the given
1.102 + 'name'.
1.103 + """
1.104 +
1.105 + value = node.getAttribute(name)
1.106 + if value is None:
1.107 + raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR)
1.108 +
1.109 + value = value.strip()
1.110 +
1.111 + # Translation.
1.112 +
1.113 + m = self.translate_regexp.match(value)
1.114 + if m:
1.115 + a, b, c, d = 1, 0, 0, 1
1.116 + e, f = self._get_params(m.group(1))
1.117 + self.matrix = a, b, c, d, e, f
1.118 + return
1.119 +
1.120 + # Scaling.
1.121 +
1.122 + m = self.scale_regexp.match(value)
1.123 + if m:
1.124 + b, c, e, f = 0, 0, 0, 0
1.125 + a, d = self._get_params(m.group(1))
1.126 + self.matrix = a, b, c, d, e, f
1.127 + return
1.128 +
1.129 + # Rotation.
1.130 +
1.131 + m = self.rotate_regexp.match(value)
1.132 + if m:
1.133 + e, f = 0, 0
1.134 + angle = float(m.group(1).strip())
1.135 + a = d = math.cos(math.radians(angle))
1.136 + b = math.sin(math.radians(angle))
1.137 + c = -b
1.138 + self.matrix = a, b, c, d, e, f
1.139 + return
1.140 +
1.141 + # Skew.
1.142 +
1.143 + m = self.skewX_regexp.match(value)
1.144 + if m:
1.145 + a, b, d, e, f = 1, 0, 1, 0, 0
1.146 + angle = float(m.group(1).strip())
1.147 + c = math.tan(math.radians(angle))
1.148 + self.matrix = a, b, c, d, e, f
1.149 + return
1.150 +
1.151 + m = self.skewY_regexp.match(value)
1.152 + if m:
1.153 + a, c, d, e, f = 1, 0, 1, 0, 0
1.154 + angle = float(m.group(1).strip())
1.155 + b = math.tan(math.radians(angle))
1.156 + self.matrix = a, b, c, d, e, f
1.157 + return
1.158 +
1.159 + # Generic.
1.160 +
1.161 + m = self.matrix_regexp.match(value)
1.162 + if m:
1.163 + self.matrix = self._get_params(m.group(1))
1.164 + return
1.165 +
1.166 + # Otherwise, complain.
1.167 +
1.168 + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.169 +
1.170 + def toNode(self, node, name):
1.171 +
1.172 + """
1.173 + Set the trait on the given 'node' using the given 'name' according to
1.174 + this object's attributes.
1.175 + """
1.176 +
1.177 + a, b, c, d, e, f = self.matrix
1.178 +
1.179 + # Translation.
1.180 +
1.181 + if (a, b, c, d) == (1, 0, 0, 1):
1.182 + node.setAttribute(name, "translate(%f, %f)" % (e, f))
1.183 +
1.184 + # Scaling.
1.185 +
1.186 + elif (b, c, e, f) == (0, 0, 0, 0):
1.187 + node.setAttribute(name, "scale(%f, %f)" % (a, d))
1.188 +
1.189 + # Rotation.
1.190 +
1.191 + elif a == d and b == -c and (e, f) == (0, 0) and math.degrees(math.acos(a)) == math.degrees(math.asin(b)):
1.192 + node.setAttribute(name, "rotate(%f)" % math.degrees(math.acos(a)))
1.193 +
1.194 + # Skew.
1.195 +
1.196 + elif (a, b, d, e, f) == (1, 0, 1, 0, 0) and c != 0:
1.197 + node.setAttribute(name, "skewX(%f)" % math.degrees(math.atan(c)))
1.198 +
1.199 + elif (a, c, d, e, f) == (1, 0, 1, 0, 0) and b != 0:
1.200 + node.setAttribute(name, "skewX(%f)" % math.degrees(math.atan(b)))
1.201 +
1.202 + # Generic matrix.
1.203 +
1.204 + else:
1.205 + node.setAttribute(name, "matrix(%f, %f, %f, %f, %f, %f)" % (a, b, c, d, e, f))
1.206 +
1.207 def getComponent(self, index):
1.208
1.209 """
1.210 @@ -238,7 +411,15 @@
1.211 [ 0 0 1 ]
1.212 """
1.213
1.214 - return self.mMultiply(SVGMatrix(math.cos(angle), math.sin(angle), -math.sin(angle), math.cos(angle), 0, 0))
1.215 + return self.mMultiply(
1.216 + SVGMatrix(
1.217 + math.cos(math.radians(angle)),
1.218 + math.sin(math.radians(angle)),
1.219 + -math.sin(math.radians(angle)),
1.220 + math.cos(math.radians(angle)),
1.221 + 0, 0
1.222 + )
1.223 + )
1.224
1.225 class SVGPath:
1.226
1.227 @@ -253,10 +434,74 @@
1.228 CURVE_TO = 67
1.229 QUAD_TO = 81
1.230 CLOSE = 90
1.231 + _CLOSE = 122 # More baggage (name not standard).
1.232 +
1.233 + nparams = {
1.234 + MOVE_TO : 2,
1.235 + LINE_TO : 2,
1.236 + CURVE_TO : 6,
1.237 + QUAD_TO : 4,
1.238 + CLOSE : 0,
1.239 + _CLOSE : 0
1.240 + }
1.241
1.242 def __init__(self):
1.243 self.segments = []
1.244
1.245 + def __eq__(self, other):
1.246 + return self.segments == other.segments
1.247 +
1.248 + def __ne__(self, other):
1.249 + return not (self == other)
1.250 +
1.251 + def fromNode(self, node, name):
1.252 +
1.253 + """
1.254 + Initialise this object from the trait on the 'node' having the given
1.255 + 'name'.
1.256 + """
1.257 +
1.258 + value = node.getAttribute(name)
1.259 + if value is None:
1.260 + raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR)
1.261 +
1.262 + # Try and unpack the attribute value.
1.263 +
1.264 + data = value.split()
1.265 + self.segments = []
1.266 + try:
1.267 + i = 0
1.268 + while i < len(data):
1.269 + cmd = ord(data[i])
1.270 + if cmd == self._CLOSE:
1.271 + cmd = self.CLOSE
1.272 + i += 1
1.273 + n = self.nparams[cmd]
1.274 + params = map(float, data[i:i+n])
1.275 + self.segments.append((cmd, params))
1.276 + i += n
1.277 + except (IndexError, ValueError):
1.278 + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.279 +
1.280 + def toNode(self, node, name):
1.281 +
1.282 + """
1.283 + Set the trait on the given 'node' using the given 'name' according to
1.284 + this object's attributes.
1.285 + """
1.286 +
1.287 + try:
1.288 + l = []
1.289 + for cmd, params in self.segments:
1.290 + l.append(unichr(cmd))
1.291 + for param in params:
1.292 + l.append(str(param))
1.293 + node.setAttribute(name, " ".join(l))
1.294 + except (IndexError, ValueError):
1.295 + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.296 +
1.297 + # Interface methods.
1.298 +
1.299 def _numberOfSegments(self):
1.300 return len(self.segments)
1.301
1.302 @@ -291,7 +536,7 @@
1.303
1.304 class SVGPoint:
1.305
1.306 - "A point."
1.307 + "A point used to provide currentTranslate."
1.308
1.309 def __init__(self, x, y):
1.310 self.x = x
1.311 @@ -301,9 +546,44 @@
1.312
1.313 "A rectangle."
1.314
1.315 - def __init__(self, x, y, width, height):
1.316 + def __init__(self, x=0, y=0, width=0, height=0):
1.317 self.x, self.y, self.width, self.height = x, y, width, height
1.318
1.319 + def __eq__(self, other):
1.320 + return (self.x, self.y, self.width, self.height) == (other.x, other.y, other.width, other.height)
1.321 +
1.322 + def __ne__(self, other):
1.323 + return not (self == other)
1.324 +
1.325 + def fromNode(self, node, name):
1.326 +
1.327 + """
1.328 + Initialise this object from the trait on the 'node' having the given
1.329 + 'name'.
1.330 + """
1.331 +
1.332 + value = node.getAttribute(name)
1.333 + if value is None:
1.334 + raise xml.dom.DOMException(xml.dom.NOT_SUPPORTED_ERR)
1.335 + try:
1.336 + values = map(float, value.split())
1.337 + self.x, self.y, self.width, self.height = values
1.338 + except (IndexError, ValueError):
1.339 + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.340 +
1.341 + def toNode(self, node, name):
1.342 +
1.343 + """
1.344 + Set the trait on the given 'node' using the given 'name' according to
1.345 + this object's attributes.
1.346 + """
1.347 +
1.348 + try:
1.349 + values = map(str, [self.x, self.y, self.width, self.height])
1.350 + node.setAttribute(name, " ".join(values))
1.351 + except (IndexError, ValueError):
1.352 + raise xml.dom.DOMException(xml.dom.TYPE_MISMATCH_ERR)
1.353 +
1.354 class SVGRGBColor:
1.355
1.356 "A colour."
1.357 @@ -311,9 +591,57 @@
1.358 def __init__(self, red, green, blue):
1.359 self.red, self.green, self.blue = red, green, blue
1.360
1.361 +class TraitAccess:
1.362 +
1.363 + """
1.364 + Access to traits stored on elements.
1.365 + See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__TraitAccess
1.366 + """
1.367 +
1.368 + def getPathTrait(self, name):
1.369 + path = SVGPath()
1.370 + path.fromNode(self, name)
1.371 + return path
1.372 +
1.373 + def setPathTrait(self, name, path):
1.374 + path.toNode(self, name)
1.375 +
1.376 + def getRectTrait(self, name):
1.377 + rect = SVGRect()
1.378 + rect.fromNode(self, name)
1.379 + return rect
1.380 +
1.381 + def setRectTrait(self, name, rect):
1.382 + rect.toNode(self, name)
1.383 +
1.384 + def getMatrixTrait(self, name):
1.385 + matrix = SVGMatrix()
1.386 + matrix.fromNode(self, name)
1.387 + return matrix
1.388 +
1.389 + def setMatrixTrait(self, name, matrix):
1.390 + matrix.toNode(self, name)
1.391 +
1.392 # Node classes.
1.393
1.394 -class SVGDocument(libxml2dom.Document, DocumentEvent, EventTarget):
1.395 +class SVGNode(libxml2dom.Node):
1.396 +
1.397 + "Convenience modifications to nodes specific to libxml2dom.svg."
1.398 +
1.399 + def xpath(self, expr, variables=None, namespaces=None):
1.400 +
1.401 + """
1.402 + Evaluate the given 'expr' using the optional 'variables' and
1.403 + 'namespaces'. If not otherwise specified, the "svg" prefix will be bound
1.404 + to SVG_NAMESPACE as defined in this module.
1.405 + """
1.406 +
1.407 + namespaces = namespaces or {}
1.408 + if not namespaces.has_key("svg"):
1.409 + namespaces["svg"] = SVG_NAMESPACE
1.410 + return libxml2dom.Node.xpath(self, expr, variables, namespaces)
1.411 +
1.412 +class SVGDocument(libxml2dom._Document, SVGNode, DocumentEvent, EventTarget):
1.413
1.414 "An SVG-specific document node."
1.415
1.416 @@ -324,10 +652,10 @@
1.417 and global (SVGGlobal) details.
1.418 """
1.419
1.420 - libxml2dom.Document.__init__(self, node, impl)
1.421 + libxml2dom._Document.__init__(self, node, impl)
1.422 self.global_ = SVGGlobal(self) # parent
1.423
1.424 -class SVGElement(libxml2dom.Node, EventTarget): # (Element), TraitAccess, ElementTraversal
1.425 +class SVGElement(SVGNode, EventTarget, TraitAccess, ElementTraversal): # NOTE: SVGNode instead of Element.
1.426
1.427 "An SVG-specific element."
1.428
1.429 @@ -401,11 +729,7 @@
1.430 return self.translate
1.431
1.432 def _viewport(self):
1.433 - attr = self.getAttribute("viewBox")
1.434 - if attr is None:
1.435 - return attr
1.436 - l = map(int, attr.split())
1.437 - return SVGRect(*l)
1.438 + return self.getRectTrait("viewBox")
1.439
1.440 def getCurrentTime(self):
1.441 return self.document_time
1.442 @@ -417,7 +741,7 @@
1.443 return SVGMatrix(a, b, c, d, e, f)
1.444
1.445 def createSVGRect(self):
1.446 - return SVGRect(0, 0, 0, 0)
1.447 + return SVGRect()
1.448
1.449 def createSVGPath(self):
1.450 return SVGPath()
1.451 @@ -439,7 +763,13 @@
1.452 currentTranslate = property(_currentTranslate)
1.453 viewport = property(_viewport)
1.454
1.455 -# Convenience functions.
1.456 +# Utility functions.
1.457 +
1.458 +createDocument = libxml2dom.createDocument
1.459 +createDocumentType = libxml2dom.createDocumentType
1.460 +
1.461 +def createSVGDocument():
1.462 + return default_impl.createSVGDocument()
1.463
1.464 def parse(stream_or_string, html=0, htmlencoding=None):
1.465 return libxml2dom.parse(stream_or_string, html, htmlencoding, default_impl)