paulb@218 | 1 | #!/usr/bin/env python |
paulb@218 | 2 | |
paulb@218 | 3 | """ |
paulb@256 | 4 | SVG-specific document support. |
paulb@256 | 5 | See: http://www.w3.org/TR/SVGMobile12/python-binding.html |
paulb@256 | 6 | See: http://www.w3.org/TR/SVGMobile12/svgudom.html |
paulb@218 | 7 | |
paul@360 | 8 | Copyright (C) 2007, 2008, 2012 Paul Boddie <paul@boddie.org.uk> |
paulb@218 | 9 | |
paulb@293 | 10 | This program is free software; you can redistribute it and/or modify it under |
paulb@293 | 11 | the terms of the GNU Lesser General Public License as published by the Free |
paulb@293 | 12 | Software Foundation; either version 3 of the License, or (at your option) any |
paulb@293 | 13 | later version. |
paulb@218 | 14 | |
paulb@293 | 15 | This program is distributed in the hope that it will be useful, but WITHOUT |
paulb@293 | 16 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
paulb@293 | 17 | FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more |
paulb@293 | 18 | details. |
paulb@218 | 19 | |
paulb@293 | 20 | You should have received a copy of the GNU Lesser General Public License along |
paulb@293 | 21 | with this program. If not, see <http://www.gnu.org/licenses/>. |
paulb@218 | 22 | """ |
paulb@218 | 23 | |
paulb@218 | 24 | import libxml2dom |
paulb@256 | 25 | from libxml2dom.events import * |
paulb@218 | 26 | from libxml2dom.macrolib import * |
paulb@258 | 27 | from libxml2dom.macrolib import \ |
paulb@258 | 28 | createDocument as Node_createDocument |
paulb@256 | 29 | import xml.dom |
paulb@262 | 30 | import urllib |
paulb@256 | 31 | import math |
paulb@258 | 32 | import re |
paulb@218 | 33 | |
paulb@235 | 34 | SVG_NAMESPACE = "http://www.w3.org/2000/svg" |
paulb@235 | 35 | |
paul@360 | 36 | comma_wsp = re.compile("\s*,\s*|\s+") |
paul@360 | 37 | |
paul@360 | 38 | TYPE_MISMATCH_ERR = 17 |
paul@360 | 39 | |
paul@360 | 40 | class TypeMismatchErr(xml.dom.DOMException): |
paul@360 | 41 | code = TYPE_MISMATCH_ERR |
paul@360 | 42 | |
paulb@256 | 43 | class _Exception(Exception): |
paulb@256 | 44 | |
paulb@256 | 45 | "A generic SVG exception." |
paulb@256 | 46 | |
paulb@256 | 47 | def __init__(self, code): |
paulb@256 | 48 | Exception.__init__(self, code) |
paulb@256 | 49 | self.code = code |
paulb@256 | 50 | |
paulb@256 | 51 | class SVGException(_Exception): |
paulb@256 | 52 | |
paulb@256 | 53 | "An SVG exception." |
paulb@256 | 54 | |
paulb@256 | 55 | SVG_WRONG_TYPE_ERR = 0 |
paulb@256 | 56 | SVG_INVALID_VALUE_ERR = 1 |
paulb@256 | 57 | SVG_MATRIX_NOT_INVERTABLE = 2 |
paulb@256 | 58 | |
paulb@256 | 59 | class GlobalException(_Exception): |
paulb@256 | 60 | |
paulb@256 | 61 | "A global exception." |
paulb@256 | 62 | |
paulb@256 | 63 | NOT_CONNECTED_ERR = 1 |
paulb@256 | 64 | ENCODING_ERR = 2 |
paulb@256 | 65 | DENIED_ERR = 3 |
paulb@256 | 66 | UNKNOWN_ERR = 4 |
paulb@256 | 67 | |
paulb@218 | 68 | class SVGImplementation(libxml2dom.Implementation): |
paulb@218 | 69 | |
paulb@218 | 70 | "Contains an SVG-specific implementation." |
paulb@218 | 71 | |
paulb@235 | 72 | # Wrapping of documents. |
paulb@235 | 73 | |
paulb@235 | 74 | def adoptDocument(self, node): |
paulb@235 | 75 | return SVGDocument(node, self) |
paulb@235 | 76 | |
paulb@235 | 77 | # Factory functions. |
paulb@235 | 78 | |
paulb@218 | 79 | def get_node(self, _node, context_node): |
paulb@218 | 80 | if Node_nodeType(_node) == context_node.ELEMENT_NODE and \ |
paulb@235 | 81 | Node_namespaceURI(_node) == SVG_NAMESPACE: |
paulb@218 | 82 | |
paulb@235 | 83 | if Node_localName(_node) == "svg": |
paulb@235 | 84 | return SVGSVGElement(_node, self, context_node.ownerDocument) |
paulb@235 | 85 | else: |
paulb@235 | 86 | return SVGElement(_node, self, context_node.ownerDocument) |
paulb@218 | 87 | else: |
paulb@218 | 88 | return libxml2dom.Implementation.get_node(self, _node, context_node) |
paulb@218 | 89 | |
paulb@262 | 90 | def get_global(self, doc): |
paulb@262 | 91 | return SVGGlobal(doc) |
paulb@262 | 92 | |
paulb@258 | 93 | # Convenience functions. |
paulb@258 | 94 | |
paulb@258 | 95 | def createSVGDocument(self): |
paulb@258 | 96 | |
paulb@258 | 97 | "Create a new SVG document." |
paulb@258 | 98 | |
paulb@258 | 99 | return SVGDocument(Node_createDocument(SVG_NAMESPACE, "svg", None), self) |
paulb@258 | 100 | |
paulb@256 | 101 | # Interfaces and helper classes. |
paulb@256 | 102 | |
paulb@256 | 103 | class AsyncStatusCallback: |
paulb@256 | 104 | |
paulb@256 | 105 | "An asynchronous callback interface." |
paulb@256 | 106 | |
paulb@256 | 107 | def operationComplete(self, status): |
paulb@256 | 108 | pass |
paulb@256 | 109 | |
paulb@256 | 110 | class AsyncURLStatus: |
paulb@256 | 111 | |
paulb@256 | 112 | "The status of a URL retrieval operation." |
paulb@256 | 113 | |
paulb@256 | 114 | def __init__(self, success, contentType, content): |
paulb@256 | 115 | self.success, self.contentType, self.content = success, contentType, content |
paulb@256 | 116 | |
paulb@258 | 117 | class ElementTraversal: |
paulb@258 | 118 | |
paulb@258 | 119 | "An interface for element traversal." |
paulb@258 | 120 | |
paulb@258 | 121 | def _firstElementChild(self): |
paulb@258 | 122 | l = self.xpath("*") |
paulb@258 | 123 | if l: |
paulb@258 | 124 | return l[0] |
paulb@258 | 125 | else: |
paulb@258 | 126 | return None |
paulb@258 | 127 | |
paulb@258 | 128 | def _lastElementChild(self): |
paulb@258 | 129 | l = self.xpath("*") |
paulb@258 | 130 | if l: |
paulb@258 | 131 | return l[-1] |
paulb@258 | 132 | else: |
paulb@258 | 133 | return None |
paulb@258 | 134 | |
paulb@258 | 135 | def _nextElementSibling(self): |
paulb@258 | 136 | l = self.xpath("following-sibling::*") |
paulb@258 | 137 | if l: |
paulb@258 | 138 | return l[0] |
paulb@258 | 139 | else: |
paulb@258 | 140 | return None |
paulb@258 | 141 | |
paulb@258 | 142 | def _previousElementSibling(self): |
paulb@258 | 143 | l = self.xpath("preceding-sibling::*") |
paulb@258 | 144 | if l: |
paulb@258 | 145 | return l[0] |
paulb@258 | 146 | else: |
paulb@258 | 147 | return None |
paulb@258 | 148 | |
paulb@258 | 149 | firstElementChild = property(_firstElementChild) |
paulb@258 | 150 | lastElementChild = property(_lastElementChild) |
paulb@258 | 151 | nextElementSibling = property(_nextElementSibling) |
paulb@258 | 152 | previousElementSibling = property(_previousElementSibling) |
paulb@258 | 153 | |
paulb@262 | 154 | class EventListenerInitializer2: |
paulb@262 | 155 | |
paulb@262 | 156 | "An event listener initialisation interface." |
paulb@262 | 157 | |
paulb@262 | 158 | def initializeEventListeners(self, scriptElement): |
paulb@262 | 159 | pass |
paulb@262 | 160 | |
paulb@262 | 161 | def createEventListener(self, handlerElement): |
paulb@262 | 162 | pass |
paulb@262 | 163 | |
paulb@262 | 164 | class Global: |
paulb@262 | 165 | |
paulb@262 | 166 | "An empty global interface." |
paulb@262 | 167 | |
paulb@262 | 168 | pass |
paulb@262 | 169 | |
paulb@262 | 170 | class SVGGlobal(Global, EventListenerInitializer2): |
paulb@256 | 171 | |
paulb@256 | 172 | "An SVG global." |
paulb@256 | 173 | |
paulb@256 | 174 | def __init__(self, document): # parent |
paulb@256 | 175 | |
paulb@256 | 176 | "Initialise the global with the given 'document'." |
paulb@256 | 177 | |
paulb@256 | 178 | self.document = document |
paulb@256 | 179 | |
paulb@262 | 180 | # Listener management. |
paulb@262 | 181 | |
paulb@262 | 182 | self.listeners = {} |
paulb@262 | 183 | |
paulb@256 | 184 | def createConnection(self): |
paulb@256 | 185 | raise NotImplementedError, "createConnection" |
paulb@256 | 186 | |
paulb@256 | 187 | def createTimer(self, initialInterval, repeatInterval): |
paulb@256 | 188 | raise NotImplementedError, "createTimer" |
paulb@256 | 189 | |
paulb@256 | 190 | def gotoLocation(self, newIRI): |
paulb@256 | 191 | raise NotImplementedError, "gotoLocation" |
paulb@256 | 192 | |
paulb@256 | 193 | def binaryToString(self, octets, encoding): |
paulb@262 | 194 | try: |
paulb@262 | 195 | return unicode(octets, encoding) |
paulb@262 | 196 | except UnicodeDecodeError, exc: |
paulb@262 | 197 | raise GlobalException(GlobalException.ENCODING_ERR) |
paulb@256 | 198 | |
paulb@256 | 199 | def stringToBinary(self, data, encoding): |
paulb@262 | 200 | try: |
paulb@262 | 201 | return data.encode(encoding) |
paulb@262 | 202 | except UnicodeEncodeError, exc: |
paulb@262 | 203 | raise GlobalException(GlobalException.ENCODING_ERR) |
paulb@256 | 204 | |
paulb@256 | 205 | def getURL(self, iri, callback): |
paulb@256 | 206 | |
paulb@256 | 207 | # NOTE: Not asynchronous. |
paulb@256 | 208 | # NOTE: The urlopen function may not support IRIs. |
paulb@256 | 209 | # No exceptions are supposed to be raised, which is a bit nasty. |
paulb@256 | 210 | |
paulb@256 | 211 | f = urllib.urlopen(iri) |
paulb@256 | 212 | try: |
paulb@256 | 213 | try: |
paulb@256 | 214 | content = f.read() |
paulb@256 | 215 | contentType = f.headers["Content-Type"] |
paulb@256 | 216 | callback.operationComplete(AsyncURLStatus(1, contentType, content)) |
paulb@256 | 217 | except: |
paulb@256 | 218 | callback.operationComplete(AsyncURLStatus(0, None, None)) |
paulb@256 | 219 | finally: |
paulb@256 | 220 | f.close() |
paulb@256 | 221 | |
paulb@262 | 222 | def postURL(self, iri, data, callback, type=None, encoding=None): |
paulb@262 | 223 | |
paulb@262 | 224 | # NOTE: Not asynchronous. |
paulb@262 | 225 | # NOTE: The urlopen function may not support IRIs. |
paulb@262 | 226 | # No exceptions are supposed to be raised, which is a bit nasty. |
paulb@262 | 227 | |
paulb@262 | 228 | opener = urllib.URLopener() |
paulb@262 | 229 | opener.addheader("Content-Type", type or "text/plain") |
paulb@262 | 230 | if encoding: |
paulb@262 | 231 | opener.addheader("Content-Encoding", encoding) |
paulb@262 | 232 | f = opener.open(iri, data) |
paulb@262 | 233 | try: |
paulb@262 | 234 | try: |
paulb@262 | 235 | content = f.read() |
paulb@262 | 236 | contentType = f.headers["Content-Type"] |
paulb@262 | 237 | callback.operationComplete(AsyncURLStatus(1, contentType, content)) |
paulb@262 | 238 | except: |
paulb@262 | 239 | callback.operationComplete(AsyncURLStatus(0, None, None)) |
paulb@262 | 240 | finally: |
paulb@262 | 241 | f.close() |
paulb@262 | 242 | opener.close() |
paulb@256 | 243 | |
paulb@256 | 244 | def parseXML(self, data, contextDoc): |
paulb@256 | 245 | doc = parseString(data) |
paulb@256 | 246 | return contextDoc.importNode(doc.documentElement, 1) |
paulb@256 | 247 | |
paulb@256 | 248 | class SVGLocatable: |
paulb@256 | 249 | |
paulb@256 | 250 | "A locatable interface." |
paulb@256 | 251 | |
paulb@256 | 252 | pass |
paulb@256 | 253 | |
paulb@256 | 254 | class SVGMatrix: |
paulb@256 | 255 | |
paulb@256 | 256 | """ |
paulb@256 | 257 | A matrix. |
paulb@256 | 258 | See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGMatrix |
paulb@256 | 259 | """ |
paulb@256 | 260 | |
paul@360 | 261 | transform_regexp = re.compile( |
paul@360 | 262 | "(translate|scale|rotate|skewX|skewY|matrix)\((.*?)\)" |
paul@360 | 263 | ) |
paulb@258 | 264 | |
paulb@258 | 265 | def __init__(self, a=0, b=0, c=0, d=0, e=0, f=0): |
paulb@256 | 266 | self.matrix = a, b, c, d, e, f |
paulb@256 | 267 | |
paul@338 | 268 | def __repr__(self): |
paul@338 | 269 | return "SVGMatrix(%f, %f, %f, %f, %f, %f)" % self.matrix |
paul@338 | 270 | |
paulb@258 | 271 | def __eq__(self, other): |
paulb@258 | 272 | return self.matrix == other.matrix |
paulb@258 | 273 | |
paulb@258 | 274 | def __ne__(self, other): |
paulb@258 | 275 | return not (self == other) |
paulb@258 | 276 | |
paulb@258 | 277 | def _get_params(self, param_string): |
paul@360 | 278 | return [float(s.strip()) for s in comma_wsp.split(param_string.strip())] |
paulb@258 | 279 | |
paulb@258 | 280 | def fromNode(self, node, name): |
paulb@258 | 281 | |
paulb@258 | 282 | """ |
paulb@258 | 283 | Initialise this object from the trait on the 'node' having the given |
paulb@258 | 284 | 'name'. |
paulb@258 | 285 | """ |
paulb@258 | 286 | |
paulb@258 | 287 | value = node.getAttribute(name) |
paulb@258 | 288 | if value is None: |
paul@360 | 289 | raise xml.dom.NotSupportedErr() |
paul@360 | 290 | |
paul@360 | 291 | last = 0 |
paul@360 | 292 | |
paul@360 | 293 | matrix = 1, 0, 0, 1, 0, 0 |
paulb@258 | 294 | |
paul@360 | 295 | for m in self.transform_regexp.finditer(value): |
paul@360 | 296 | start, end = m.span() |
paulb@258 | 297 | |
paul@360 | 298 | # Make sure that nothing significant was found between the last |
paul@360 | 299 | # match and this one. |
paul@360 | 300 | |
paul@360 | 301 | if value[last:start].strip(): |
paul@360 | 302 | raise TypeMismatchErr() |
paulb@258 | 303 | |
paul@360 | 304 | last = end |
paul@360 | 305 | transform, arguments = m.groups() |
paulb@258 | 306 | |
paul@360 | 307 | # Translation. |
paulb@258 | 308 | |
paul@360 | 309 | if transform == "translate": |
paul@360 | 310 | a, b, c, d = 1, 0, 0, 1 |
paul@360 | 311 | e, f = self._get_params(arguments) |
paul@360 | 312 | |
paul@360 | 313 | # Scaling. |
paulb@258 | 314 | |
paul@360 | 315 | elif transform == "scale": |
paul@360 | 316 | b, c, e, f = 0, 0, 0, 0 |
paul@360 | 317 | a, d = self._get_params(arguments) |
paul@360 | 318 | |
paul@360 | 319 | # Rotation. |
paulb@258 | 320 | |
paul@360 | 321 | elif transform == "rotate": |
paul@360 | 322 | e, f = 0, 0 |
paul@360 | 323 | angle = float(arguments.strip()) |
paul@360 | 324 | a = d = math.cos(math.radians(angle)) |
paul@360 | 325 | b = math.sin(math.radians(angle)) |
paul@360 | 326 | c = -b |
paul@360 | 327 | |
paul@360 | 328 | # Skew. |
paulb@258 | 329 | |
paul@360 | 330 | elif transform == "skewX": |
paul@360 | 331 | a, b, d, e, f = 1, 0, 1, 0, 0 |
paul@360 | 332 | angle = float(arguments.strip()) |
paul@360 | 333 | c = math.tan(math.radians(angle)) |
paul@360 | 334 | |
paul@360 | 335 | elif transform == "skewY": |
paul@360 | 336 | a, c, d, e, f = 1, 0, 1, 0, 0 |
paul@360 | 337 | angle = float(arguments.strip()) |
paul@360 | 338 | b = math.tan(math.radians(angle)) |
paulb@258 | 339 | |
paul@360 | 340 | # Generic. |
paul@360 | 341 | |
paul@360 | 342 | elif transform == "matrix": |
paul@360 | 343 | a, b, c, d, e, f = self._get_params(arguments) |
paul@360 | 344 | |
paul@360 | 345 | else: |
paul@360 | 346 | raise TypeMismatchErr() |
paul@360 | 347 | |
paul@360 | 348 | # Combine the existing matrix with the new one. |
paulb@258 | 349 | |
paul@360 | 350 | matrix = self._multiply(matrix, (a, b, c, d, e, f)) |
paul@360 | 351 | |
paul@360 | 352 | else: |
paul@360 | 353 | # Make sure that nothing significant was found after the final |
paul@360 | 354 | # match. |
paulb@258 | 355 | |
paul@360 | 356 | if value[last:].strip(): |
paul@360 | 357 | raise TypeMismatchErr() |
paulb@258 | 358 | |
paul@360 | 359 | if last != 0: |
paul@360 | 360 | self.matrix = matrix |
paulb@258 | 361 | |
paulb@258 | 362 | def toNode(self, node, name): |
paulb@258 | 363 | |
paulb@258 | 364 | """ |
paulb@258 | 365 | Set the trait on the given 'node' using the given 'name' according to |
paulb@258 | 366 | this object's attributes. |
paulb@258 | 367 | """ |
paulb@258 | 368 | |
paulb@258 | 369 | a, b, c, d, e, f = self.matrix |
paulb@258 | 370 | |
paulb@258 | 371 | # Translation. |
paulb@258 | 372 | |
paulb@258 | 373 | if (a, b, c, d) == (1, 0, 0, 1): |
paulb@258 | 374 | node.setAttribute(name, "translate(%f, %f)" % (e, f)) |
paulb@258 | 375 | |
paulb@258 | 376 | # Scaling. |
paulb@258 | 377 | |
paulb@258 | 378 | elif (b, c, e, f) == (0, 0, 0, 0): |
paulb@258 | 379 | node.setAttribute(name, "scale(%f, %f)" % (a, d)) |
paulb@258 | 380 | |
paulb@258 | 381 | # Rotation. |
paulb@258 | 382 | |
paulb@258 | 383 | elif a == d and b == -c and (e, f) == (0, 0) and math.degrees(math.acos(a)) == math.degrees(math.asin(b)): |
paulb@258 | 384 | node.setAttribute(name, "rotate(%f)" % math.degrees(math.acos(a))) |
paulb@258 | 385 | |
paulb@258 | 386 | # Skew. |
paulb@258 | 387 | |
paulb@258 | 388 | elif (a, b, d, e, f) == (1, 0, 1, 0, 0) and c != 0: |
paulb@258 | 389 | node.setAttribute(name, "skewX(%f)" % math.degrees(math.atan(c))) |
paulb@258 | 390 | |
paulb@258 | 391 | elif (a, c, d, e, f) == (1, 0, 1, 0, 0) and b != 0: |
paulb@258 | 392 | node.setAttribute(name, "skewX(%f)" % math.degrees(math.atan(b))) |
paulb@258 | 393 | |
paulb@258 | 394 | # Generic matrix. |
paulb@258 | 395 | |
paulb@258 | 396 | else: |
paulb@258 | 397 | node.setAttribute(name, "matrix(%f, %f, %f, %f, %f, %f)" % (a, b, c, d, e, f)) |
paulb@258 | 398 | |
paulb@256 | 399 | def getComponent(self, index): |
paulb@256 | 400 | |
paulb@256 | 401 | """ |
paulb@256 | 402 | Return the component with the given 'index' (starting at zero) from the |
paulb@256 | 403 | sequence a, b, c, d, e, f where each element corresponds to the matrix |
paulb@256 | 404 | as follows: |
paulb@256 | 405 | |
paulb@256 | 406 | [ a c e ] |
paulb@256 | 407 | [ b d f ] |
paulb@256 | 408 | [ 0 0 1 ] |
paulb@256 | 409 | """ |
paulb@256 | 410 | |
paulb@256 | 411 | try: |
paulb@256 | 412 | return self.matrix[index] |
paulb@256 | 413 | except IndexError: |
paul@360 | 414 | raise xml.dom.IndexSizeErr() |
paul@360 | 415 | |
paul@360 | 416 | def _multiply(self, matrix1, matrix2): |
paul@360 | 417 | a1, b1, c1, d1, e1, f1 = matrix1 |
paul@360 | 418 | a2, b2, c2, d2, e2, f2 = matrix2 |
paul@360 | 419 | return a1*a2 + c1*b2, b1*a2 + d1*b2, a1*c2 + c1*d2, b1*c2 + d1*d2, a1*e2 + c1*f2 + e1, b1*e2 + d1*f2 + f1 |
paulb@256 | 420 | |
paulb@256 | 421 | def mMultiply(self, secondMatrix): |
paulb@256 | 422 | |
paulb@256 | 423 | """ |
paul@338 | 424 | Post-multiply this matrix with 'secondMatrix' and update its contents to |
paul@338 | 425 | the result of the multiplication operation defined as follows: |
paulb@256 | 426 | |
paul@338 | 427 | [ A C E ] [ a c e ] |
paul@338 | 428 | [ B D F ] [ b d f ] |
paulb@256 | 429 | [ 0 0 1 ] [ 0 0 1 ] |
paulb@256 | 430 | |
paulb@256 | 431 | Return this object as a result. |
paulb@256 | 432 | """ |
paulb@256 | 433 | |
paul@360 | 434 | self.matrix = self._multiply(secondMatrix.matrix, self.matrix) |
paulb@256 | 435 | return self |
paulb@256 | 436 | |
paulb@256 | 437 | def inverse(self): |
paulb@256 | 438 | |
paulb@256 | 439 | """ |
paulb@256 | 440 | det = ad - cb |
paulb@256 | 441 | |
paulb@256 | 442 | See (for example): http://mathworld.wolfram.com/MatrixInverse.html |
paulb@256 | 443 | """ |
paulb@256 | 444 | |
paulb@256 | 445 | det = a*d - c*b |
paulb@256 | 446 | if det != 0: |
paulb@256 | 447 | m = 1/det |
paulb@256 | 448 | a, b, c, d, e, f = self.matrix |
paulb@256 | 449 | self.matrix = m * d, m * -b, m * -c, m * a, m * (c*f - e*d), m * (e*b - a*f) |
paulb@256 | 450 | return self |
paulb@256 | 451 | else: |
paulb@256 | 452 | raise SVGException(SVGException.SVG_MATRIX_NOT_INVERTABLE) |
paulb@256 | 453 | |
paulb@256 | 454 | def mTranslate(self, x, y): |
paulb@256 | 455 | |
paulb@256 | 456 | """ |
paulb@256 | 457 | [ 1 0 x ] |
paulb@256 | 458 | [ 0 1 y ] |
paulb@256 | 459 | [ 0 0 1 ] |
paulb@256 | 460 | """ |
paulb@256 | 461 | |
paulb@256 | 462 | return self.mMultiply(SVGMatrix(1, 0, 0, 1, x, y)) |
paulb@256 | 463 | |
paulb@256 | 464 | def mScale(self, scaleFactor): |
paulb@256 | 465 | |
paulb@256 | 466 | """ |
paulb@256 | 467 | [ scaleFactor 0 0 ] |
paulb@256 | 468 | [ 0 scaleFactor 0 ] |
paulb@256 | 469 | [ 0 0 1 ] |
paulb@256 | 470 | """ |
paulb@256 | 471 | |
paulb@256 | 472 | return self.mMultiply(SVGMatrix(scaleFactor, 0, 0, scaleFactor, 0, 0)) |
paulb@256 | 473 | |
paulb@256 | 474 | def mRotate(self, angle): |
paulb@256 | 475 | |
paulb@256 | 476 | """ |
paulb@256 | 477 | [ cos(angle) -sin(angle) 0 ] |
paulb@256 | 478 | [ sin(angle) cos(angle) 0 ] |
paulb@256 | 479 | [ 0 0 1 ] |
paulb@256 | 480 | """ |
paulb@256 | 481 | |
paulb@258 | 482 | return self.mMultiply( |
paulb@258 | 483 | SVGMatrix( |
paulb@258 | 484 | math.cos(math.radians(angle)), |
paulb@258 | 485 | math.sin(math.radians(angle)), |
paulb@258 | 486 | -math.sin(math.radians(angle)), |
paulb@258 | 487 | math.cos(math.radians(angle)), |
paulb@258 | 488 | 0, 0 |
paulb@258 | 489 | ) |
paulb@258 | 490 | ) |
paulb@256 | 491 | |
paulb@256 | 492 | class SVGPath: |
paulb@256 | 493 | |
paulb@256 | 494 | """ |
paulb@256 | 495 | A path. |
paulb@256 | 496 | See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__SVGPath |
paulb@256 | 497 | See: http://www.w3.org/TR/SVGMobile12/paths.html |
paulb@256 | 498 | """ |
paulb@256 | 499 | |
paul@360 | 500 | path_regexp = re.compile("([mMzZlLhHvVcCsSqQtTaA])\s*([^mMzZlLhHvVcCsSqQtTaA]*)") |
paul@360 | 501 | |
paul@360 | 502 | # NOTE: Upper case involves absolute coordinates; lower case involves |
paul@360 | 503 | # NOTE: relative coordinates. The IDL constants only seem to represent the |
paul@360 | 504 | # NOTE: former commands. |
paul@360 | 505 | |
paul@360 | 506 | MOVE_TO = ord("M") |
paul@360 | 507 | LINE_TO = ord("L") |
paul@360 | 508 | CURVE_TO = ord("C") |
paul@360 | 509 | QUAD_TO = ord("Q") |
paul@360 | 510 | CLOSE = ord("Z") |
paulb@258 | 511 | |
paulb@258 | 512 | nparams = { |
paul@360 | 513 | "A" : 7, |
paul@360 | 514 | "C" : 6, |
paul@360 | 515 | "H" : 1, |
paul@360 | 516 | "L" : 2, |
paul@360 | 517 | "M" : 2, |
paul@360 | 518 | "Q" : 4, |
paul@360 | 519 | "S" : 4, |
paul@360 | 520 | "T" : 2, |
paul@360 | 521 | "V" : 1, |
paul@360 | 522 | "Z" : 0, |
paulb@258 | 523 | } |
paulb@256 | 524 | |
paulb@256 | 525 | def __init__(self): |
paulb@256 | 526 | self.segments = [] |
paulb@256 | 527 | |
paulb@258 | 528 | def __eq__(self, other): |
paulb@258 | 529 | return self.segments == other.segments |
paulb@258 | 530 | |
paulb@258 | 531 | def __ne__(self, other): |
paulb@258 | 532 | return not (self == other) |
paulb@258 | 533 | |
paulb@258 | 534 | def fromNode(self, node, name): |
paulb@258 | 535 | |
paulb@258 | 536 | """ |
paulb@258 | 537 | Initialise this object from the trait on the 'node' having the given |
paulb@258 | 538 | 'name'. |
paulb@258 | 539 | """ |
paulb@258 | 540 | |
paulb@258 | 541 | value = node.getAttribute(name) |
paulb@258 | 542 | if value is None: |
paul@360 | 543 | raise xml.dom.NotSupportedErr() |
paulb@258 | 544 | |
paulb@258 | 545 | # Try and unpack the attribute value. |
paul@360 | 546 | # NOTE: This does not yet satisfy the permissive parsing behaviour |
paul@360 | 547 | # NOTE: described in the specification. |
paul@360 | 548 | # See: http://www.w3.org/TR/SVG11/paths.html#PathDataBNF |
paulb@258 | 549 | |
paulb@258 | 550 | self.segments = [] |
paul@360 | 551 | last = 0 |
paul@360 | 552 | |
paul@360 | 553 | for m in self.path_regexp.finditer(value): |
paul@360 | 554 | start, end = m.span() |
paul@360 | 555 | if value[last:start].strip(): |
paul@360 | 556 | raise TypeMismatchErr() |
paul@360 | 557 | |
paul@360 | 558 | last = end |
paul@360 | 559 | cmd, arguments = m.groups() |
paul@360 | 560 | |
paul@360 | 561 | try: |
paul@360 | 562 | n = self.nparams[cmd.upper()] |
paul@360 | 563 | |
paul@360 | 564 | if arguments.strip(): |
paul@360 | 565 | params = [float(s.strip()) for s in comma_wsp.split(arguments.strip())] |
paul@360 | 566 | else: |
paul@360 | 567 | params = [] |
paul@360 | 568 | |
paul@360 | 569 | if n != 0 and len(params) % n != 0: |
paul@360 | 570 | raise TypeMismatchErr(cmd, arguments) |
paul@360 | 571 | |
paulb@258 | 572 | self.segments.append((cmd, params)) |
paul@360 | 573 | |
paul@360 | 574 | except (IndexError, ValueError): |
paul@360 | 575 | raise TypeMismatchErr(cmd, arguments) |
paul@360 | 576 | |
paul@360 | 577 | else: |
paul@360 | 578 | if value[last:start].strip(): |
paul@360 | 579 | raise TypeMismatchErr() |
paulb@258 | 580 | |
paulb@258 | 581 | def toNode(self, node, name): |
paulb@258 | 582 | |
paulb@258 | 583 | """ |
paulb@258 | 584 | Set the trait on the given 'node' using the given 'name' according to |
paulb@258 | 585 | this object's attributes. |
paulb@258 | 586 | """ |
paulb@258 | 587 | |
paulb@258 | 588 | try: |
paulb@258 | 589 | l = [] |
paulb@258 | 590 | for cmd, params in self.segments: |
paulb@258 | 591 | l.append(unichr(cmd)) |
paulb@258 | 592 | for param in params: |
paulb@258 | 593 | l.append(str(param)) |
paulb@258 | 594 | node.setAttribute(name, " ".join(l)) |
paulb@258 | 595 | except (IndexError, ValueError): |
paul@360 | 596 | raise TypeMismatchErr() |
paulb@258 | 597 | |
paulb@258 | 598 | # Interface methods. |
paulb@258 | 599 | |
paulb@256 | 600 | def _numberOfSegments(self): |
paulb@256 | 601 | return len(self.segments) |
paulb@256 | 602 | |
paulb@256 | 603 | numberOfSegments = property(_numberOfSegments) |
paulb@256 | 604 | |
paulb@256 | 605 | def getSegment(self, cmdIndex): |
paulb@256 | 606 | try: |
paul@360 | 607 | return ord(self.segments[cmdIndex][0]) |
paulb@256 | 608 | except IndexError: |
paul@360 | 609 | raise xml.dom.IndexSizeErr() |
paulb@256 | 610 | |
paulb@256 | 611 | def getSegmentParam(self, cmdIndex, paramIndex): |
paulb@256 | 612 | try: |
paulb@256 | 613 | return self.segments[cmdIndex][1][paramIndex] |
paulb@256 | 614 | except IndexError: |
paul@360 | 615 | raise xml.dom.IndexSizeErr() |
paulb@256 | 616 | |
paulb@256 | 617 | def moveTo(self, x, y): |
paul@360 | 618 | self.segments.append(("M", (x, y))) |
paulb@256 | 619 | |
paulb@256 | 620 | def lineTo(self, x, y): |
paul@360 | 621 | self.segments.append(("L", (x, y))) |
paulb@256 | 622 | |
paulb@256 | 623 | def quadTo(self, x1, y1, x2, y2): |
paul@360 | 624 | self.segments.append(("Q", (x1, y1, x2, y2))) |
paulb@256 | 625 | |
paulb@256 | 626 | def curveTo(self, x1, y1, x2, y2, x3, y3): |
paul@360 | 627 | self.segments.append(("C", (x1, y1, x2, y2, x3, y3))) |
paulb@256 | 628 | |
paulb@256 | 629 | def close(self): |
paul@360 | 630 | self.segments.append(("Z",)) |
paulb@256 | 631 | |
paulb@256 | 632 | class SVGPoint: |
paulb@256 | 633 | |
paulb@258 | 634 | "A point used to provide currentTranslate." |
paulb@256 | 635 | |
paulb@256 | 636 | def __init__(self, x, y): |
paulb@256 | 637 | self.x = x |
paulb@256 | 638 | self.y = y |
paulb@256 | 639 | |
paul@338 | 640 | def __repr__(self): |
paul@338 | 641 | return "SVGPoint(%f, %f)" % (self.x, self.y) |
paul@338 | 642 | |
paulb@256 | 643 | class SVGRect: |
paulb@256 | 644 | |
paulb@256 | 645 | "A rectangle." |
paulb@256 | 646 | |
paulb@258 | 647 | def __init__(self, x=0, y=0, width=0, height=0): |
paulb@256 | 648 | self.x, self.y, self.width, self.height = x, y, width, height |
paulb@256 | 649 | |
paulb@258 | 650 | def __eq__(self, other): |
paulb@258 | 651 | return (self.x, self.y, self.width, self.height) == (other.x, other.y, other.width, other.height) |
paulb@258 | 652 | |
paulb@258 | 653 | def __ne__(self, other): |
paulb@258 | 654 | return not (self == other) |
paulb@258 | 655 | |
paulb@258 | 656 | def fromNode(self, node, name): |
paulb@258 | 657 | |
paulb@258 | 658 | """ |
paulb@258 | 659 | Initialise this object from the trait on the 'node' having the given |
paulb@258 | 660 | 'name'. |
paulb@258 | 661 | """ |
paulb@258 | 662 | |
paulb@258 | 663 | value = node.getAttribute(name) |
paulb@258 | 664 | if value is None: |
paul@360 | 665 | raise xml.dom.NotSupportedErr() |
paulb@258 | 666 | try: |
paulb@258 | 667 | values = map(float, value.split()) |
paulb@258 | 668 | self.x, self.y, self.width, self.height = values |
paulb@258 | 669 | except (IndexError, ValueError): |
paul@360 | 670 | raise TypeMismatchErr() |
paulb@258 | 671 | |
paulb@258 | 672 | def toNode(self, node, name): |
paulb@258 | 673 | |
paulb@258 | 674 | """ |
paulb@258 | 675 | Set the trait on the given 'node' using the given 'name' according to |
paulb@258 | 676 | this object's attributes. |
paulb@258 | 677 | """ |
paulb@258 | 678 | |
paulb@258 | 679 | try: |
paulb@258 | 680 | values = map(str, [self.x, self.y, self.width, self.height]) |
paulb@258 | 681 | node.setAttribute(name, " ".join(values)) |
paulb@258 | 682 | except (IndexError, ValueError): |
paul@360 | 683 | raise TypeMismatchErr() |
paulb@258 | 684 | |
paul@338 | 685 | def __repr__(self): |
paul@338 | 686 | return "SVGRect(%f, %f, %f, %f)" % (self.x, self.y, self.width, self.height) |
paul@338 | 687 | |
paulb@256 | 688 | class SVGRGBColor: |
paulb@256 | 689 | |
paul@338 | 690 | """ |
paul@338 | 691 | A colour. |
paul@338 | 692 | See: http://www.w3.org/TR/SVGMobile12/painting.html#colorSyntax |
paul@338 | 693 | """ |
paulb@256 | 694 | |
paulb@256 | 695 | def __init__(self, red, green, blue): |
paulb@256 | 696 | self.red, self.green, self.blue = red, green, blue |
paulb@256 | 697 | |
paul@338 | 698 | def __repr__(self): |
paul@338 | 699 | return "SVGRGBColor(%f, %f, %f)" % (self.red, self.green, self.blue) |
paul@338 | 700 | |
paulb@258 | 701 | class TraitAccess: |
paulb@258 | 702 | |
paulb@258 | 703 | """ |
paulb@258 | 704 | Access to traits stored on elements. |
paulb@258 | 705 | See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__TraitAccess |
paulb@258 | 706 | """ |
paulb@258 | 707 | |
paulb@258 | 708 | def getPathTrait(self, name): |
paulb@258 | 709 | path = SVGPath() |
paulb@258 | 710 | path.fromNode(self, name) |
paulb@258 | 711 | return path |
paulb@258 | 712 | |
paulb@258 | 713 | def setPathTrait(self, name, path): |
paulb@258 | 714 | path.toNode(self, name) |
paulb@258 | 715 | |
paulb@258 | 716 | def getRectTrait(self, name): |
paulb@258 | 717 | rect = SVGRect() |
paulb@258 | 718 | rect.fromNode(self, name) |
paulb@258 | 719 | return rect |
paulb@258 | 720 | |
paulb@258 | 721 | def setRectTrait(self, name, rect): |
paulb@258 | 722 | rect.toNode(self, name) |
paulb@258 | 723 | |
paulb@258 | 724 | def getMatrixTrait(self, name): |
paul@338 | 725 | if name != "transform": |
paul@360 | 726 | raise xml.dom.NotSupportedErr() |
paulb@258 | 727 | matrix = SVGMatrix() |
paulb@258 | 728 | matrix.fromNode(self, name) |
paulb@258 | 729 | return matrix |
paulb@258 | 730 | |
paulb@258 | 731 | def setMatrixTrait(self, name, matrix): |
paul@338 | 732 | if name != "transform": |
paul@360 | 733 | raise xml.dom.NotSupportedErr() |
paulb@258 | 734 | matrix.toNode(self, name) |
paulb@258 | 735 | |
paulb@256 | 736 | # Node classes. |
paulb@256 | 737 | |
paulb@258 | 738 | class SVGNode(libxml2dom.Node): |
paulb@258 | 739 | |
paulb@258 | 740 | "Convenience modifications to nodes specific to libxml2dom.svg." |
paulb@258 | 741 | |
paulb@258 | 742 | def xpath(self, expr, variables=None, namespaces=None): |
paulb@258 | 743 | |
paulb@258 | 744 | """ |
paulb@258 | 745 | Evaluate the given 'expr' using the optional 'variables' and |
paulb@258 | 746 | 'namespaces'. If not otherwise specified, the "svg" prefix will be bound |
paulb@258 | 747 | to SVG_NAMESPACE as defined in this module. |
paulb@258 | 748 | """ |
paulb@258 | 749 | |
paulb@258 | 750 | namespaces = namespaces or {} |
paulb@258 | 751 | if not namespaces.has_key("svg"): |
paulb@258 | 752 | namespaces["svg"] = SVG_NAMESPACE |
paulb@258 | 753 | return libxml2dom.Node.xpath(self, expr, variables, namespaces) |
paulb@258 | 754 | |
paulb@265 | 755 | # NOTE: DocumentEvent is from DOM Level 3 Events. |
paulb@265 | 756 | # NOTE: EventSystem is a special libxml2dom.events class. |
paulb@265 | 757 | |
paulb@265 | 758 | class SVGDocument(libxml2dom._Document, SVGNode, EventTarget, DocumentEvent, EventSystem): |
paulb@256 | 759 | |
paulb@256 | 760 | "An SVG-specific document node." |
paulb@256 | 761 | |
paulb@256 | 762 | def __init__(self, node, impl): |
paulb@256 | 763 | |
paulb@256 | 764 | """ |
paulb@256 | 765 | Initialise the document with the given 'node', implementation 'impl', |
paulb@256 | 766 | and global (SVGGlobal) details. |
paulb@256 | 767 | """ |
paulb@256 | 768 | |
paulb@258 | 769 | libxml2dom._Document.__init__(self, node, impl) |
paulb@262 | 770 | self.global_ = self.impl.get_global(self) # parent |
paulb@256 | 771 | |
paulb@258 | 772 | class SVGElement(SVGNode, EventTarget, TraitAccess, ElementTraversal): # NOTE: SVGNode instead of Element. |
paulb@235 | 773 | |
paulb@235 | 774 | "An SVG-specific element." |
paulb@235 | 775 | |
paulb@262 | 776 | def __init__(self, *args, **kw): |
paulb@262 | 777 | SVGNode.__init__(self, *args, **kw) |
paulb@262 | 778 | |
paulb@235 | 779 | def _id(self): |
paulb@235 | 780 | return self.getAttribute("id") |
paulb@235 | 781 | |
paulb@235 | 782 | def _setId(self, value): |
paulb@235 | 783 | self.setAttribute("id", value) |
paulb@235 | 784 | |
paulb@235 | 785 | id = property(_id, _setId) |
paulb@235 | 786 | |
paulb@235 | 787 | class SVGLocatableElement(SVGElement, SVGLocatable): |
paulb@235 | 788 | |
paulb@235 | 789 | "A locatable element." |
paulb@235 | 790 | |
paulb@256 | 791 | pass |
paulb@256 | 792 | |
paulb@256 | 793 | class SVGTimedElement(SVGElement): # smil::ElementTimeControl |
paulb@256 | 794 | |
paulb@256 | 795 | "A timed element." |
paulb@256 | 796 | |
paulb@256 | 797 | def __init__(self, *args): |
paulb@256 | 798 | |
paulb@256 | 799 | "Initialise the element with the underlying 'args'." |
paulb@256 | 800 | |
paulb@256 | 801 | SVGElement.__init__(self, *args) |
paulb@256 | 802 | self.document_time = 0 |
paulb@256 | 803 | self.paused = 0 |
paulb@256 | 804 | |
paulb@256 | 805 | def _isPaused(self): |
paulb@256 | 806 | return self.paused |
paulb@256 | 807 | |
paulb@256 | 808 | def pauseElement(self): |
paulb@256 | 809 | self.paused = 1 |
paulb@256 | 810 | |
paulb@256 | 811 | def resumeElement(self): |
paulb@256 | 812 | self.paused = 0 |
paulb@256 | 813 | |
paulb@256 | 814 | class SVGSVGElement(SVGLocatableElement, SVGTimedElement): |
paulb@218 | 815 | |
paulb@218 | 816 | "An SVG-specific top-level element." |
paulb@218 | 817 | |
paulb@218 | 818 | NAV_AUTO = 1 |
paulb@218 | 819 | NAV_NEXT = 2 |
paulb@218 | 820 | NAV_PREV = 3 |
paulb@218 | 821 | NAV_UP = 4 |
paulb@218 | 822 | NAV_UP_RIGHT = 5 |
paulb@218 | 823 | NAV_RIGHT = 6 |
paulb@218 | 824 | NAV_DOWN_RIGHT = 7 |
paulb@218 | 825 | NAV_DOWN = 8 |
paulb@218 | 826 | NAV_DOWN_LEFT = 9 |
paulb@218 | 827 | NAV_LEFT = 10 |
paulb@218 | 828 | NAV_UP_LEFT = 11 |
paulb@218 | 829 | |
paulb@256 | 830 | def __init__(self, *args): |
paulb@256 | 831 | |
paulb@256 | 832 | "Initialise the element with the underlying 'args'." |
paulb@256 | 833 | |
paulb@256 | 834 | SVGTimedElement.__init__(self, *args) |
paulb@256 | 835 | self.scale = 1 |
paulb@256 | 836 | self.rotate = 0 |
paulb@256 | 837 | self.translate = SVGPoint(0, 0) |
paulb@256 | 838 | |
paul@338 | 839 | # NOTE: The scale, rotate and translate properties are not persistent, and |
paul@338 | 840 | # NOTE: are specific to individual objects. |
paul@338 | 841 | |
paulb@256 | 842 | def _currentScale(self): |
paulb@256 | 843 | return self.scale |
paulb@256 | 844 | |
paulb@256 | 845 | def _currentRotate(self): |
paulb@256 | 846 | return self.rotate |
paulb@256 | 847 | |
paulb@256 | 848 | def _currentTranslate(self): |
paulb@256 | 849 | return self.translate |
paulb@256 | 850 | |
paul@338 | 851 | def _setCurrentScale(self, scale): |
paul@338 | 852 | if scale == 0: |
paul@360 | 853 | raise xml.dom.InvalidAccessErr() |
paul@338 | 854 | self.scale = scale |
paul@338 | 855 | |
paul@338 | 856 | def _setCurrentRotate(self, rotate): |
paul@338 | 857 | self.rotate = rotate |
paul@338 | 858 | |
paulb@256 | 859 | def _viewport(self): |
paul@338 | 860 | if self.hasAttribute("viewBox"): |
paul@338 | 861 | return self.getRectTrait("viewBox") |
paul@360 | 862 | elif self.hasAttribute("width") and self.hasAttribute("height"): |
paul@338 | 863 | return SVGRect(0, 0, self._convertMeasurement(self.getAttribute("width")), |
paul@338 | 864 | self._convertMeasurement(self.getAttribute("height"))) |
paul@360 | 865 | else: |
paul@360 | 866 | return None |
paul@338 | 867 | |
paul@338 | 868 | # Utility methods. |
paul@338 | 869 | |
paul@338 | 870 | units = ["in", "cm", "mm", "pt", "pc", "px", "%"] |
paul@338 | 871 | |
paul@338 | 872 | def _convertMeasurement(self, value): |
paul@338 | 873 | value = value.strip() |
paul@338 | 874 | for unit in self.units: |
paul@338 | 875 | if value.endswith(unit): |
paul@338 | 876 | # NOTE: No conversion yet! |
paul@338 | 877 | return float(value[:-len(unit)].strip()) |
paul@338 | 878 | |
paul@360 | 879 | raise TypeMismatchErr() |
paul@338 | 880 | |
paul@338 | 881 | # Standard methods. |
paulb@256 | 882 | |
paulb@256 | 883 | def getCurrentTime(self): |
paulb@256 | 884 | return self.document_time |
paulb@235 | 885 | |
paulb@256 | 886 | def setCurrentTime(self, setCurrentTime): |
paulb@256 | 887 | self.document_time = setCurrentTime |
paulb@256 | 888 | |
paulb@256 | 889 | def createSVGMatrixComponents(self, a, b, c, d, e, f): |
paulb@256 | 890 | return SVGMatrix(a, b, c, d, e, f) |
paulb@256 | 891 | |
paulb@256 | 892 | def createSVGRect(self): |
paulb@258 | 893 | return SVGRect() |
paulb@256 | 894 | |
paulb@256 | 895 | def createSVGPath(self): |
paulb@256 | 896 | return SVGPath() |
paulb@235 | 897 | |
paulb@256 | 898 | def createSVGRGBColor(self, red, green, blue): |
paulb@256 | 899 | return SVGRGBColor(red, green, blue) |
paulb@256 | 900 | |
paulb@256 | 901 | def moveFocus(self, motionType): |
paulb@256 | 902 | raise NotImplementedError, "moveFocus" |
paulb@256 | 903 | |
paulb@256 | 904 | def setFocus(self, object): |
paulb@256 | 905 | raise NotImplementedError, "setFocus" |
paulb@256 | 906 | |
paulb@256 | 907 | def getCurrentFocusedObject(self): |
paulb@256 | 908 | raise NotImplementedError, "getCurrentFocusedObject" |
paulb@256 | 909 | |
paul@338 | 910 | currentScale = property(_currentScale, _setCurrentScale) |
paul@338 | 911 | currentRotate = property(_currentRotate, _setCurrentRotate) |
paulb@256 | 912 | currentTranslate = property(_currentTranslate) |
paulb@256 | 913 | viewport = property(_viewport) |
paulb@235 | 914 | |
paulb@262 | 915 | # Event handler initialisation. |
paulb@262 | 916 | |
paulb@262 | 917 | def initialiseEvents(doc): |
paulb@262 | 918 | |
paulb@262 | 919 | """ |
paulb@262 | 920 | See: http://www.w3.org/TR/SVGMobile12/svgudom.html#svg__EventListenerInitializer2 |
paulb@265 | 921 | See: http://www.w3.org/TR/xml-events/#section-listener-element |
paulb@262 | 922 | """ |
paulb@262 | 923 | |
paulb@265 | 924 | # Initialise script element listeners. |
paulb@265 | 925 | |
paulb@262 | 926 | for script in doc.xpath("//svg:script"): |
paulb@262 | 927 | doc.global_.initializeEventListeners(script) |
paulb@265 | 928 | |
paulb@265 | 929 | # Initialise handler element listeners using XML Events. |
paulb@265 | 930 | |
paulb@262 | 931 | for handler in doc.xpath("//svg:handler"): |
paulb@262 | 932 | listener = doc.global_.createEventListener(handler) |
paulb@262 | 933 | |
paulb@265 | 934 | # Attempt to parameterise the registration using the XML Events attributes. |
paulb@265 | 935 | |
paulb@265 | 936 | phase = handler.getAttributeNS(libxml2dom.events.XML_EVENTS_NAMESPACE, "phase") |
paulb@265 | 937 | |
paulb@265 | 938 | # Add the listener for the appropriate type and phases. |
paulb@262 | 939 | |
paulb@262 | 940 | handler.parentNode.addEventListener( |
paulb@262 | 941 | handler.getAttributeNS(libxml2dom.events.XML_EVENTS_NAMESPACE, "event"), |
paulb@262 | 942 | listener, |
paulb@265 | 943 | phase == "capture" |
paulb@262 | 944 | ) |
paulb@262 | 945 | |
paulb@258 | 946 | # Utility functions. |
paulb@258 | 947 | |
paulb@258 | 948 | createDocument = libxml2dom.createDocument |
paulb@258 | 949 | createDocumentType = libxml2dom.createDocumentType |
paulb@258 | 950 | |
paulb@258 | 951 | def createSVGDocument(): |
paulb@258 | 952 | return default_impl.createSVGDocument() |
paulb@235 | 953 | |
paulb@268 | 954 | def parse(stream_or_string, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@268 | 955 | doc = libxml2dom.parse(stream_or_string, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@262 | 956 | initialiseEvents(doc) |
paulb@262 | 957 | return doc |
paulb@218 | 958 | |
paulb@268 | 959 | def parseFile(filename, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@268 | 960 | doc = libxml2dom.parseFile(filename, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@262 | 961 | initialiseEvents(doc) |
paulb@262 | 962 | return doc |
paulb@218 | 963 | |
paulb@268 | 964 | def parseString(s, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@268 | 965 | doc = libxml2dom.parseString(s, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@262 | 966 | initialiseEvents(doc) |
paulb@262 | 967 | return doc |
paulb@218 | 968 | |
paulb@268 | 969 | def parseURI(uri, html=0, htmlencoding=None, unfinished=0, impl=None): |
paulb@268 | 970 | doc = libxml2dom.parseURI(uri, html=html, htmlencoding=htmlencoding, unfinished=unfinished, impl=(impl or default_impl)) |
paulb@262 | 971 | initialiseEvents(doc) |
paulb@262 | 972 | return doc |
paulb@218 | 973 | |
paulb@218 | 974 | # Single instance of the implementation. |
paulb@218 | 975 | |
paulb@218 | 976 | default_impl = SVGImplementation() |
paulb@218 | 977 | |
paulb@218 | 978 | # vim: tabstop=4 expandtab shiftwidth=4 |