root/tags/Release-0_3_2/RBFoundation/RBFoundation/XMLBuilder.py

Revision 272, 8.6 kB (checked in by sholloway, 6 years ago)

More bugfixes

Line 
1 #!/usr/bin/env python
2 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3 ##~ License
4 ##~
5 ##- The RuneBlade Foundation library is intended to ease some
6 ##- aspects of writing intricate Jabber, XML, and User Interface (wxPython, etc.)
7 ##- applications, while providing the flexibility to modularly change the
8 ##- architecture. Enjoy.
9 ##~
10 ##~ Copyright (C) 2002  TechGame Networks, LLC.
11 ##~
12 ##~ This library is free software; you can redistribute it and/or
13 ##~ modify it under the terms of the BSD style License as found in the
14 ##~ LICENSE file included with this distribution.
15 ##~
16 ##~ TechGame Networks, LLC can be reached at:
17 ##~ 3578 E. Hartsel Drive #211
18 ##~ Colorado Springs, Colorado, USA, 80920
19 ##~
20 ##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
21
22 """Abstract base classes for building Python object trees from an XML stream.
23
24 Classes:
25
26     XMLBuilderObjectBase
27     XMLBuilderMixin
28     XMLBuilder
29
30 History:
31
32     Please see XMLObjectify's history.
33 """
34
35 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
36 #~ Imports                                           
37 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38
39 from xml.parsers.expat import ParserCreate as _ParserCreate
40 from WeakBind import BindCallable as _BindCallable
41 from Foundation.ChainedDict import ChainedDict
42
43 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
44 #~ Classes
45 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
46
47 class XMLBuilderObjectBase(object):
48     """Base class for objects created by XMLBuilderMixin derived classes."""
49     def __init__(self, owner, parent, node, attributes, namespacemap): pass
50     def _addElement(self, node, obj): pass
51     def _addData(self, data): pass
52     def _xmlInitStarted(self): pass
53     def _xmlInitComplete(self): pass
54     def _xmlChildFactory(self, owner, parent, node, attributes, namespacemap): return None
55     def _toXML(self, strSplit='', *args, **kw):
56         if strSplit is not None: return strSplit.join([])
57         else: return []
58
59 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
60  
61 class XMLBuilderMixin(object):
62     """Abstract base class that guides the building of python objects from XML. 
63     Depends upon the interface defined by XMLBuilderObjectBase."""
64
65     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
66     #~ Constants / Variables / Etc.
67     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
68
69     _encoding = 'ASCII'
70     _seperator = '.'
71     _ParserFactory = _ParserCreate
72     NamespaceSynonyms = {}
73
74     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
75     #~ Special
76     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
77
78     def __init__(self):
79         self._elements = []
80         self._current_namespacemap = ChainedDict()
81
82     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
83     #~ Protected Methods
84     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
85
86     def _GetElementFactory(self, owner, element, node, attributes, namespacemap):
87         """Allows a derived class to return an instance factory (a class, since python is so awesome) for
88         this particular data.  The template method makes no restrictions, except that the instance factory
89         must be able to accept the same arguments as this method."""
90         raise KeyError, 'No Class Registered: %s %s' % node
91
92     def _GetOwner(self):
93         """Used by _start_element to point to the owner of the node.  Derived classes should
94         override this method the owner is different than the XMLBuilder"""
95         return self
96
97     def _start_namespace_decl_handler(self, prefix, uri):
98         """Part of the tree-style template method, called at the before the beginning of an XML node parse
99         to manage namespaces."""
100         self._current_namespacemap [uri] = prefix
101
102     def _end_namespace_decl_handler(self, prefix):
103         """Part of the tree-style template method, called at the after the end of an XML node parse
104         to manage namespaces."""
105         pass
106
107     def _start_element(self, name, attributes):
108         """Part of the tree-style template method, called at the beginning of an XML node parse.
109         Instantiates the element returned by _GetElementFactory."""
110
111         node = self._SplitQualifiedName(name)
112
113         newattributes = {}
114         for attrname, attrvalue in attributes.iteritems():
115             attrnamespace, attrname = self._SplitQualifiedName(attrname)
116             if not attrnamespace or attrnamespace == node[0]:
117                 newattributes[node[0], attrname] = attrvalue
118                 newattributes[attrname] = attrvalue
119             else: newattributes[attrnamespace, attrname] = attrvalue
120
121         self._current_namespacemap = ChainedDict({}, self._current_namespacemap)
122
123         args = (self._GetOwner(), self._elements and self._elements[-1] or None, node, newattributes, self._current_namespacemap.chained)
124         build_factory = self._GetElementFactory(*args)
125         newelement = build_factory(*args)
126         if self._elements:
127             self._elements[-1]._addElement(node, newelement)
128         self._elements.append(newelement)
129         self._elements[-1]._xmlInitStarted()
130
131     def _end_element(self, name):
132         """Part of the tree-style template method, called at the closing of an XML node parse.
133         Simply notifies the element that it is complete."""
134         self._current_namespacemap = ChainedDict({}, self._current_namespacemap.chained)
135
136         if self._elements:
137             result = self._elements.pop()
138             result._xmlInitComplete()
139         else: result = None
140
141         return result
142
143     def _char_data(self, data):
144         """Part of the tree-style template method, called when PCData is found."""
145         self._elements[-1]._addData(data)
146
147     def SetParserFactory(self, ParserFactory):
148         self._ParserFactory = ParserFactory
149
150     def _SplitQualifiedName(self, combined):
151         idx = combined.rfind(self._seperator)
152         if idx < 0:
153             namespace = None
154             name = combined
155         else:
156             namespace = combined[0:idx]
157             name = combined[idx + len(self._seperator):]
158         namespace = self.NamespaceSynonyms.get(namespace, namespace)
159         return namespace, name
160
161     def _CreateParser(self):
162         """Creates the Expat parser in a python-OO way."""
163         parser = self._ParserFactory(self._encoding, self._seperator)
164         parser.returns_unicode = self._encoding != 'ASCII' and 1 or 0
165         parser.StartElementHandler = _BindCallable(self._start_element)
166         parser.EndElementHandler = _BindCallable(self._end_element)
167         parser.CharacterDataHandler = _BindCallable(self._char_data)
168         parser.StartNamespaceDeclHandler = _BindCallable(self._start_namespace_decl_handler)
169         parser.EndNamespaceDeclHandler = _BindCallable(self._end_namespace_decl_handler)
170         return parser
171
172 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
173
174 class XMLBuilder(XMLBuilderMixin):
175     """Abstract base class closer to actualizing python object building. 
176     See XMLObjectify, or XMLClassBuilder for more concrete builders."""
177
178     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
179     #~ Special
180     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
181
182     def __call__(self, *args, **kw):
183         """Calls ParseFile if the first argument is an open file, and Parse otherwise."""
184         if isinstance(args[0], file):
185             return self.ParseFile(*args, **kw)
186         else: return self.Parse(*args, **kw)
187
188     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
189     #~ Public Methods
190     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
191
192     def Parse(self, *args, **kw):
193         """Starts the building of python objects using the XML parser.  Assumes first argument is string-like object."""
194         parser= self._PreParse()
195         parser.Parse(*args, **kw)
196         return self._PostParse(parser)
197
198     def ParseFile(self, *args, **kw):
199         """Starts the building of python objects using the XML parser.  Assumes first argument is a file-like object."""
200         parser= self._PreParse()
201         parser.ParseFile(*args, **kw)
202         return self._PostParse(parser)
203
204     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
205     #~ Protected Methods
206     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
207
208     def _end_element(self, name):
209         """Manages the _LastCompleteElement variable."""
210         self._LastCompleteElement = XMLBuilderMixin._end_element(self, name)
211
212     def _PreParse(self):
213         """Few initialization things to be done at the beginning of a parse session."""
214         self._elements, self._LastCompleteElement  = [], None
215         return self._CreateParser()
216
217     def _PostParse(self, parser):
218         """Few cleanup things to be done at the end of a parse session."""
219         result, self._LastCompleteElement = self._LastCompleteElement, None
220         return result
221
Note: See TracBrowser for help on using the browser.