Changeset 129

Show
Ignore:
Timestamp:
04/28/02 14:20:57 (6 years ago)
Author:
sholloway
Message:

Documentation Additions
XMLBuilder rework
Additional Features for XMLObjectify

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/RBFoundation/RBFoundation/XMLBuilder.py

    r115 r129  
    3131##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    3232 
     33"""Abstract base classes for building Python object trees from an XML stream. 
     34 
     35Classes: 
     36 
     37    XMLBuilderObjectBase 
     38    XMLBuilderMixin 
     39    XMLBuilder 
     40 
     41History: 
     42 
     43    Please see XMLObjectify's history. 
     44""" 
     45 
    3346#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    3447#~ Imports                                            
    3548#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    3649 
    37 from xml.parsers.expat import ParserCreate 
    38 from WeakBind import BindCallable 
     50from xml.parsers.expat import ParserCreate as _ParserCreate 
     51from WeakBind import BindCallable as _BindCallable 
    3952 
    4053#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     
    4356 
    4457class XMLBuilderObjectBase(object): 
    45     def __init__(self, owner, parent, namespace, node, attributes): 
    46         pass 
    47          
    48     def _addElement(self, namespace, node, object): 
    49         pass 
    50  
    51     def _addData(self, data): 
    52         pass 
    53  
    54     def _xmlInitStarted(self): 
    55         pass 
    56  
    57     def _xmlInitComplete(self): 
    58         pass 
    59  
    60     def _xmlChildFactory(self, owner, parent, namespace, node, attributes): 
    61         return None 
     58    """Base class for objects created by XMLBuilderMixin derived classes.""" 
     59    def __init__(self, owner, parent, namespace, node, attributes): pass 
     60    def _addElement(self, namespace, node, object): pass 
     61    def _addData(self, data): pass 
     62    def _xmlInitStarted(self): pass  
     63    def _xmlInitComplete(self): pass  
     64    def _xmlChildFactory(self, owner, parent, namespace, node, attributes): return None 
    6265 
    6366#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     67  
     68class XMLBuilderMixin(object):  
     69    """Abstract base class that guides the building of python objects from XML.   
     70    Depends upon the interface defined by XMLBuilderObjectBase.""" 
    6471 
    65 class XMLBuilderMixin(object):  
     72    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     73    #~ Constants / Variables / Etc.  
     74    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     75 
     76    _encoding = 'ASCII' 
    6677    _seperator = '.' 
    67     e_no_class_registered = "No Class Registered" 
     78 
     79    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     80    #~ Special  
     81    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    6882 
    6983    def __init__(self): 
    7084        self._elements = [] 
    7185 
     86    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     87    #~ Protected Methods  
     88    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     89 
    7290    def _GetElementFactory(self, owner, element, namespace, node, attributes): 
    73         raise KeyError, '%s: %s %s' % (self.e_no_class_registered, namespace, node) 
     91        """Allows a derived class to return an instance factory (a class, since python is so awesome) for  
     92        this particular data.  The template method makes no restrictions, except that the instance factory 
     93        must be able to accept the same arguments as this method.""" 
     94        raise KeyError, 'No Class Registered: %s %s' % (namespace, node) 
    7495 
    7596    def _GetOwner(self): 
     97        """Used by _start_element to point to the owner of the node.  Derived classes should 
     98        override this method the owner is different than the XMLBuilder""" 
    7699        return self 
    77100 
    78101    def _start_element(self, name, attributes): 
     102        """Part of the tree-style template method, called at the beginning of an XML node parse. 
     103        Instantiates the element returned by _GetElementFactory.""" 
    79104        idx = name.rfind(self._seperator) 
    80105        if idx < 0:  
     
    84109            namespace = name[0:idx] 
    85110            node = name[idx + len(self._seperator):] 
    86              
    87111 
    88112        args = (self._GetOwner(), self._elements and self._elements[-1] or None, namespace, node, attributes) 
     
    95119 
    96120    def _end_element(self, name): 
     121        """Part of the tree-style template method, called at the closing of an XML node parse. 
     122        Simply notifies the element that it is complete.""" 
    97123        self._elements[-1]._xmlInitComplete() 
    98124        if self._elements: 
     
    101127 
    102128    def _char_data(self, data): 
     129        """Part of the tree-style template method, called when PCData is found.""" 
    103130        self._elements[-1]._addData(data) 
     131 
     132    def _CreateParser(self): 
     133        """Creates the Expat parser in a python-OO way.""" 
     134        parser = _ParserCreate(self._encoding, self._seperator) 
     135        parser.returns_unicode = self._encoding != 'ASCII' and 1 or 0 
     136        parser.StartElementHandler = _BindCallable(self._start_element) 
     137        parser.EndElementHandler = _BindCallable(self._end_element) 
     138        parser.CharacterDataHandler = _BindCallable(self._char_data) 
     139        return parser 
    104140 
    105141#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    106142 
    107143class XMLBuilder(XMLBuilderMixin): 
     144    """Abstract base class closer to actualizing python object building.   
     145    See XMLObjectify, or XMLClassBuilder for more concrete builders.""" 
     146 
     147    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     148    #~ Special  
     149    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     150 
    108151    def __call__(self, *args, **kw): 
     152        """Calls ParseFile if the first argument is an open file, and Parse otherwise.""" 
    109153        if isinstance(args[0], file): 
    110154            return apply(self.ParseFile, args, kw) 
    111         else:  
    112             return apply(self.Parse, args, kw) 
     155        else: return apply(self.Parse, args, kw) 
    113156 
    114     def _end_element(self, name): 
    115         self._LastCompleteElement = XMLBuilderMixin._end_element(self, name) 
    116  
    117     def _PreParse(self): 
    118         self._elements, self._LastCompleteElement  = [], None 
    119         parser = ParserCreate('ASCII', self._seperator) 
    120         parser.returns_unicode = 0 
    121         parser.StartElementHandler = BindCallable(self._start_element) 
    122         parser.EndElementHandler = BindCallable(self._end_element) 
    123         parser.CharacterDataHandler = BindCallable(self._char_data) 
    124         return parser 
    125   
    126     def _PostParse(self, parser): 
    127         result, self._LastCompleteElement = self._LastCompleteElement, None 
    128         return result 
     157    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     158    #~ Public Methods  
     159    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    129160 
    130161    def Parse(self, *args, **kw): 
     162        """Starts the building of python objects using the XML parser.  Assumes first argument is string-like object.""" 
    131163        parser= self._PreParse() 
    132164        apply(parser.Parse, args, kw) 
     
    134166 
    135167    def ParseFile(self, *args, **kw): 
     168        """Starts the building of python objects using the XML parser.  Assumes first argument is a file-like object.""" 
    136169        parser= self._PreParse() 
    137170        apply(parser.ParseFile, args, kw) 
    138171        return self._PostParse(parser) 
    139172 
     173    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     174    #~ Protected Methods  
     175    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     176 
     177    def _end_element(self, name): 
     178        """Manages the _LastCompleteElement variable.""" 
     179        self._LastCompleteElement = XMLBuilderMixin._end_element(self, name) 
     180 
     181    def _PreParse(self): 
     182        """Few initialization things to be done at the beginning of a parse session.""" 
     183        self._elements, self._LastCompleteElement  = [], None 
     184        return self._CreateParser() 
     185 
     186    def _PostParse(self, parser): 
     187        """Few cleanup things to be done at the end of a parse session.""" 
     188        result, self._LastCompleteElement = self._LastCompleteElement, None 
     189        return result 
     190 
  • trunk/RBFoundation/RBFoundation/XMLClassBuilder.py

    r115 r129  
    3131##~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    3232 
     33"""Concrete classes for building Python object trees where each node comes from a potentially different python module dynamically imported from the python path. 
     34 
     35Classes: 
     36 
     37    ModuleByNamespaceMixin 
     38    ModuleByDictionaryMixin 
     39 
     40    XMLClassBuilderMixin 
     41 
     42    XMLClassBuilderByNamespace 
     43    XMLClassBuilderByDictionary 
     44    XMLClassBuilderByBoth  
     45    XMLClassBuilder 
     46 
     47History: 
     48 
     49    Please see XMLObjectify's history. 
     50""" 
     51 
    3352#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    3453#~ Imports                                            
     
    4362 
    4463class ModuleByNamespaceMixin(object): 
     64    """Mixin that forms the python import directly from the namespace and node name.""" 
    4565    def _GetModuleClass(self, namespace, node): 
     66        """Emulates an import path like "from <namespace.node> import <node>" """ 
    4667        # Simple construction to convert an XML namespace and node to a python import 
    4768        return ('%s.%s' % (namespace, node), node) 
    4869 
    49         ## Another plausable import scheme would be.  However, I don't care for the 
    50         ## directory structure that this creates; but, since this is an arbitrary 
    51         ## opinion...  You can do whatever you please.  =) 
    52         ## return (namespace, node
     70        # return (namespace, node) 
     71        ## The previous is another plausable import scheme.  However,  
     72        ## I don't care for the directory structure that this creates; but, since  
     73        ## this is an arbitrary opinion...  You can do whatever you please.  =
    5374 
    5475#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    5576 
    5677class ModuleByDictionaryMixin(object): 
     78    """Mixin that forms the python import indirectly from a dictionary lookup of the namespace,  
     79    and then combines the result with the node name.""" 
     80 
     81    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     82    #~ Constants / Variables / Etc.  
     83    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     84 
    5785    ModuleNamespaces = {} 
    5886 
     87    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     88    #~ Public Methods  
     89    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     90 
    5991    def AddModuleNamespaces(self, dictModuleNamespaces): 
     92        """Registeres a new mapping between xmlns and python import strings.""" 
    6093        # Did they send us a real change? 
    6194        if dictModuleNamespaces: 
     
    67100            self.ModuleNamespaces.update(dictModuleNamespaces) 
    68101  
     102    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     103    #~ Protected Methods  
     104    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     105 
    69106    def _GetModuleClass(self, namespace, node): 
     107        """Emulates an import path like "from <mapping[namespace]> import <node>".   
     108        If this lookup fails, the next method in the super line is asked, or an  
     109        exception is raised.""" 
    70110        try: 
    71111            # Try returning the entry in the dictionary 
     
    74114            try: 
    75115                # Try asking the "next" class in line via super 
    76                 self.__super._GetModuleClass 
     116                Method = super(ModuleByDictionaryMixin, self)._GetModuleClass 
    77117            except AttributeError: 
    78118                # Oops... there didnt seem to be a next class in line 
     
    80120            else: 
    81121                # Ok, there is a next class in line, and their errors are their own 
    82                 return self.__super._GetModuleClass(namespace, node) 
     122                return Method(namespace, node) 
    83123 
    84 ModuleByDictionaryMixin._ModuleByDictionaryMixin__super = super(ModuleByDictionaryMixin) 
    85    
    86124#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    87125 
    88126class XMLClassBuilderMixin(XMLBuilder.XMLBuilder):  
    89     seperator = '.' 
     127    """Base class for building a python object tree from XML where each node comes 
     128    from a potentially different python module dynamically imported from the python path.""" 
     129     
     130    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     131    #~ Constants / Variables / Etc.  
     132    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     133 
    90134    _CachedElementFactories = {} 
    91135 
     136    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     137    #~ Public Methods  
     138    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     139 
     140    Build = XMLBuilder.XMLBuilder.Parse 
     141    BuildFile = XMLBuilder.XMLBuilder.ParseFile 
     142 
     143    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     144    #~ Protected Methods  
     145    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     146 
    92147    def _GetElementFactory(self, owner, parent, namespace, node, attributes): 
     148        """Returns an instance factory.  See XMLBuilderMixin._GetElementFactory for more information.""" 
    93149        result = None 
    94150        if self._elements: 
     
    105161 
    106162    def _LoadModule(self, tupleModuleClass): 
     163        """Attempts to use python's dynamic imports to retrieve an instance factory  
     164        (usually a class) from a python module.""" 
    107165        result = self._CachedElementFactories.get(tupleModuleClass, None) 
    108166        if not result: 
     
    116174 
    117175#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     176#~ Concrete Class Definitions  
     177#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    118178 
    119179class XMLClassBuilderByNamespace(ModuleByNamespaceMixin, XMLClassBuilderMixin):  
    120     Build = XMLClassBuilderMixin.Parse 
    121     BuildFile = XMLClassBuilderMixin.ParseFile 
     180    """Combines dynamic imports from XMLClassBuilderMixin with an import style from ModuleByNamespaceMixin""" 
     181     
     182class XMLClassBuilderByDictionary(ModuleByDictionaryMixin, XMLClassBuilderMixin):  
     183    """Combines dynamic imports from XMLClassBuilderMixin with an import style from ModuleByDictionaryMixin""" 
     184     
     185class XMLClassBuilderByBoth(ModuleByDictionaryMixin, ModuleByNamespaceMixin, XMLClassBuilderMixin):  
     186    """Combines dynamic imports from XMLClassBuilderMixin with an import style from ModuleByDictionaryMixin, 
     187    or from XMLClassBuilderByNamespace if the lookup fails.""" 
     188     
     189XMLClassBuilder = XMLClassBuilderByBoth  
    122190 
    123 class XMLClassBuilderByDictionary(ModuleByDictionaryMixin, XMLClassBuilderMixin):  
    124     Build = XMLClassBuilderMixin.Parse 
    125     BuildFile = XMLClassBuilderMixin.ParseFile 
    126  
    127 class XMLClassBuilderByBoth(ModuleByDictionaryMixin, ModuleByNamespaceMixin, XMLClassBuilderMixin):  
    128     Build = XMLClassBuilderMixin.Parse 
    129     BuildFile = XMLClassBuilderMixin.ParseFile 
    130  
    131 XMLClassBuilder = XMLClassBuilderByBoth 
    132  
  • trunk/RBFoundation/RBFoundation/XMLObjectify.py

    r124 r129  
    3333"""Builds a Python object tree from an XML stream. 
    3434 
    35 History 
    36  
    37     XMLObjectify found its conceptual roots in a similar tool created by David Mertz <mertz@gnosis.cx> 
     35Functions: 
     36 
     37    Objectify 
     38    ObjectifyFile 
     39 
     40Classes: 
     41 
     42    ObjectifiedXML 
     43    Objectifier 
     44 
     45Example: 
     46 
     47    from Foundation.XMLObjectify import Objectify 
     48     
     49    obj = Objectify('''<stream:stream xmlns:stream="Test Stream Namespace"> 
     50            <comment>Some generic xml</comment> 
     51            <comment author="Shane Holloway">Fun with xml!</comment> 
     52        </stream:stream>''') 
     53 
     54    # iteration 
     55    for each in obj.comment: 
     56        print 'Author: %-20s' % getattr(each, 'author', '?unknown?'), 
     57        print each() 
     58 
     59    # direct access 
     60    print obj.comment[1].author 
     61 
     62History: 
     63 
     64    XMLObjectify found its conceptual roots in similar tool created by David Mertz <mertz@gnosis.cx> 
    3865    named objectify.py, which is still extremely useful and can still be found at http://gnosis.cx .   
    3966    Futher history of his module can be found there.  Now I say conceptual roots can be found there,  
    40     because the present state of XMLObjectify represents a 2nd rewrite of that code to serve the new  
    41     requirements.   
     67    because the present state of XMLObjectify represents a 2nd rewrite code, maintaining only the  
     68    founding ideas, in order to serve new requirements.   
    4269 
    4370    One new requirement is the ability to "build" a python object tree by instantiating python classes 
     
    4572    (Raising that exception is one of the essential reasons for the 2nd rewrite, as I did not factor it 
    4673    in the 1st.)  This requirement provides the Foundation of the Skinning framework found in the  
    47     RuneBlade distribution, as well as XMLClassBuilder module 
     74    RuneBlade distribution, as well as XMLClassBuilder module. 
     75 
     76    A second requirement was "the need for speed".  In both Mertz's objectify code and my first rewrite, 
     77    a python object tree can take quite a while to build.  (2.2 seconds for 31 k skin file.)  This,  
     78    in part is due to the DOM model provided with python, and partly to creating new python classes 
     79    on the fly.  This second rewrite notes that fact, and uses a prebuilt class (ObjectifiedXML) with 
     80    different data elements to avoid having to create new classes at run time.  This requirement is the  
     81    corner stone of the RuneBlade Jabber package's XML socket stream. 
     82 
     83    However, note that the first and second requirements are pretty much in direct opposition, as  
     84    dynamic imports will always be slower than using a predefined class that is already in scope.   
     85    A conflict such as this points us in the direction of refactoring the functionality, allowing  
     86    for either speedy building of python object trees, or the great flexability of dynamically building 
     87    that object tree from modules on disk.  Hence, a new (pseudo) hierarchy is formed for this: 
     88         
     89        + XMLBuilder (Abstract) 
     90        | 
     91        +-- XMLObjectify 
     92        | 
     93        +-+ XMLClassBuilder 
     94        | | 
     95        | +-- Skinning.XMLSkinner 
     96        | 
     97        +-- Jabber.Base 
    4898""" 
    4999 
     
    56106 
    57107#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    58 #~ Classes 
     108#~ Classes                                            
    59109#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    60110 
    61111class ObjectifiedXML(XMLBuilder.XMLBuilderObjectBase): 
     112    """Represents an objectified XML node and its attributes with links to subnodes and contained PCData. 
     113 
     114    Example: 
     115     
     116        from Foundation.XMLObjectify import Objectify 
     117         
     118        obj = Objectify('''<stream:stream xmlns:stream="Test Stream Namespace"> 
     119                <comment>Some generic xml</comment> 
     120                <comment author="Shane Holloway"> 
     121                    Fun with xml! 
     122                    Lots of fun! 
     123                </comment> 
     124            </stream:stream>''') 
     125 
     126        assert isinstance(obj, ObjectifiedXML) 
     127 
     128        # Element Access (note: there are two comments in the example XML 
     129        assert isinstance(obj.comment, list) 
     130        assert isinstance(obj.comment[1], ObjectifiedXML) 
     131 
     132        # PCData Access 
     133        assert isinstance(obj.comment[0](''), str) 
     134        assert isinstance(obj.comment[0](None), list) 
     135 
     136        # Attribute Access 
     137        assert isinstance(obj.comment[1].author, str) 
     138         
     139        # Create new element 
     140        obj._addNewElement(None, 'comment', author='William') 
     141        assert obj.comment[2].author == 'William' 
     142 
     143        # Create new data 
     144        obj.comment[2]._addData("Some new data") 
     145 
     146        # Modify / create new attribute 
     147        obj.fun = "with XML!" 
     148 
     149        # Remove an attribute 
     150        del obj.fun 
     151 
     152        # Remove PCData 
     153        obj.comment[1]._removeElement(obj.comment[1](None)[-1]) 
     154 
     155        # Remove All PCData 
     156        obj.comment[0]._clearData() 
     157 
     158        # Remove an element 
     159        obj._removeElement(obj.comment[0]) 
     160 
     161        # Remove multiple elements 
     162        del obj.comments 
     163    """ 
     164 
     165    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     166    #~ Constants / Variables / Etc.                       
     167    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     168 
    62169    _default_attributes = {} 
    63170  
     171    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     172    #~ Special                                            
     173    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     174 
    64175    def __init__(self, owner, parent, namespace, node, attributes): 
    65176        self.__namespace__ = namespace 
     
    70181 
    71182    def __getattribute__(self, name): 
     183        """Allows for the node.attribute or node.subnode semantics""" 
    72184        if '_' != name[0]: 
    73185            xmlName = name.replace('_', '-') 
     
    82194 
    83195    def __setattr__(self, name, value): 
     196        """Allows user to create new attributes, or change values using node.attribute semantics""" 
    84197        if '_' != name[0:1]: 
    85198            xmlName = name.replace('_', '-') 
     
    90203         
    91204    def __delattr__(self, name): 
     205        """Allows for deletion of attributes or subnodes through node.attribute or node.subnode semantics""" 
    92206        if '_' != name[0:1]: 
    93207            xmlName = name.replace('_', '-') 
     
    97211                return 
    98212            else: 
    99                 elements = [x[-1] for x in self._elements if x[0][-1] != xmlName] 
     213                elements = [x for x in self._elements if x[0][-1] != xmlName] 
    100214                if len(elements) != len(self._elements): 
    101215                    self._elements = elements 
     
    106220         
    107221    def __call__(self, joinstr=''): 
     222        """One way to get the PC data out of the node.  See __str__, __int__, __float__, and __complex__. 
     223        The joinstr argument adjusts how the different lines of PCData are joined, and passing None will  
     224        signal that the list should not be joined""" 
    108225        result = [x[-1] for x in self._elements if not x[0][-1]] 
    109226        if joinstr is not None: 
     
    112229     
    113230    def __str__(self): 
     231        """Returns the PCData of the node in str format""" 
    114232        return self('') 
    115233     
    116234    def __int__(self):  
    117         return int(str(self)) 
     235        """Returns the PCData of the node in int format.  Be prepared to catch exceptions in the face of incorrect data.  (Or incorrect assumptions ;) )""" 
     236        return int(self('')) 
    118237 
    119238    def __long__(self):  
    120         return long(str(self))  
     239        """Returns the PCData of the node in long format.  Be prepared to catch exceptions in the face of incorrect data.  (Or incorrect assumptions ;) ) 
     240        Warning:  I think long type is being phased out?  Not quite sure.""" 
     241        return long(self(''))  
    121242 
    122243    def __float__(self):  
    123         return float(str(self))  
     244        """Returns the PCData of the node in float format.  Be prepared to catch exceptions in the face of incorrect data.  (Or incorrect assumptions ;) )""" 
     245        return float(self(''))  
    124246 
    125247    def __complex__(self):  
    126         return complex(str(self))  
     248        """Returns the PCData of the node in complex format.  Be prepared to catch exceptions in the face of incorrect data.  (Or incorrect assumptions ;) )""" 
     249        return complex(self(''))  
    127250 
    128251    def __repr__(self): 
     252        """A slightly different repr""" 
    129253        result = XMLBuilder.XMLBuilderObjectBase.__repr__(self) 
    130254        return '<%s %r %r>' % (result[1:-1], self.__namespace__, self.__node__) 
    131255         
    132     def _addElement(self, namespace, node, object): 
    133         self._elements.append(((namespace, node), object)) 
     256    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     257    #~ Protected Methods  
     258    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     259 
     260    def _addElement(self, namespace, node, obj): 
     261        """Adds a subnode obj, that is in namespace, and has name node.  Obj is not necessarily an ObjectifiedXML class, but is required to implement _toXML.""" 
     262        self._elements.append(((namespace, node), obj)) 
    134263        return self._elements[-1] 
    135264 
    136265    def _addObjectifiedElement(self, obj): 
     266        """Same as _addElement, but assumes object has attributes __namespace__ and __node__.  A little more convenient for adding subnodes in code.""" 
    137267        return self._addElement(obj.__namespace__, obj.__node__, obj) 
    138268 
    139269    def _addNewElement(self, namespace, node, **attributes): 
     270        """Creates and adds a new element in namespace, with name node, having attributes as given.  Uses self.__class__ for creating the element instance.""" 
    140271        namespace = namespace or self.__namespace__ 
    141         return self._addElement(namespace, node, ObjectifiedXML(self, self, namespace, node, attributes)) 
    142          
     272        return self._addElement(namespace, node, self.__class__(self, self, namespace, node, attributes)) 
     273         
     274    def _removeElement(self, element): 
     275        """Removes a subnode element based on != relationship.  (Note: Can be a PCData element)""" 
     276        elements = [x[-1] for x in self._elements if x[-1] != element] 
     277        delta = len(self._elements) - len(elements) 
     278        if delta: self._elements = elements 
     279        return delta 
     280         
     281    def _clearData(self): 
     282        """Removes all PCData from the element node.""" 
     283        elements = [x[-1] for x in self._elements if x[0][-1]] 
     284        if len(elements) != len(self._elements): 
     285            self._elements = elements 
     286 
    143287    def _addData(self, data): 
     288        """Adds PCData to the element node.""" 
    144289        self._elements.append(((self.__namespace__, ''), data)) 
    145290 
     291    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     292 
    146293    def _toXML(self, strSplit='', nsOuter='', bHeaderOnly=0): 
     294        """Converts the python object back into XML.   
     295         
     296            - If strSplit is None, the result is a nested list structure; otherwise, strSplit is used to join those lists into a string.   
     297                (default is '') 
     298             
     299            - If nsOuter matches node.__namespace__, the namespace will not be included in the XML again.   
     300                (default is '') 
     301 
     302            - If bHeaderOnly is true, then only node, namespace, and attributes are included in the XML.  If bHeaderOnly is greater than 1, 
     303                the header tag will also be closed.  If bHeader is false, then the child elements are included. 
     304                (default is 0) 
     305        """ 
    147306        result = ['<%s ' % self.__node__] 
    148307        if nsOuter != self.__namespace__: 
     
    150309        result[-1] += ' '.join(['%s=%s' % (x[0], quoteattr(str(x[1]))) for x in self._attributes.iteritems()]) 
    151310        if bHeaderOnly:  
    152             result[-1] += '>' 
     311            if bHeaderOnly > 1: 
     312                result[-1] += '/>' 
     313            else: result[-1] += '>' 
    153314        elif self._elements: 
    154315            result[-1] += '>' 
     
    163324 
    164325    def _childrenToXML(self, strSplit='', nsOuter=''): 
     326        """Converts child python objects back into XML. 
     327            - If strSplit is None, the result is a nested list structure; otherwise, strSplit is used to join those lists into a string.   
     328                (default is '') 
     329             
     330            - If nsOuter matches subnode.__namespace__, the namespace will not be included in the XML again.   
     331                (default is '') 
     332        """ 
    165333        result = [] 
    166334        for tupleNSNode, each in self._elements: 
     
    177345     
    178346class Objectifier(XMLBuilder.XMLBuilder):  
     347    """An implementation of XMLBuilder that creates ObjectifiedXML nodes from an XML stream. 
     348    See Objectify and ObjectifyFile for usage.""" 
    179349    objectified_class = ObjectifiedXML 
    180350    Objectify = XMLBuilder.XMLBuilder.Parse 
     
    182352 
    183353    def _GetElementFactory(self, owner, parent, namespace, node, attributes): 
     354        """Signals that we always want to create self.objectified_class, which defaults to the ObjectifiedXML class.""" 
    184355        return self.objectified_class 
    185356 
    186357#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    187 #~ Functional Definitions  
    188 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    189  
    190 _defaultObjectifier = Objectifier() 
    191 Objectify = _defaultObjectifier.Objectify 
    192 ObjectifyFile = _defaultObjectifier.ObjectifyFile 
    193  
    194 #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    195 #~ Testing  
     358#~ Functional Definitions                             
     359#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     360 
     361def _defaultObjectifier(): 
     362    """Returns a shared Objectifier, created only on demand.  See Objectify and ObjectifyFile.""" 
     363    result = getattr(_defaultObjectifier, 'Objectifier', None) 
     364    if not result: 
     365        result = _defaultObjectifier.Objectifier = Objectifier() 
     366    return result 
     367 
     368def Objectify(*args, **kw): 
     369    """Simple access to Objectifier.Objectify or Objectifier.ParseFile.   
     370    Warning: Uses a shared object from _defaultObjectifier, and therefore is not threadsafe.""" 
     371    return apply(_defaultObjectifier().Objectify, args, kw) 
     372 
     373def ObjectifyFile(*args, **kw): 
     374    """Simple access to Objectifier.ObjectifyFile or Objectifier.ParseFile.   
     375    Warning: Uses a shared object from _defaultObjectifier, and therefore is not threadsafe.""" 
     376    return apply(_defaultObjectifier().ObjectifyFile, args, kw) 
     377 
     378#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
     379#~ Testing                                            
    196380#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    197381 
    198382def _Test_XMLObjectify(): 
    199383    from pprint import pprint 
    200     xml = open('test_objectify.xml', 'r') 
    201     objectifier = Objectifier() 
    202     obj = objectifier(xml) 
     384    xmlfile = open('test_objectify.xml', 'r') 
     385    obj = ObjectifyFile(xmlfile) 
    203386    print repr(obj) 
    204387    print ' ~ ' * 20 
  • trunk/RBFoundation/RBFoundation/__init__.py

    r68 r129  
    3333"""RuneBlade Foundation Components 
    3434 
    35 Python-Powered:  
    36     Once you meet this snake, all other become dull.   
    37     Plays well with others.  Get bit. 
    38      
    39     Please see http://www.python.org 
    40  
    4135XML Objectify Inspiration: 
    4236    David Mertz at Gnosis software just has a head for Python,  
  • trunk/RBJabber/RBJabber/JabberConnection.py

    r115 r129  
    3535#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    3636 
    37 from Foundation.XMLBuilder import XMLBuilderMixin, XMLBuilderObjectBase, ParserCreate 
     37from Foundation.XMLBuilder import XMLBuilderMixin, XMLBuilderObjectBase 
    3838from Foundation import SmartSelect 
    3939from Foundation.ContextApply import BindCallable 
     
    8282 
    8383        # Create our xml parser 
    84         self._parser = ParserCreate('ASCII', self._seperator) 
    85         self._parser.returns_unicode = 0 
    86         self._parser.StartElementHandler = BindCallable(self._start_element) 
    87         self._parser.EndElementHandler = BindCallable(self._end_element) 
    88         self._parser.CharacterDataHandler = BindCallable(self._char_data) 
     84        self._parser = self._CreateParser() 
    8985 
    9086        # Send the initial header 
  • trunk/RBSkinning/RBSkinning/XMLSkinner.py

    r119 r129  
    3535#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    3636 
    37 from xml.parsers.expat import ParserCreate, ExpatError 
    3837from Foundation.XMLClassBuilder import XMLClassBuilderByDictionary 
    3938from Foundation.WeakBind import BindCallable 
     
    7271    #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    7372 
    74     def _PreParse(self): 
    75         parser = ParserCreate('ASCII', self._seperator) 
    76         parser.returns_unicode = 0 
    77         parser.StartElementHandler = BindCallable(self._start_element) 
    78         parser.EndElementHandler = BindCallable(self._end_element) 
    79         parser.CharacterDataHandler = BindCallable(self._char_data) 
    80         return parser 
    81  
    82     #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
    83  
    8473    def SkinXML(self, xml, contextIn=None, **kwAddedContext): 
    8574        rootPath = self.rootPath or os.getcwd() 
    8675        self._elements, self._LastCompleteElement  = [], None 
    87         parser= self._PreParse() 
     76        parser = self._CreateParser() 
    8877        self.context = SkinContext.SkinContext(contextIn) 
    8978        self.context.__skinner__ = weakref.ref(self) 
     
    10796 
    10897        self._elements, self._LastCompleteElement  = [], None 
    109         parser= self._PreParse() 
     98        parser = self._CreateParser() 
    11099        self.context = SkinContext.SkinContext(contextIn) 
    111100        self.context.__skinner__ = weakref.ref(self) 
     
    123112    def _SkinXMLFromTopElement(self, xml, kwAddedContext): 
    124113        element = self._elements[-1] 
    125         parser= self._PreParse() 
     114        parser = self._CreateParser() 
    126115        element.context.__skinner__ = weakref.ref(self) 
    127116        element.context.__dict__.update(kwAddedContext) 
     
    142131        else: rootPath = '' 
    143132 
    144         parser= self._PreParse() 
     133        parser = self._CreateParser() 
    145134        element.context.__skinner__ = weakref.ref(self) 
    146135        if rootPath: element.context.__root__ = rootPath