Reset platonic code.
[voro++.git] / branches / 2d_boundary / Tests / svgfig / svg.py
blobc865496dcd3e3113297434d2f19a86465cf4e18b
1 import math, cmath, random, re, os, sys, copy, itertools, codecs, tempfile, new, types, copy_reg, warnings
2 import defaults
4 saved = [] # keep track of all fileNames saved for the user's convenience
6 ############################### convenient functions for dealing with SVG
8 def randomid(prefix="", characters=10):
9 return prefix + "".join(random.sample("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", characters))
11 # This rgb function could be a lot better... something to think about...
12 def rgb(r, g, b, maximum=1.):
13 return "#%02x%02x%02x" % (max(0, min(r*255./maximum, 255)), max(0, min(g*255./maximum, 255)), max(0, min(b*255./maximum, 255)))
15 ############################### class SVG
17 def shortcut(tag): return eval("lambda *args, **kwds: SVG(\"%s\", *args, **kwds)" % tag)
19 class SVG:
20 def _preprocess_attribname(self, name):
21 name_colon = re.sub("__", ":", name)
22 if name_colon != name: name = name_colon
24 name_dash = re.sub("_", "-", name)
25 if name_dash != name: name = name_dash
27 return name
29 def __init__(self, tag, *signature_attrib, **more_attrib):
30 self.__dict__["tag"] = tag
31 self.__dict__["attrib"] = dict(getattr(defaults, "defaults_%s" % tag, {}))
32 self.__dict__["children"] = []
33 self.__dict__["_svg"] = self
35 signature = getattr(defaults, "signature_%s" % tag, None)
37 # if there is no signature, inline arguments are interpreted as children
38 if signature is None:
39 self.children.extend(signature_attrib)
41 else:
42 if len(signature_attrib) > len(signature):
43 raise TypeError, "Tag '%s' expects no more than %d signature attributes (saw %d)" % (tag, len(signature), len(signature_attrib))
45 for name, value in zip(signature, signature_attrib):
46 self.attrib[name] = value
48 # preprocess more_attrib names
49 for name, value in more_attrib.items():
50 processed_name = self._preprocess_attribname(name)
51 if processed_name != name:
52 del more_attrib[name]
53 more_attrib[processed_name] = value
55 self.attrib.update(more_attrib)
57 require = getattr(defaults, "require_%s" % tag, None)
58 if require is not None:
59 for name in require:
60 if name not in self.attrib:
61 raise TypeError, "Tag '%s' requires a '%s' attribute" % (tag, name)
63 ### construct trees inline
64 def __call__(self, *children):
65 self.children.extend(children)
66 return self
68 ### recursively tonumber, transform, bbox, and svg
69 def tonumber(self):
70 if self.tag is not None:
71 tonumber_tag = getattr(defaults, "tonumber_%s" % self.tag, None)
72 if tonumber_tag is not None: tonumber_tag(self)
74 for child in self.children:
75 if isinstance(child, SVG): child.tonumber()
77 def transform(self, t):
78 t = cannonical_transformation(t)
80 if self.tag is not None:
81 tonumber_tag = getattr(defaults, "tonumber_%s" % self.tag, None)
82 if tonumber_tag is not None: tonumber_tag(self)
84 transform_tag = getattr(defaults, "transform_%s" % self.tag, None)
85 if transform_tag is not None: transform_tag(t, self)
87 for child in self.children:
88 if isinstance(child, SVG): child.transform(t)
90 def bbox(self):
91 if self.tag is not None:
92 tonumber_tag = getattr(defaults, "tonumber_%s" % self.tag, None)
93 if tonumber_tag is not None: tonumber_tag(self)
95 bbox_tag = getattr(defaults, "bbox_%s" % self.tag, None)
96 if bbox_tag is not None:
97 output = bbox_tag(self)
98 else:
99 output = defaults.BBox(None, None, None, None)
101 for child in self.children:
102 if isinstance(child, SVG): output += child.bbox()
103 return output
105 def svg(self): self._svg = self
107 ### signature attributes are accessible as member data
108 def __getattr__(self, name):
109 if self.__dict__["tag"] is None: return self.__dict__[name]
111 signature = getattr(defaults, "signature_%s" % self.__dict__["tag"], None)
112 if signature is not None and name in signature:
113 return self.attrib[name]
114 else:
115 raise AttributeError, "Tag '%s' has no signature attrib '%s'" % (self.tag, name)
117 def __setattr__(self, name, value):
118 if self.__dict__["tag"] is None or name == "repr" or name in self.__dict__:
119 self.__dict__[name] = value
121 else:
122 signature = getattr(defaults, "signature_%s" % self.__dict__["tag"], None)
123 if signature is not None and name in signature:
124 self.attrib[name] = value
125 else:
126 raise AttributeError, "Tag '%s' has no signature attrib '%s'" % (self.tag, name)
128 def __nonzero__(self): return True
130 ### support access to deep children with tree indexes
131 def _treeindex_descend(self, obj, treeindex):
132 if isinstance(treeindex, (list, tuple)):
133 for i in treeindex[:-1]:
134 obj = obj[i]
135 treeindex = treeindex[-1]
136 return treeindex, obj
138 def __getitem__(self, treeindex):
139 treeindex, obj = self._treeindex_descend(self, treeindex)
141 if isinstance(treeindex, (int, long, slice)): return obj.children[treeindex]
142 elif isinstance(treeindex, basestring): return obj.attrib[treeindex]
143 else:
144 raise IndexError, "treeindex must be [#, #, ... #] or [#, #, ... \"str\"]"
146 def __setitem__(self, treeindex, value):
147 treeindex, obj = self._treeindex_descend(self, treeindex)
149 if isinstance(treeindex, (int, long, slice)): obj.children[treeindex] = value
150 elif isinstance(treeindex, basestring): obj.attrib[treeindex] = value
151 else:
152 raise IndexError, "treeindex must be [#, #, ... #] or [#, #, ... \"str\"]"
154 def __delitem__(self, treeindex):
155 treeindex, obj = self._treeindex_descend(self, treeindex)
157 if isinstance(treeindex, (int, long, slice)): del obj.children[treeindex]
158 elif isinstance(treeindex, basestring): del obj.attrib[treeindex]
159 else:
160 raise IndexError, "treeindex must be [#, #, ... #] or [#, #, ... \"str\"]"
162 ################ nested class for walking the tree
163 class _SVGDepthIterator:
164 def __init__(self, svg, treeindex, depth_limit, attrib, attrib_first):
165 self.current = svg
166 self.treeindex = treeindex
167 self.shown = False
168 self.depth_limit = depth_limit
169 self.attrib = attrib
170 self.attrib_first = attrib_first
172 def __iter__(self): return self
174 def make_children_iterators(self):
175 if getattr(self.current, "children", None) is not None:
176 for i, s in enumerate(self.current.children):
177 self.iterators.append(self.__class__(s, self.treeindex + (i,), self.depth_limit, self.attrib, self.attrib_first))
179 def make_attrib_iterators(self):
180 if getattr(self.current, "attrib", None) is not None:
181 items = self.current.attrib.items()
182 items.sort()
183 for k, s in items:
184 self.iterators.append(self.__class__(s, self.treeindex + (k,), self.depth_limit, self.attrib, self.attrib_first))
186 def next(self):
187 if not self.shown:
188 self.shown = True
189 if self.treeindex != ():
190 return self.treeindex, self.current
192 if self.depth_limit is not None and len(self.treeindex) >= self.depth_limit: raise StopIteration
194 if "iterators" not in self.__dict__:
195 self.iterators = []
197 if self.attrib and self.attrib_first: self.make_attrib_iterators()
198 self.make_children_iterators()
199 if self.attrib and not self.attrib_first: self.make_attrib_iterators()
201 self.iterators = itertools.chain(*self.iterators)
203 return self.iterators.next()
204 ################ end nested class
206 ### walk the tree or show it (uses the nested class)
207 def walk(self, depth_limit=None, attrib=False, attrib_first=False):
208 return self._SVGDepthIterator(self, (), depth_limit, attrib, attrib_first)
210 def tree(self, depth_limit=None, attrib=False, attrib_first=False, index_width=20, showtop=True, asstring=False):
211 if showtop:
212 output = [("%s %s" % (("%%-%ds" % index_width) % repr(None), repr(self)))]
213 else:
214 output = []
216 for treeindex, element in self.walk(depth_limit, attrib, attrib_first):
217 if isinstance(element, basestring):
218 if len(element) > 13:
219 repr_element = "'%s...'" % element[0:10]
220 else:
221 repr_element = "'%s'" % element
222 else:
223 repr_element = repr(element)
225 output.append(("%s %s%s" % (("%%-%ds" % index_width) % repr(list(treeindex)), ". . " * len(treeindex), repr_element)))
227 if asstring: return "\n".join(output)
228 else: print "\n".join(output)
230 ### how to present SVG objects on the commandline (used in tree)
231 def __repr__(self):
232 if "repr" in self.__dict__: return self.repr
234 output = ["%s" % self.tag]
236 remaining = copy.copy(self.attrib) # shallow copy
238 value = remaining.pop("id", None)
239 if value is not None:
240 output.append("id='%s'" % value)
242 # special handling of a text child: print it out and truncate if it's too long
243 if self.tag in ("text", "tspan") and len(self.children) == 1 and isinstance(self.children[0], basestring):
244 value = re.sub("\n", "\\\\n", self.children[0])
245 if len(value) > 13:
246 repr_value = "'%s...'" % value[0:10]
247 else:
248 repr_value = "'%s'" % value
249 output.append(repr_value)
251 signature = getattr(defaults, "signature_%s" % self.tag, None)
252 if signature is not None:
253 for name in signature:
254 try:
255 value = remaining.pop(name)
257 # special handling of path data: truncate it if it's too long
258 if name == "d":
259 if isinstance(value, basestring):
260 value = re.sub("\n", "\\\\n", value)
261 if len(value) > 13:
262 repr_value = "'%s...'" % value[0:10]
263 else:
264 repr_value = "'%s'" % value
266 elif isinstance(value, (list, tuple)):
267 if len(value) > 3:
268 repr_value = repr(value[0:3])
269 repr_value = "%s, ...%s" % (repr_value[0:-1], repr_value[-1])
270 else:
271 repr_value = repr(value)
273 else:
274 repr_value = repr(value)
276 # special handling of floats: use __str__ instead of __repr__
277 elif isinstance(value, float):
278 repr_value = "%s" % str(value)
280 # normal handling
281 else:
282 repr_value = repr(value)
284 output.append("%s=%s" % (name, repr_value))
286 # if the signature item is not present, don't print it
287 except KeyError:
288 pass
290 lenchildren = len(self.children)
291 if lenchildren == 1:
292 # special handling of a text child: already printed
293 if self.tag in ("text", "tspan") and isinstance(self.children[0], basestring):
294 pass
295 else:
296 output.append("(1 child)")
298 elif lenchildren > 1:
299 output.append("(%d children)" % lenchildren)
301 lenremaining = len(remaining)
302 if lenremaining == 1:
303 output.append("(1 other attribute)")
304 elif lenremaining > 1:
305 output.append("(%d other attributes)" % lenremaining)
307 return "<%s>" % " ".join(output)
309 ### convert to XML, view, and save
310 def xml(self, indent=u" ", newl=u"\n"):
311 # need a parent node
312 if self.tag == "svg" or "_tag" in self.__dict__ and self._tag == "svg":
313 svg = self
314 else:
315 svg = SVG("svg")(self)
317 output = [defaults.xml_header] + svg_to_xml(svg, indent) + [u""]
318 return unicode(newl.join(output))
320 def view(self): # no writing-to-disk needed!
321 import _viewer
322 _viewer.str(self.xml())
324 def save(self, fileName, encoding="utf-8", compresslevel=None):
325 fileName = defaults.expand_fileName(fileName)
327 if compresslevel is not None or re.search("\.svgz$", fileName, re.I) or re.search("\.gz$", fileName, re.I):
328 import gzip
329 if compresslevel is None:
330 f = gzip.GzipFile(fileName, "w")
331 else:
332 f = gzip.GzipFile(fileName, "w", compresslevel)
334 f = codecs.EncodedFile(f, "utf-16", encoding)
335 f.write(self.xml())
336 f.close()
338 else:
339 f = codecs.open(fileName, "w", encoding=encoding)
340 f.write(self.xml())
341 f.close()
343 saved.append(fileName)
345 def _write_tempfile(self, fileName=None, encoding="utf-8"):
346 if fileName is None:
347 fd, fileName = tempfile.mkstemp(".svg", "svgfig-")
348 os.close(fd)
349 else:
350 fileName = defaults.expand_fileName(fileName)
352 self.save(fileName, encoding)
353 return fileName
355 def inkview(self, fileName=None, encoding="utf-8"):
356 fileName = self._write_tempfile(fileName, encoding)
357 os.spawnvp(os.P_NOWAIT, "inkview", ("inkview", fileName))
359 def inkscape(self, fileName=None, encoding="utf-8"):
360 fileName = self._write_tempfile(fileName, encoding)
361 os.spawnvp(os.P_NOWAIT, "inkscape", ("inkscape", fileName))
363 def firefox(self, fileName=None, encoding="utf-8"):
364 fileName = self._write_tempfile(fileName, encoding)
365 os.spawnvp(os.P_NOWAIT, "firefox", ("firefox", fileName))
367 ### pickleability and value-based equality
368 def __getstate__(self):
369 return (sys.version_info, defaults.version_info, self.__dict__)
371 def __setstate__(self, state):
372 python_version = state[0]
373 svgfig_version = state[1]
374 if svgfig_version != defaults.version_info:
375 warnings.warn("Object created in SVGFig %s, but this is SVGFig %s" % (".".join(map(str, svgfig_version)), ".".join(map(str, defaults.version_info))), defaults.VersionWarning, 5)
376 self.__dict__ = state[2]
378 def __eq__(self, other):
379 if id(self) == id(other): return True
380 if self.__class__ != other.__class__: return False
381 selfdict = copy.copy(self.__dict__)
382 otherdict = copy.copy(other.__dict__)
383 del selfdict["_svg"]
384 del otherdict["_svg"]
385 return selfdict == otherdict
387 def __ne__(self, other): return not (self == other)
389 def __deepcopy__(self, memo={}):
390 output = new.instance(self.__class__)
391 output.__dict__ = copy.deepcopy(self.__dict__, memo)
392 if "repr" in output.__dict__: del output.__dict__["repr"]
393 memo[id(self)] = output
394 return output
396 ### act like a list
397 def append(self, other): self.children.append(other)
398 def prepend(self, other): self.children[0:0] = [other]
399 def insert(self, i, other): self.children.insert(i, other)
400 def remove(self, other): self.children.remove(other)
401 def __len__(self): return len(self.children)
403 def extend(self, other):
404 if isinstance(other, SVG):
405 self.children.extend(other.children)
406 elif isinstance(other, basestring):
407 self.children.append(other)
408 else:
409 self.children.extend(other)
411 def __add__(self, other):
412 output = copy.deepcopy(self)
413 output += other
414 return output
416 def __iadd__(self, other):
417 self.children.append(other)
418 return self
420 def __mul__(self, other):
421 output = copy.deepcopy(self)
422 output *= other
423 return output
425 def __rmul__(self, other):
426 return self * other
428 def __imul__(self, other):
429 self.children *= other
430 return self
432 def count(self, *args, **kwds): return self.children.count(*args, **kwds)
433 def index(self, *args, **kwds): return self.children.index(*args, **kwds)
434 def pop(self, *args, **kwds): return self.children.pop(*args, **kwds)
435 def reverse(self, *args, **kwds): return self.children.reverse(*args, **kwds)
437 ### act like a dict
438 def clear(self, *args, **kwds):
439 self.children = []
440 self.attrib.clear(*args, **kwds)
442 def update(self, other):
443 if isinstance(other, SVG):
444 self.attrib.update(other.attrib)
445 else:
446 self.attrib.update(other)
448 def __contains__(self, other):
449 return other in self.attrib or other in self.children
451 def fromkeys(self, *args, **kwds): return self.attrib.fromkeys(*args, **kwds)
452 def has_key(self, *args, **kwds): return self.attrib.has_key(*args, **kwds)
453 def items(self, *args, **kwds): return self.attrib.items(*args, **kwds)
454 def keys(self, *args, **kwds): return self.attrib.keys(*args, **kwds)
455 def values(self, *args, **kwds): return self.attrib.values(*args, **kwds)
456 def get(self, *args, **kwds): return self.attrib.get(*args, **kwds)
457 def setdefault(self, *args, **kwds): return self.attrib.setdefault(*args, **kwds)
458 def iteritems(self, *args, **kwds): return self.attrib.iteritems(*args, **kwds)
459 def iterkeys(self, *args, **kwds): return self.attrib.iterkeys(*args, **kwds)
460 def itervalues(self, *args, **kwds): return self.attrib.itervalues(*args, **kwds)
461 def pop(self, *args, **kwds): return self.attrib.pop(*args, **kwds)
462 def popitem(self, *args, **kwds): return self.attrib.popitem(*args, **kwds)
463 def copy(self): return copy.copy(self)
464 def deepcopy(self): return copy.deepcopy(self)
466 ############################### rules for converting into XML
468 # how to convert SVG objects into XML (as a list of lines to be joined later)
469 def svg_to_xml(svg, indent, depth=0):
470 # if the tag is None, it's a dynamic object that needs to be turned into _svg
471 if isinstance(svg, SVG) and svg.tag is None:
472 svg.svg()
473 svg = svg._svg # follow that? good.
475 if isinstance(svg, basestring):
476 return [svg]
478 elif isinstance(svg, SVG):
479 line = [indent * depth, u"<", svg.tag, u" "]
480 remaining = copy.copy(svg.attrib) # shallow copy that we can pop
482 try:
483 line.append(u"id=\"%s\" " % remaining.pop("id"))
484 except KeyError: pass
486 # signature attributes first, for readability
487 signature = getattr(defaults, "signature_%s" % svg.tag, None)
488 if signature is not None:
489 for name in signature:
490 try:
491 line.append(u"%s=\"%s\" " % (name, attrib_to_xml(svg.tag, name, remaining.pop(name))))
492 except KeyError: pass
494 remainingkeys = remaining.keys()
495 remainingkeys.sort() # for reproducible XML (maybe also helps readability)
496 for name in remainingkeys:
497 line.append(u"%s=\"%s\" " % (name, attrib_to_xml(svg.tag, name, remaining[name])))
499 if len(svg.children) == 0:
500 line.append(u"/>")
501 return [u"".join(line)]
503 else:
504 line.append(u">")
506 # no indenting for text
507 if svg.tag in ("text", "tspan"):
508 for i in svg.children:
509 line.extend(svg_to_xml(i, indent, 0))
510 line.append(u"</%s>" % (svg.tag))
511 return [u"".join(line)]
513 else:
514 lines = [u"".join(line)]
515 for i in svg.children:
516 lines.extend(svg_to_xml(i, indent, depth+1))
517 lines.append(u"%s</%s>" % (indent * depth, svg.tag))
518 return lines
520 else:
521 if type(svg) == types.InstanceType:
522 raise TypeError, "SVG contains an unrecognized object: instance of class %s" % svg.__class__.__name__
523 else:
524 raise TypeError, "SVG contains an unrecognized object: %s" % type(svg)
526 # how to convert different attribute types into XML
527 def attrib_to_xml(tag, name, value):
528 if isinstance(value, basestring):
529 return value
531 elif isinstance(value, (int, long, float)):
532 return repr(value) # more precise
534 elif isinstance(value, (list, tuple)) and tag == "path" and name == "d":
535 def numbertostr(x):
536 if isinstance(x, (int, long, float)): return repr(x) # more precise
537 else: return x
539 line = []
540 lastcommand = None
541 for datum in value:
542 if not isinstance(datum, (list, tuple)):
543 raise TypeError, "Pathdata elements must be lists/tuples"
545 command = datum[0]
546 args = map(numbertostr, datum[1:])
548 if lastcommand == command:
549 line.append(u" ")
550 line.append(u" ".join(args))
551 lastcommand = command
552 else:
553 line.append(command)
554 line.append(u" ".join(args))
556 lastcommand = command
558 return u"".join(line)
560 elif isinstance(value, (list, tuple)):
561 line = []
562 for v in value:
563 line.append(attrib_to_xml(tag, name, v))
564 return u", ".join(line)
566 elif isinstance(value, dict):
567 line = []
568 for n, v in value.items():
569 line.append(u"%s:%s" % (n, attrib_to_xml(tag, name, v)))
570 return u"; ".join(line)
572 else:
573 return unicode(value)
575 ############################### XML preprocessor instructions and comments
577 class Instruction(SVG):
578 def __init__(self, tag, text):
579 self.__dict__["tag"] = tag
580 self.__dict__["attrib"] = {}
581 self.__dict__["children"] = []
582 self.__dict__["_svg"] = self
583 self.__dict__["text"] = text
585 def xml(self): return "<?%s %s?>" % (self.tag, self.text)
587 def __repr__(self):
588 value = re.sub("\n", "\\\\n", self.text)
589 if len(value) > 23:
590 return "<?%s %s...?>" % (self.tag, value[0:20])
591 else:
592 return "<?%s %s?>" % (self.tag, value)
594 class Comment(SVG):
595 def __init__(self, text):
596 if text.find("--") != -1:
597 raise ValueError, "SVG comments must not include '--'"
598 self.__dict__["tag"] = "comment"
599 self.__dict__["attrib"] = {}
600 self.__dict__["children"] = []
601 self.__dict__["_svg"] = self
602 self.__dict__["text"] = text
604 def __eq__(self, other): return SVG.__eq__(self, other) and self.text == other.text
606 def xml(self): return "<!-- %s -->" % self.text
608 def __repr__(self):
609 value = re.sub("\n", "\\\\n", self.text)
610 if len(value) > 23:
611 return "<!-- %s... -->" % value[0:20]
612 else:
613 return "<!-- %s -->" % value
615 class CDATA(SVG):
616 def __init__(self, text):
617 if text.find("]]>") != -1:
618 raise ValueError, "CDATA must not include ']]>'"
619 self.__dict__["tag"] = "CDATA"
620 self.__dict__["attrib"] = {}
621 self.__dict__["children"] = []
622 self.__dict__["_svg"] = self
623 self.__dict__["text"] = text
625 def __eq__(self, other): return SVG.__eq__(self, other) and self.text == other.text
627 def xml(self): return "<![CDATA[%s]]>" % self.text
629 def __repr__(self):
630 value = re.sub("\n", "\\\\n", self.text)
631 if len(value) > 23:
632 return "<![CDATA[%s...]]>" % value[0:20]
633 else:
634 return "<![CDATA[%s]]>" % value
636 ############################### reading SVG from a file
638 def load(fileName):
639 if re.search("\.svgz$", fileName, re.I) or re.search("\.gz$", fileName, re.I):
640 import gzip
641 f = gzip.GzipFile(fileName)
642 else:
643 f = file(fileName)
644 return load_stream(f)
646 def template(fileName, svg, replaceme="REPLACEME"):
647 output = load(fileName)
648 for treeindex, i in output.walk():
649 if isinstance(i, SVG) and i.tag == replaceme:
650 output[treeindex] = svg
651 return output
653 def load_stream(stream):
654 from xml.sax import handler, make_parser
655 from xml.sax.handler import feature_namespaces, feature_external_ges, feature_external_pes
657 # With a little thought, this could be streamlined. In its current
658 # state, it works (with processing instructions, comments, and CDATA).
659 class ContentHandler(handler.ContentHandler):
660 def __init__(self):
661 self.stack = []
662 self.output = None
663 self.all_whitespace = re.compile("^\s*$")
664 self.CDATA = False
666 def startElement(self, tag, attrib):
667 s = SVG(tag)
668 s.attrib = dict(attrib.items())
669 if len(self.stack) > 0:
670 last = self.stack[-1]
671 last.children.append(s)
672 self.stack.append(s)
674 def characters(self, ch):
675 if self.CDATA:
676 last = self.stack[-1]
677 last.text += ch
679 else:
680 if not isinstance(ch, basestring) or self.all_whitespace.match(ch) is None:
681 if len(self.stack) > 0:
682 last = self.stack[-1]
683 if len(last.children) > 0 and isinstance(last.children[-1], basestring):
684 last.children[-1] = last.children[-1] + "\n" + ch
685 else:
686 last.children.append(ch)
688 def endElement(self, tag):
689 if len(self.stack) > 0:
690 last = self.stack[-1]
691 self.output = self.stack.pop()
693 # If a processing instruction is outside the main <svg> tag, it will be lost.
694 def processingInstruction(self, target, data):
695 s = Instruction(target, data)
696 if len(self.stack) > 0:
697 last = self.stack[-1]
698 last.children.append(s)
699 self.output = s
701 def comment(self, comment):
702 s = Comment(re.sub("(^ | $)", "", comment))
703 if len(self.stack) > 0:
704 last = self.stack[-1]
705 last.children.append(s)
706 self.output = s
708 def startCDATA(self):
709 s = CDATA("")
710 if len(self.stack) > 0:
711 last = self.stack[-1]
712 last.children.append(s)
713 self.stack.append(s)
714 self.CDATA = True
716 def endCDATA(self):
717 if len(self.stack) > 0:
718 last = self.stack[-1]
719 self.output = self.stack.pop()
720 self.CDATA = False
722 def startDTD(self, name, public_id, system_id): pass
723 def endDTD(self): pass
724 def startEntity(self, name): pass
725 def endEntity(self, name): pass
727 ch = ContentHandler()
728 parser = make_parser()
729 parser.setContentHandler(ch)
730 parser.setProperty(handler.property_lexical_handler, ch)
731 parser.setFeature(feature_namespaces, 0)
732 parser.setFeature(feature_external_ges, 0)
733 parser.parse(stream)
734 return ch.output
736 ############################### standard representation for transformations and parametric functions
738 def cannonical_transformation(expr):
739 if expr is None:
740 output = lambda x, y: (x, y)
741 output.func_name = "identity"
742 return output
744 elif callable(expr):
746 # 2 real -> 2 real
747 if expr.func_code.co_argcount == 2:
748 return expr
750 # complex -> complex
751 elif expr.func_code.co_argcount == 1:
752 split = lambda z: (z.real, z.imag)
753 output = lambda x, y: split(expr(complex(x, y)))
754 output.func_name = expr.func_name
755 return output
757 else:
758 raise TypeError, "Must be a 2 -> 2 real function or a complex -> complex function"
760 else:
761 compiled = compile(expr, expr, "eval")
763 # 2 real -> 2 real
764 if "x" in compiled.co_names and "y" in compiled.co_names:
765 evalexpr = expr
766 evalexpr = re.sub("x", "float(x)", evalexpr)
767 evalexpr = re.sub("y", "float(y)", evalexpr)
768 output = eval("lambda x,y: (%s)" % evalexpr, math.__dict__)
769 output.func_name = "x, y -> %s" % expr
770 return output
772 # complex -> complex
773 elif "z" in compiled.co_names:
774 evalexpr = re.sub("z", "complex(x,y)", expr)
775 output = eval("lambda x,y: ((%s).real, (%s).imag)" % (evalexpr, evalexpr), cmath.__dict__)
776 output.func_name = "z -> %s" % expr
777 return output
779 else:
780 raise TypeError, "Transformation string '%s' must contain real 'x' and 'y' or complex 'z'" % expr
782 def cannonical_parametric(expr):
783 if callable(expr):
785 # 1 real -> 2 real
786 if expr.func_code.co_argcount == 1:
787 return expr
789 else:
790 raise TypeError, "Must be a 1 -> 2 real function"
792 else:
793 compiled = compile(expr, expr, "eval")
795 # 1 real -> 2 real
796 if "t" in compiled.co_names:
797 output = eval("lambda t: (%s)" % re.sub("t", "float(t)", expr), math.__dict__)
798 output.func_name = "t -> %s" % expr
799 return output
801 # 1 real -> 1 real
802 elif "x" in compiled.co_names:
803 output = eval("lambda t: (t, %s)" % re.sub("x", "float(t)", expr), math.__dict__)
804 output.func_name = "x -> %s" % expr
805 return output
807 # real (a complex number restricted to the real axis) -> complex
808 elif "z" in compiled.co_names:
809 evalexpr = re.sub("z", "complex(t,0)", expr)
810 output = eval("lambda t: ((%s).real, (%s).imag)" % (evalexpr, evalexpr), cmath.__dict__)
811 output.func_name = "z -> %s" % expr
812 return output
814 else:
815 raise TypeError, "Parametric string '%s' must contain real 't', 'x', or 'z'" % expr
817 ############################### make code objects pickleable (so that curves and transformations are pickleable)
819 def _code_constructor(code_args, python_version, svgfig_version):
820 if python_version != sys.version_info or svgfig_version != defaults.version_info:
821 warnings.warn("Function created in Python %s/SVGFig %s, but this is Python %s/SVGFig %s"
822 % (".".join(map(str, python_version)), ".".join(map(str, svgfig_version)), ".".join(map(str, sys.version_info)), ".".join(map(str, defaults.version_info))),
823 defaults.VersionWarning, 5)
824 return new.code(*code_args)
826 def _code_serializer(code):
827 if code.co_freevars or code.co_cellvars:
828 raise ValueError, "Sorry, can't pickle code that depends on local variables %s %s" % (str(code.co_freevars), str(code.co_cellvars))
829 return _code_constructor, ((code.co_argcount, code.co_nlocals, code.co_stacksize,
830 code.co_flags, code.co_code, code.co_consts, code.co_names,
831 code.co_varnames, code.co_filename, code.co_name,
832 code.co_firstlineno, code.co_lnotab),
833 sys.version_info, defaults.version_info)
836 copy_reg.pickle(types.CodeType, _code_serializer)