root/trunk/proj/w3c/xmlNode.py

Revision 369, 25.9 kB (checked in by sholloway, 5 years ago)

Correction to the colors module to handle both 6-digit and 3-digit hex numbers.
Added xmlns search parameters as an alias for "namespace"

Line 
1 #!/usr/bin/env python
2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 ##~ Copyright (C) 2002-2004  TechGame Networks, LLC.
4 ##~
5 ##~ This library is free software; you can redistribute it and/or
6 ##~ modify it under the terms of the BSD style License as found in the
7 ##~ LICENSE file included with this distribution.
8 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
9
10 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11 #~ Imports
12 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
13
14 from types import SliceType as _SliceType
15 from xml.sax.saxutils import quoteattr as xmlquoteattr
16 from xml.sax.saxutils import escape as xmlescape
17
18 from xmlNamespaceMap import XMLNamespaceMap
19
20 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21 #~ Definitions
22 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
23
24 linesep = '\n'
25
26 _NodeKeyOrSlice = (int, long, _SliceType)
27
28 class Any(object):
29     def __eq__(self, other): return True
30     def __ne__(self, other): return False
31 Any = Any()
32
33 class MatchObj(object):
34     def __init__(self, testcall):
35         self.testcall = testcall
36     def __eq__(self, other):
37         return bool(self.testcall(other))
38
39 truelambda = lambda each: True
40 falselambda = lambda each: False
41
42 def makeNodeMatcher(*args, **kw):
43     if args or kw:
44         return makeNodeMatcherEx(*args, **kw)
45     else: return truelambda
46
47 def makeNodeMatcherEx(node=Any, namespace=Any, prefix=Any, **kw):
48     if 'xmlns' in kw:
49         namespace = kw.pop('xmlns')
50     def nodematcher(each):
51         if isinstance(each, basestring): return node==each
52         else: return node==each.node and namespace==each.namespace and prefix==each.prefix
53     return nodematcher
54
55 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
56
57 class XMLNodeBase(object):
58     node = None
59     namespace = None
60     prefix = None
61
62     def __str__(self):
63         """Returns XMLNode as a string in XML form."""
64         return str(self.toXML())
65
66     def asXMLNode(self):
67         return self
68
69     def toXML(self, pretty=False, level=0, indent='    ', newline=linesep):
70         raise NotImplementedError
71
72     def encode(self, *args, **kw):
73         return self.toXML().encode(*args, **kw)
74
75     def decode(self, *args, **kw):
76         return self.toXML().decode(*args, **kw)
77
78 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
79
80 class XMLExplicit(XMLNodeBase):
81     explicit = ''
82
83     def __init__(self, explicit=''):
84         self.setExplicit(explicit)
85
86     def toXML(self, pretty=False, level=0, indent='    ', newline=linesep):
87         result = self.getExplicit()
88         if not pretty:
89             return result
90         else:
91             return result.replace(newline, newline+indent*level)
92
93     def __cmp__(self, other):
94         return cmp(self.getExplicit(), other)
95
96     def getExplicit(self):
97         return self.explicit
98     def setExplicit(self, explicit):
99         self.explicit = explicit
100
101 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
102
103 class XMLComment(XMLNodeBase):
104     comment = ''
105     def __init__(self, comment=''):
106         self.setComment(comment)
107
108     def toXML(self, pretty=False, level=0, indent='    ', newline=linesep):
109         comment = self.getComment()
110         if not pretty:
111             return '<!--' + comment + '-->'
112         else:
113             result = ['<!--'] + list(comment.split(newline)) + ['-->']
114             if len(result) > 3:
115                 joinstr = newline+indent*level
116             else: joinstr = ' '
117             return joinstr.join(result)
118
119     def __cmp__(self, other):
120         return cmp(self.getComment(), other)
121
122     def getComment(self):
123         return self.comment.replace('<!--', '').replace('-->', '')
124     def setComment(self, comment):
125         self.comment = comment.replace('<!--', '').replace('-->', '')
126
127 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
128
129 class XMLNode(XMLNodeBase):
130     """Simple class to build valid XML.
131     Also has some basic access, query, and iteration methods.
132    
133     >>> node = XMLNode('mynode', 'mynamespace')
134     >>> node.attrs['aname'] = 'avalue'
135     >>> node.aname
136     'avalue'
137     >>> node += "\n"
138     >>> node += "some cdata text \n"
139     >>> node += "some more cdata text \n"
140     >>> node += ('another_node', )
141     >>> node += "\n"
142     >>> node += ('third', 'namespace-for-third')
143     >>> node += "\n"
144     >>> node += ('forth', 'namespace-for-forth', 'four')
145     >>> node += "\n"
146     >>> print node
147     <mynode xmlns="mynamespace" aname="avalue">
148     some cdata text
149     some more cdata text
150     <another_node/>
151     <third xmlns="namespace-for-third"/>
152     <four:forth xmlns:four="namespace-for-forth"/>
153     </mynode>
154     """
155
156     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
157     #~ Constants / Variables / Etc.
158     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
159
160     #__slots__ = ['node', 'prefix', 'namespaces', 'elems', 'attrs', 'nodebuilder', 'softspace']
161     nodebuilder = None
162     default_node = None
163     default_attributes = {}
164     default_namespaces = XMLNamespaceMap()
165     default_elements = []
166     __xmlattrmixin__ = None
167
168     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
169     #~ Special Methods
170     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
171
172     def __init__(self, node=None, namespace=None, prefix='', default_namespaces=None):
173         if default_namespaces is not None:
174             self.namespaces = default_namespaces
175         else:
176             self.namespaces = self.default_namespaces.copy()
177         self.elems = self.default_elements[:]
178         self.attrs = self.default_attributes.copy()
179         self.node = node or self.default_node or self.__class__.__name__
180         if namespace is None:
181             self.namespace = (prefix, )
182         else:
183             self.namespace = (prefix, namespace)
184         self.softspace = 0 # for compatibility with file-like objects
185
186     def __len__(self):
187         return len(self.elems)
188
189     def __getitem__(self, key, *args, **kw):
190         """Returns a list of matching child elements of XMLNode.  See listItems."""
191         if isinstance(key, _NodeKeyOrSlice):
192             return self.elems.__getitem__(key, *args, **kw)
193         else:
194             return self.listItems(key, *args, **kw)
195     def __setitem__(self, key, *args, **kw):
196         """Sets child element at self.elem[key] = value."""
197         if isinstance(key, _NodeKeyOrSlice):
198             return self.elems.__setitem__(key, *args, **kw)
199         else:
200             raise TypeError, "Cannot set node elemenets with non integer key item operations"
201     def __delitem__(self, key, *args, **kw):
202         """Removes all matching child elements of XMLNode.  See delItems."""
203         if isinstance(key, _NodeKeyOrSlice):
204             return self.elems.__delitem__(key, *args, **kw)
205         else:
206             self.delItems(key, *args, **kw)
207
208     def __contains__(self, key, *args, **kw):
209         """Returns True if key is a child element of XMLNode.  See hasItem."""
210         if isinstance(key, _NodeKeyOrSlice):
211             return self.elems.__contains__(key, *args, **kw)
212         else:
213             return self.hasItem(key, *args, **kw)
214
215     def __iadd__(self, other):
216         """Adds an element to XMLNode.  Returns self.  See addItem."""
217         self.addItem(other)
218         return self
219
220     def __add__(self, other):
221         """Adds an element to XMLNode.  Returns node or self.  See addItem."""
222         return self.addItem(other)
223
224     def __iter__(self):
225         """Returns an iterator of child elements."""
226         return iter(self.elems)
227
228     def __repr__(self):
229         return '''<%s (%r, %r, %r)>''' % (self.__class__.__name__, self.node, self.namespace, self.prefix)
230
231     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
232     #~ Public Methods
233     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
234
235     def addAttr(self, *args, **kw):
236         """Adds attributes to XMLNode."""
237         if args: self.attrs.update(dict(args))
238         if kw: self.attrs.update(kw)
239         return self
240
241     def addData(self, cdata):
242         """Adds a child cdata to XMLNode."""
243         if cdata is not None:
244             self.elems.append(cdata)
245         return self
246
247     def addComment(self, comment, *args, **kw):
248         if comment is not None:
249             self.elems.append(XMLComment(comment, *args, **kw))
250         return self
251
252     def addExplicit(self, *args, **kw):
253         self.elems.append(XMLExplicit(*args, **kw))
254         return self
255
256     def addRaw(self, arg0, *args, **kw):
257         if not (args or kw) and isinstance(arg0, XMLNodeBase):
258             self.elems.append(arg0)
259             return arg0
260         else:
261             return self.addExplicit(arg0, *args, **kw)
262
263     def addText(self, *args, **kw):
264         """See addData."""
265         return self.addData(*args, **kw)
266
267     def insertData(self, idx, cdata):
268         if cdata is not None:
269             self.elems.insert(idx, cdata)
270         return self
271
272     def insertText(self, *args, **kw):
273         """See insertData."""
274         return self.insertData(*args, **kw)
275
276     def write(self, data):
277         """For use in conjunction with file-like uses.
278
279         >>> node = XMLNode('mynode', 'mynamespace')
280         >>> print >> node
281         >>> print >> node, 'some cdata'
282         >>> node.toXML()
283         '<mynode xmlns="mynamespace">\\nsome cdata\\n</mynode>'
284         """
285         self.addData(data)
286
287     def addNode(self, arg0, *args, **kw):
288         """Adds a child node to XMLNode."""
289         if arg0 is None:
290             return None
291         self.softspace = 0
292         result = self._makeNode(arg0, *args, **kw)
293         self.elems.append(result)
294         return result
295
296     def insertNode(self, idx, arg0, *args, **kw):
297         if arg0 is None:
298             return None
299         result = self._makeNode(arg0, *args, **kw)
300         self.elems.insert(idx, result)
301         return result
302
303     def addItem(self, elem):
304         """Adds a child element to XMLNode.
305         Item is considered cdata if it is of string type,
306         or a node, otherwise."""
307         if isinstance(elem, basestring):
308             return self.addData(elem)
309         elif isinstance(elem, XMLNodeBase):
310             # For elements that are prebuilt
311             self.elems.append(elem)
312             return elem
313         else:
314             # For building from tuples and lists
315             return self.addNode(*elem)
316
317     def insertItem(self, idx, elem):
318         if isinstance(elem, basestring):
319             return self.insertData(elem)
320         elif isinstance(elem, XMLNodeBase):
321             # For elements that are prebuilt
322             self.elems.insert(idx, elem)
323             return elem
324         else:
325             # For building from tuples and lists
326             return self.insertNode(*elem)
327
328     def setxmlns(self, *args, **kw):
329         return self.namespaces.setxmlns(*args, **kw)
330
331     #~ iteration over elements ~~~~~~~~~~~~~~~~~~~~~~~~~~
332
333     def enumData(self, match=None):
334         """Returns a generator to iterate through the matching data indices in XMLNode"""
335         if match is None: match = Any
336         elif callable(match): match = MatchObj(match)
337         idx = 0
338         for each in self.elems:
339             if isinstance(each, basestring):
340                 if match == each:
341                     yield idx, each
342             idx += 1
343
344     def popData(self, match=None, **kw):
345         idxList, result = [], []
346         for idx, item in self.enumData(match, **kw):
347             idxList.append(idx)
348             result.append(item)
349         map(self.elems.__delitem__, idxList[::-1])
350         return result
351
352     def iterData(self, match=None):
353         """Returns a generator to iterate through the matching data in XMLNode"""
354         if match is None: match = Any
355         elif callable(match): match = MatchObj(match)
356         for each in self.elems:
357             if isinstance(each, basestring):
358                 if match == each:
359                     yield each
360
361     def listData(self, *args, **kw):
362         """Returns a list of matching data in XMLNode"""
363         return [x for x in self.iterData(*args, **kw)]
364
365     def delData(self, *args, **kw):
366         idxList = [x[0] for x in self.enumData(*args, **kw)]
367         map(self.elems.__delitem__, idxList[::-1])
368
369     def data(self, joinstr=''):
370         return joinstr.join(map(type(joinstr), self.iterData(None)))
371
372     def hasData(self, *args, **kw):
373         """Returns True if data is in XMLNode"""
374         try:
375             self.iterData(*args, **kw).next()
376             return True
377         except StopIteration:
378             return False
379
380     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
381
382     def enumComments(self, match=None):
383         """Returns a generator to iterate through the matching comments indices in XMLNode"""
384         if match is None: match = Any
385         elif callable(match): match = MatchObj(match)
386         idx = 0
387         for each in self.elems:
388             if isinstance(each, XMLComment):
389                 if match == each:
390                     yield idx, each
391             idx += 1
392
393     def popComments(self, match=None, **kw):
394         idxList, result = [], []
395         for idx, item in self.enumComments(match, **kw):
396             idxList.append(idx)
397             result.append(item)
398         map(self.elems.__delitem__, idxList[::-1])
399         return result
400
401     def iterComments(self, match=None):
402         """Returns a generator to iterate through the matching comments in XMLNode"""
403         if match is None: match = Any
404         elif callable(match): match = MatchObj(match)
405         for each in self.elems:
406             if isinstance(each, XMLComment):
407                 if match == each:
408                     yield each
409
410     def listComments(self, *args, **kw):
411         """Returns a list of matching comments in XMLNode"""
412         return [x for x in self.iterComments(*args, **kw)]
413
414     def delComments(self, *args, **kw):
415         idxList = [x[0] for x in self.enumComments(*args, **kw)]
416         map(self.elems.__delitem__, idxList[::-1])
417
418     def comments(self, joinstr=''):
419         return joinstr.join(map(type(joinstr), self.iterComments(None)))
420
421     def hasComments(self, *args, **kw):
422         """Returns True if comments is in XMLNode"""
423         try:
424             self.iterComments(*args, **kw).next()
425             return True
426         except StopIteration:
427             return False
428
429     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
430
431     def enumNodes(self, *args, **kw):
432         """Returns a generator to iterate through the matching child node indicies of XMLNode.
433         obj.enumNodes(node=Any, namespace=Any, prefix=Any)"""
434         match = makeNodeMatcher(*args, **kw)
435         idx = 0
436         for each in self.elems:
437             if not isinstance(each, basestring):
438                 if match(each):
439                     yield idx, each
440             idx += 1
441
442     def popNodes(self, *args, **kw):
443         idxList, result = [], []
444         for idx, item in self.enumNodes(*args, **kw):
445             idxList.append(idx)
446             result.append(item)
447         map(self.elems.__delitem__, idxList[::-1])
448         return result
449
450     def iterNodes(self, *args, **kw):
451         """Returns a generator to iterate through the matching child nodes of XMLNode.
452         obj.iterNodes(node=Any, namespace=Any, prefix=Any)"""
453         match = makeNodeMatcher(*args, **kw)
454         for each in self.elems:
455             if not isinstance(each, basestring):
456                 if match(each):
457                     yield each
458
459     def listNodes(self, *args, **kw):
460         """Returns a list of matching child nodes of XMLNode"""
461         return [x for x in self.iterNodes(*args, **kw)]
462
463     def delNodes(self, *args, **kw):
464         """Removes all matching child nodes of XMLNode"""
465         idxList = [x[0] for x in self.enumNodes(*args, **kw)]
466         map(self.elems.__delitem__, idxList[::-1])
467
468     def hasNode(self, *args, **kw):
469         """Returns True if elem is a child node of the XMLNode"""
470         try:
471             self.iterNodes(*args, **kw).next()
472             return True
473         except StopIteration:
474             return False
475
476     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
477
478     def enumItems(self, key=None, **kw):
479         """Returns a generator to iterate through the matching child element indicies of XMLNode"""
480         if key is None:
481             return xrange(0, len(self.elems))
482         elif isinstance(key, tuple):
483             return self.enumNodes(*key, **kw)
484         else:
485             return self.enumData(key, **kw)
486
487     def popItems(self, key=None, **kw):
488         idxList, result = [], []
489         for idx, item in self.enumItems(key, **kw):
490             idxList.append(idx)
491             result.append(item)
492         map(self.elems.__delitem__, idxList[::-1])
493         return result
494
495     def iterItems(self, key=None, **kw):
496         """Returns a generator to iterate through the matching child elements of XMLNode"""
497         if key is None:
498             return iter(self.elems)
499         elif isinstance(key, tuple):
500             return self.iterNodes(*key, **kw)
501         else:
502             return self.iterData(key, **kw)
503
504     def listItems(self, *args, **kw):
505         """Returns a list of matching child elements of XMLNode"""
506         return [x for x in self.iterItems(*args, **kw)]
507
508     def delItems(self, key, **kw):
509         """Removes all matching child elements of XMLNode"""
510         if key is None:
511             del self.elems[:]
512         elif isinstance(key, tuple):
513             self.delNodes(*key, **kw)
514         else:
515             self.delData(key, **kw)
516
517     def hasItem(self, elem=None, **kw):
518         """Returns True if elem is a child element of the XMLNode"""
519         if elem is None:
520             for i in self.iterItems(None, **kw):
521                 return True
522             return False
523         elif isinstance(elem, (int, long)):
524             del self.elems[elem]
525         elif isinstance(elem, tuple):
526             return self.hasNode(*elem, **kw)
527         else:
528             return self.hasData(*elem, **kw)
529
530     #~ namespace property ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
531
532     def getNamespace(self):
533         """Returns the namespace of the XMLNode"""
534         return self.namespaces.xmlns(self.prefix or '')
535     def setNamespace(self, value):
536         """Sets the namespace of the XMLNode"""
537         if isinstance(value, tuple):
538             if len(value) == 1:
539                 self.prefix, namespace = value[0] or '', None
540             else:
541                 self.prefix, namespace = value[0] or '', value[1]
542         else:
543             self.prefix, namespace = self.prefix or '', value
544         if namespace is not None:
545             self.namespaces.setxmlns(self.prefix, namespace)
546     def delNamespace(self):
547         """Removes the namespace of the XMLNode"""
548         try: del self.namespaces[self.prefix or '']
549         except KeyError: pass
550     namespace = property(getNamespace, setNamespace, delNamespace)
551
552     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
553
554     def toXML(self, pretty=False, level=0, indent='    ', newline=linesep):
555         """Converts the XMLNode to valid XML."""
556         result = ['<']
557         if self.prefix:
558             nodename = self.prefix + ':' + self.node
559         else: nodename = self.node
560         result = ['<' + nodename]
561
562         result.append(self.namespaces.toXML())
563
564         for name, value in self.attrs.iteritems():
565             result.append(' %s=%s' % (name, xmlquoteattr(value or "")))
566
567         if self.elems:
568             result.append('>')
569             result.extend(self._elemsToXML(pretty, level+1, indent, newline))
570             if pretty: result.append(newline + indent * level)
571             result.append('</%s>' % nodename)
572         else:
573             result.append('/>')
574         return ''.join(result) 
575
576     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
577     #~ Protected Methods
578     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
579
580     def _getNodeFactory(self, *args, **kw):
581         nodebuilder = kw.pop('nodebuilder', self.__class__)
582         return nodebuilder, args, kw
583
584     def _makeNode(self, arg0, *args, **kw):
585         nodebuilder = getattr(arg0, 'asXMLNode', None)
586         if nodebuilder is not None:
587             return nodebuilder()
588         else:
589             kw['default_namespaces'] = self.namespaces.newChain()
590             nodebuilder, args, kw = self._getNodeFactory(arg0, *args, **kw)
591             return nodebuilder(*args, **kw)
592
593     def _elemsToXML(self, pretty, level, indent, newline):
594         """Converts child elements of XMLNode to valid XML."""
595         if pretty: prettyIndent = newline + indent * level
596         result = []
597         wasText = False
598         for elem in self.elems:
599             if isinstance(elem, basestring):
600                 if pretty and not wasText: result.append(prettyIndent)
601                 elem = xmlescape(elem)
602                 if pretty:
603                     elem = elem.replace(newline, prettyIndent)
604                 result.append(elem)
605                 wasText = True
606             else:
607                 if pretty: result.append(prettyIndent)
608                 result.append(elem.toXML(pretty, level, indent, newline))
609                 wasText = False
610         return result
611
612 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
613 #~ xmlBuilder cooperation
614 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
615
616 try: import xmlBuilder
617 except ImportError: pass
618 else:
619     class NodeXML(XMLNode, xmlBuilder.ElementBase):
620         """An inherited bridge between ElementBase interface and XMLNode"""
621         def __init__(self, elemBuilder, parent, node, attributes, namespacemap):
622             prefix = namespacemap.prefix(node[0])
623             if node[0] in namespacemap: xmlns, nodename = node
624             elif namespacemap.nextmap is None: xmlns, nodename = node
625             else: xmlns, nodename = None, node[1]
626             XMLNode.__init__(self, nodename, xmlns, prefix, default_namespaces=namespacemap)
627
628             for key, value in attributes.iteritems():
629                 if isinstance(key, tuple):
630                     try:
631                         prefix = namespacemap.prefix(key[0])
632                         if not prefix: key = key[1]
633                         else: key = ':'.join((prefix, key[1]))
634                     except KeyError: key = key[1]
635                 self.attrs[key] = value
636
637         def xmlAddElement(self, elemBuilder, node, obj, srcref):
638             if obj is not None:
639                 self.elems.append(obj)
640         def xmlAddData(self, elemBuilder, data, srcref):
641             if data is not None:
642                 self.elems.append(data)
643         def xmlAddComment(self, elemBuilder, comment):
644             if comment is not None:
645                 self.elems.append(XMLComment(comment))
646
647     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
648
649     class NodeXMLAdaptor(xmlBuilder.ElementBase):
650         """An external adaptor to bridge between ElementBase interface and XMLNode"""
651         nodebuilder = XMLNode
652
653         def __init__(self, elemBuilder, parent, node, attributes, namespacemap, nodebuilder=XMLNode):
654             prefix = namespacemap.prefix(node[0])
655             if node[0] in namespacemap: xmlns, nodename = node
656             elif namespacemap.nextmap is None: xmlns, nodename = node
657             else: xmlns, nodename = None, node[1]
658             nodebuilder = nodebuilder or self.nodebuilder
659             self.result = nodebuilder(nodename, xmlns, prefix, default_namespaces=namespacemap)
660
661             for key, value in attributes.iteritems():
662                 if isinstance(key, tuple):
663                     try:
664                         prefix = namespacemap.prefix(key[0])
665                         if not prefix: key = key[1]
666                         else: key = ':'.join((prefix, key[1]))
667                     except KeyError: key = key[1]
668                 self.result.attrs[key] = value
669
670         def xmlAddElement(self, elemBuilder, node, obj, srcref):
671             self.result.elems.append(obj)
672         def xmlAddData(self, elemBuilder, data, srcref):
673             self.result.elems.append(data)
674         def xmlAddComment(self, elemBuilder, comment):
675             self.result.elems.append(XMLComment(comment))
676         def xmlGetElement(self, elemBuilder):
677             return self.result
678         def toXML(self, *args, **kw):
679             return self.result.toXML()
680
681     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
682
683     class XMLNodeFactory(object):
684         #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
685         #~ Constants / Variables / Etc.
686         #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
687
688         nodebuilder = XMLNode
689         xmladaptor = NodeXMLAdaptor
690
691         #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
692         #~ Public Methods
693         #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
694
695         def __init__(self, nodebuilder=None, xmladaptor=None):
696             self.nodebuilder = nodebuilder or self.nodebuilder
697             self.xmladaptor = xmladaptor or self.xmladaptor
698
699         def __call__(self, elemBuilder, parent, node, attributes, namespacemap):
700             return self.buildNode
701                
702         def buildNode(self, *args, **kw):
703             return self.xmladaptor(nodebuilder=self.nodebuilder, *args, **kw)
704
705     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
706
707     class Producer(xmlBuilder.XMLBuilder):
708         """An implementation of XMLBuilder that creates xmlnodes from an XML stream."""
709         ElementFactory = NodeXMLAdaptor
710         def _getElementFactory(self, elemBuilder, parent, node, attributes, namespacemap):
711             return self.xmlClassFactory
712
713     def asXMLNode(xmlOrNode):
714         if isinstance(xmlOrNode, XMLNode):
715             return xmlOrNode
716         else:
717             return Producer().parse(xmlOrNode)
718     XMLNode.asXMLNode = staticmethod(asXMLNode)
719
720 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
721 #~ Testing
722 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
723
724 if __name__=='__main__':
725     import StringIO
726     root = XMLNode('root', 'MyRootNamespace')
727     root += 'Some cdata'
728     root += ('subelem',)
729     root += 'More data'
730     root += ('element',)
731     root += 'anotherelem', 'SubNamespace'
732     anotherelem = root[-1]
733     anotherelem.attrs['myattr'] = 'a value'
734     anotherelem += 'some text'
735
736     print
737     print "Plain::"
738     print root.toXML()
739     print
740
741     print
742     print "Pretty::"
743     print root.toXML(True)
744     print
745
746     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
747
748     someXML = root.toXML(True)
749     xml = Producer().parse(someXML)
750     assert xml.toXML() == someXML
751
752     someXMLFile = StringIO.StringIO(someXML)
753     xml = Producer().parseFile(someXMLFile)
754     assert xml.toXML() == someXML
755
Note: See TracBrowser for help on using the browser.