xmlbase.py :  » Template-Engines » webstring » webstring-0.5 » webstring » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Template Engines » webstring 
webstring » webstring 0.5 » webstring » xmlbase.py
# Copyright (c) 2006 L. C. Rees.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1.  Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2.  Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3.  Neither the name of the Portable Site Information Project nor the names
# of its contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

'''XML template base.'''

import random as _random
import md5 as _md5
from sys import maxint
from random import randint
# Python 2.5 import
try:
    from xml.etree.ElementTree import Element
# non-Python 2.5 import
except ImportError:
    from elementtree.ElementTree import Element
from webstring.base import _checkname,_Template,_Group,_Field,_exceptions,_Stemplate

__all__ = ['_XMLTemplate', '_XMLGroup', '_XMLOne', '_XMLField', '_copytree']

def _copytree(tree):
    '''Copies an element.'''
    element = tree.makeelement(tree.tag, tree.attrib)
    element.tail, element.text = tree.tail, tree.text
    for child in tree: element.append(_copytree(child))
    return element

def _copyetree(tree, builder):
    '''Copies an element to a different ElementTree implementation.'''
    element = builder(tree.tag, dict(tree.attrib.iteritems()))
    element.tail, element.text = tree.tail, tree.text
    for child in tree: element.append(_copyetree(child, builder))
    return element

_random.seed()


class _XMLMany(object):

    '''Base class for XML Templates with subtemplates.'''

    # XML attribute indicating fields and groups 
    _mark, _groupmark  = 'id', 'class'

    def __delattr__(self, attr):
        '''Delete an attribute.'''
        try:
            # Try removing field from _XMLMany
            try:
                # Delete from internal field dictionary
                obj = self._fielddict.pop(attr)
                # Remove from internal field list
                self._fields.remove(obj) 
                # Remove internal element from parent
                obj._parent.remove(obj._tree)
            except KeyError: pass
        # Always delete object attribute
        finally:
            if hasattr(self, attr): object.__delattr__(self, attr)

    def _addfield(self, child, parent):
        '''Adds a field from an element.'''
        # If element has variable delimiter, make field
        name = _checkname(child.attrib[self._mark])
        # Check if processed already
        if name not in self._filter:
            # Add to filter list if unprocessed
            self._filter.add(name)
            # Add child as field
            self._setfield(name, self._field(child, parent, self._auto, self._max))

    def _delgmark(self):
        '''Removes group delimiter attribute.'''
        for field in self._fields:
            if hasattr(field, 'groupmark'): del field.groupmark

    def _delmark(self):
        '''Removes variable delimiter attribute.'''
        for field in self._fields: del field.mark             
                
    def templates(self, tempdict):
        '''Sets inline text and attribute templates for child fields.

        @param tempdict Dictionary of templates        
        '''        
        if isinstance(tempdict, dict):
            # Make sure there are no more templates than there are fields
            if len(tempdict) <= len(self):
                self._templates = tempdict
                for key, value in tempdict.iteritems():
                    item = self._fielddict[key]
                    # Handle groups
                    if isinstance(item, _XMLGroup):
                        item.templates(value)
                    # Handle fields
                    elif not hasattr(item, 'groupmark'):
                        # Check if text template is in dict
                        try:
                            item.template = value['text']                            
                        except KeyError: pass
                        # Check if attribute template is in dict
                        try:
                            item.atemplates(value['attrib'])
                        except KeyError: pass
            # Raise exception if more templates than fields
            else:
                raise TypeError('template count exceeds field count')
        else:
            raise TypeError('invalid source for templates')   


class _NonRoot(object):

    '''Base class for non-root XML templates.'''

    def __iadd__(self, data):
        '''Inserts an element or another Template's elements after the internal
        element and this Template (self) is returned modified.
        '''
        # Process Templates
        if hasattr(data, 'mark'):
            # Get fresh copy of data (needed for lxml compatibility)
            if data._parent is not None: data = data.current
            # Add to _tempfields
            self._tempfields.append(data)
            # Make Template's internal tree the data
            data = data._tree
        # Process elements
        if hasattr(data, 'tag'):
            # Insert element after internal element + self._siblings length
            self._parent.insert(self._index + len(self._siblings), data)
            # Insert into sibling tracking list
            self._siblings.append(data)
        else:
            raise TypeError(_exceptions[2])
        return self
    
    def _getcurrent(self, call, **kw):
        '''Property that returns the current state of this Field.'''
        newparent, tfield = _copytree(self._parent), list()
        # Sibling elements
        sibs = newparent[self._index:self._index+len(self._siblings)]
        # Copy fields based on any new sibling elements
        for sib in sibs:
            # Try making a field
            try:
                tfield.append(self._field(sib, newparent, self._auto, self.max))
            except KeyError:
                # Try making a group
                try:
                    tfield.append(self._group(sib, newparent, self._auto, self.max))
                except KeyError: pass
        # Internal elements and siblings for new Field come from copy of parent
        return call(newparent[self._index-1], newparent, self._auto, self.max, 
            siblings=sibs, tempfield=tfield, **kw)

    def _getdefault(self, call, **kw):
        '''Property that returns the default state of self.'''
        return call(_copytree(self._btree), None, self._auto, self.max, **kw)

    @property
    def _index(self):
        '''Returns the index under the parent after the internal element.'''
        try:
            return self._idx
        # Store index
        except AttributeError:
            self._idx = self._parent.getchildren().index(self._tree) + 1
            return self._idx      

    def append(self, data):        
        '''Makes an element or another Template's elements children of this
        Template's internal element.

        @param data Template or element
        '''
        # Process elements
        if hasattr(data, 'tag'):
            # Make element child of internal element
            self._tree.append(data)
        # Process Templates
        elif hasattr(data, 'mark'):
            # Make the other Template's children children of internal element
            self._tree.append(data._tree)
            self._tempfields.append(data)
        else:
            raise TypeError(_exceptions[2])

    def render(self, info=None, format='xml', encoding='utf-8'):
        '''Returns a string version the internal element's parent.

        @param info Data to substitute into a document (default: None)
        @param format Format of document (default:'xml')
        @param encoding Encoding type for return string (default: 'utf-8')
        '''
        tostring = self._etree.tostring
        if info is not None: self.__imod__(info)
        # Build up list of strings from internal element and newer siblings
        output = [tostring(self._tree, encoding)]
        output.extend(tostring(f, encoding) for f in self._siblings)
        return ''.join(output)

    def reset(self, **kw):
        '''Returns a Template object to its default state.'''
        # Remove any new siblings        
        for item in self._siblings: self._parent.remove(item)        
        # Save current internal element and its index
        tree, idx = self._tree, self._index
        # Re-initialize self
        self.__init__(self._btree, self._parent, self._auto, self.max, **kw)
        # Insert the new internal element
        self._parent.insert(idx, self._tree)
        # Remove the old internal element
        self._parent.remove(tree)


class _XMLOne(_Field, _NonRoot):

    '''Base class for XML template fields.'''

    # Attribute indicating fields
    _mark = 'id'    
    
    def __init__(self, src, parent=None, auto=True, omax=25, **kw):
        '''Initialization method for fields.
        
        @param src Element source
        @param par Parent element of the source (default: None)
        @param auto Turns automagic on and off (default: True)
        @param omax Maximum number of times a field can repeat (default: 25)
        '''
        super(_XMLOne, self).__init__(auto, omax, **kw)
        # Parent and inline text template
        self._parent, self._template = parent, kw.get('template')
        # Attribute templates
        self._tattrib = kw.get('tattrib', dict())
        if hasattr(src, 'tag'): self._setelement(src)

    def __imod__(self, data):
        '''Substitutes text data into the internal element's text and
        attributes and this field (self) is returned modified.
        '''        
        if isinstance(data, dict):
            # Try popping templates
            try:
                txt = data.pop('template') 
                # Try popping inline text template
                try:
                    self.template = txt.pop('text')
                except KeyError: pass
                # Try popping attribute templates
                try:
                    self.atemplates(txt.pop('attrib'))
                except KeyError: pass
            except KeyError: pass        
            # Try popping attribute content
            try:               
                self.update(data.pop('attrib'))
            except KeyError: pass
        # Run superclass method
        return super(_XMLOne, self).__imod__(data)
        
    def __getitem__(self, key):
        '''Returns the attribute with the given key.'''
        return self._attrib[key]
        
    def __setitem__(self, key, value):
        '''Sets the XML attribute at the given key.'''
        # Set attribute property if automagic on
        if hasattr(self, key):
            setattr(self, key, value)
        else:
            self._setattr(key, value)
        
    def __delitem__(self, key):
        '''Deletes an XML attribute.'''
        # Delete the property if automagic on
        try:
            delattr(self, _checkname(key))
        except AttributeError:
            del self._attrib[key]
        # Remove any template for the attribute
        try:
            self._tattrib.pop(key)
        except (AttributeError, KeyError): pass

    def __contains__(self, key):
        '''Tells if an XML attribute is in a field.'''
        return key in self._attrib        

    def __len__(self):
        '''The number of XML attributes in a field.'''
        return len(self._attrib)    

    def __iter__(self):
        '''An iterator for the internal XML attribute dict.'''
        return iter(self._attrib)

    def _delmark(self):
        '''Removes variable delimiter attribute from output.'''
        self.__delitem__(self.mark)
        for field in self._tempfields: del field.mark    

    def _deltext(self):
        '''Sets internal element's text attribute to None.'''
        self._tree.text = None       
 
    def _setattr(self, key, value):
        '''Sets an attribute of the internal element to a new value.'''
        if isinstance(value, basestring):
            self._tree.set(key, value)
        elif isinstance(value, dict):
            # Try string.Template substitution
            try:
                self._tree.set(key, self._tattrib[key].substitute(value))
            # Try string interpolation
            except AttributeError:
                self._tree.set(key, self._tattrib[key] % value)
        if self._auto:
            name = _checkname(key)
            # Make attribute an attribute of self's superclass if automagic on
            setattr(self.__class__, name, _Attribute(key, name))

    def _setelement(self, tree):
        '''Sets the internal element.'''
        self.__name__ = _checkname(tree.attrib[self.mark])        
        # Set internal element and backup tree
        self._tree, self._btree = tree, _copytree(tree)
        # Make tree attribute dictionary a field attribute
        self._attrib, tattrib = tree.attrib, dict()        
        # If inline text template in tree, assign it to template
        try:
            self.template = tree.text
        except TypeError: pass
        for attr, atext in self._attrib.iteritems():
            # If attribute template text in template source, add to _tattrib
            if '$' in atext or '%' in atext: tattrib[attr] = atext
            # Make XML attrs attributes of self if automagic on & not a mark
            if self._auto and attr != self.mark:
                name = _checkname(attr)
                # Set new class as attribute of self's superclass
                setattr(self.__class__, name, _Attribute(attr, name))
        # Assign any attribute templates        
        if tattrib: self.atemplates(tattrib)

    def _settemplate(self, text):
        '''Sets inline text templates for the internal element.'''        
        if isinstance(text, basestring):
            # Make string.Template instance if delimiter '$' found. 
            if '$' in text:
                self._template = _Stemplate(text)
            # Use standard Python format string if delimiter '%' found
            elif '%' in text:
                self._template = text
            # Raise exception if no delimiter found
            else:
                raise TypeError(_exceptions[8]) 
        else:
            raise TypeError(_exceptions[7])

    def _settext(self, text):
        '''Sets internal element's text attribute.'''
        if isinstance(text, basestring):
            self._tree.text = text
        elif isinstance(text, dict):
            # Try string.Template substitution
            try:
                self._tree.text = self._template.substitute(text)
            # Try string interpolation
            except AttributeError:
                self._tree.text = self._template % text
        else:
            raise TypeError(_exceptions[7])        

    @property
    def current(self):
        '''Property that returns the current state of self.'''
        return self._getcurrent(self._field, template=self._template,
            tattrib=self._tattrib)    

    @property
    def default(self):
        '''Property that returns the default state of self.'''
        return self._getdefault(self._field, template=self._template, 
            tattrib=self._tattrib)           

    def atemplates(self, attr):
        '''Sets templates for the internal element's attributes.'''
        if isinstance(attr, dict):
            tattrib = dict() 
            # Set template for each item in attrib dict
            for key, value in attr.iteritems():
                # Make string.Template instance if delimiter '$' is found 
                if '$' in value:
                    tattrib[key] = _Stemplate(value)
                # Use standard Python format string if delimiter '%' found
                elif '%' in value:
                    tattrib[key] = value
                # Raise exception if no delimiter found
                else:
                    raise TypeError(_exceptions[8])
            # Assign object attribute for attribute template dictionary
            self._tattrib.update(tattrib)
        else:
            raise TypeError(_exceptions[7])

    def purge(self, *attrs):
        '''Removes XML attributes from afield. import 

        @param attrs Tuple of attributes to remove
        '''
        for item in attrs: self.__delitem__(item)    
        
    def reset(self, **kw):
        '''Returns a Template object to its default state.'''
        super(_XMLOne, self).reset(template=self._template, tattrib=self._tattrib)
        
    def update(self, attr):
        '''Sets an internal element's attributes from adictionary. import 

        @param attr Dictionary of attributes
        '''
        if isinstance(attr, dict):
            for key, value in attr.iteritems(): self._setattr(key, value)
        else:
            raise TypeError('invalid attribute source')

    # Template variable delimiter attribute name        
    mark = property(lambda self: self._mark, _Field._setmark, _delmark)
    # Internal element text
    text = property(lambda self: self._tree.text, _settext, _deltext)
    # Internal element text template
    template = property(lambda self: self._template, _settemplate)
    

class _Attribute(object):

    '''Class for manipulating the XML attributes of an internal element.'''    

    __slots__ = '_key', '_name'  

    def __init__(self, key, name):
        '''Initializes an _Attribute object.
        
        @param key Name of _Attribute's attribute
        @param name Name of this _Attribute object
        '''
        self._key, self._name = key, name

    def __repr__(self):
        '''Returns string representation of an XML attribute.'''
        return ''.join(['attribute: ', self._key])

    def __get__(self, inst1, inst2):
        '''Returns value of an XML attribute.'''
        return inst1._attrib[self._key]

    def __set__(self, inst, value):
        '''Sets value of an XML attribute.'''
        inst._setattr(self._key, value)

    def __delete__(self, inst):
        '''Deletes an XML attribute.'''
        # Delete the XML attribute from internal element
        del inst._attrib[self._key]
        # Delete self as attribute of object's superclass
        delattr(inst.__class__, self._name)


class _XMLField(object):

    '''Dispatcher class for XML template fields.''' 
    
    def __new__(cls, *arg, **kw):
        '''Dispatcher method for XML fields.'''        
        c = cls._klass
        c._etree, c._group, c._field = cls._etree, cls._group, cls
        # Make new _XMLOne class if automagic on to avoid attribute property overlap         
        if arg[2]:
            c = type(_md5.new(str(_randint(0, _maxint))).digest(), (c, ),
                dict(c.__dict__))
        return c(*arg, **kw)


class _XMLGroup(_XMLMany, _Group, _NonRoot):

    '''Class for XML group Templates.'''

    def __init__(self, src=None, parent=None, auto=True, omax=25, **kw):
        '''Initialization method for a group Template
        
        @param src Element source (default: None)
        @param parent Parent element of the source (default: None)
        @param auto Turns automagic on and off (default: True)
        @param omax Maximum number of times a field can repeat (default: 25)
        '''
        super(_XMLGroup, self).__init__(auto, omax, **kw)
        self._parent = parent
        if hasattr(src, 'tag'): self._setelement(src)
        self._templates = kw.get('templates')
        if self._templates is not None: self.templates(self._templates)

    @property
    def current(self):
        '''Property that returns the current state of this group.'''
        return self._getcurrent(_XMLGroup, templates=self._templates)

    @property
    def default(self):
        '''Property that returns the default state of this group.'''
        return self._getdefault(_XMLGroup, templates=self._templates) 

    def _delgmark(self):
        '''Removes group mark attribute from output.'''
        del self._tree.attrib[self._groupmark]
        super(_XMLGroup, self)._delgmark()
        # Remove groupmark on any concatentated groups
        for field in self._tempfields:
            if hasattr(field, 'groupmark'): del field.groupmark

    def _delmark(self):
        '''Removes variable mark attribute from output.'''
        super(_XMLGroup, self)._delmark()
        for field in self._tempfields: del field.mark   

    def _setelement(self, tree):
        '''Sets the internal element.'''         
        self.__name__ = _checkname(tree.attrib[self.groupmark])
        # Set internal element and backup tree attributes
        self._tree, self._btree = tree, _copytree(tree)
        # Find fields in group
        for parent in tree.getiterator():
            # Get child, parent pairs
            for child in parent:                
                try:
                    self._addfield(child, parent)
                except KeyError: pass

    def reset(self, **kw):
        '''Resets a group back to its default state.'''
        super(_Group, self).reset(templates=self._templates)                

    # Template variable attribute name
    mark = property(lambda self: self._mark, _Group._setmark, _delmark)
    # Group delimiter attribute name
    groupmark = property(lambda self: self._groupmark, _Group._setgmark, _delgmark)
    

class _XMLTemplate(_XMLMany, _Template):

    '''XML root Template class.'''

    __name__, _parent = 'root', None

    def __init__(self, src=None, auto=True, omax=25, **kw):
        '''
        @param src Path, string, or element source (default: None)
        @param auto Turns automagic on or off (default: True)
        @param omax Max number of times a Template can repeat (default: 25)
        '''
        super(_XMLTemplate, self).__init__(auto, omax, **kw)
        # Process element if source is an element
        if hasattr(src, 'tag'):
            self._setelement(src)
        # Check if source exists
        elif src is not None:
            # Try reading source from a file
            try:
                open(src)
                self.fromfile(src)
            except IOError:
                # Try reading from a string
                try:
                    self.fromstring(src)
                except SyntaxError:
                    raise IOError(_exceptions[1]) 
        # Assign any templates
        self._templates = kw.get('templates')
        if self._templates is not None: self.templates(self._templates)

    def __iadd__(self, data):
        '''Inserts an element or another Template's internal elements after
        the internal element and returns the modified Template (self).

        @param data Template or element
        '''        
        # Process element
        if hasattr(data, 'tag'):
            self._tree.append(data)
        # Process Template
        elif hasattr(data, 'mark'):
            # Make int. element of other Template child of this int. element
            self._tree.append(_copytree(data._tree))
            # Extend Template's internal fields with group Template's fields
            if hasattr(data, 'groupmark'):
                self._fields.extend(data._fields)
            # Append standalone fields to Template's field's
            else:
                self._fields.append(data)           
        else:
            raise TypeError(_exceptions[2])        
        return self

    def __getstate__(self):
        '''Prepares a Template for object serialization.'''
        # Convert internal and backup elements to pure Python element trees
        return dict(tree=_copyetree(self._tree, Element), _mark=self.mark,
            btree=_copyetree(self._btree, Element), _templates=self._templates,
             _groupmark=self.groupmark, _auto=self._auto, _max = self._max)            

    def __setstate__(self, s):
        '''Restores a Template from object serialization.'''
        # Recreate internal trackers
        self._fielddict, self._fields, self._filter = dict(), list(), set()
        # Convert old tree and backup tree to cElementTrees
        tree = _copyetree(s.pop('tree'), self._etree.Element)
        btree = _copyetree(s.pop('btree'), self._etree.Element)
        # Update internal dictionary
        self.__dict__.update(s)
        # Recreate tree
        self._setelement(tree)
        # Reassign backuptree
        self._btree = btree
        # Reprocess templates
        if self._templates is not None: self.templates(self._templates)        

    def _addgroup(self, child, parent):
        '''Creates group Templates.'''
        # Verify element is a group element
        realname = child.attrib[self._groupmark]
        name = _checkname(realname)
        # Check if group already processed
        if name not in self._filter:
            # Mark as processed
            self._filter.add(name)
            # Extract any groups under the group's element
            for element in child:
                try:
                    self._addgroup(element, child)
                except KeyError: pass   
            # Make new group Template w/o passing group's element to __init__
            node = self._group(None, parent, self._auto, self._max)       
            # Add self's filter to avoid duplicate children
            node._filter = self._filter
            # Process group's element
            node._setelement(child)
            # Only attach groups with children to root Template
            if len(node): self._setfield(name, node)

    def _setelement(self, tree):
        '''Sets the internal element.''' 
        # Set internal element and backup element
        self._tree, self._btree = tree, _copytree(tree)
        # Find fields and groups
        for parent in tree.getiterator():
            for child in parent:
                # Try making child a field
                try:                    
                    self._addfield(child, parent)
                except KeyError:
                    # Try making child a group
                    try:
                        self._addgroup(child, parent)
                    except KeyError: pass

    def _setgmark(self, mark):
        '''Sets the groupmark for a group or root.'''
        super(_Template, self)._setgmark(mark)
        # Set mark on all child Groups
        for field in self._fields:
            if hasattr(field, 'groupmark'): field.groupmark = mark                    

    def fromfile(self, path):
        '''Creates an internal element from afilesource. import 

        @param path Path to template source
        '''
        self._setelement(self._etree.parse(path).getroot())

    def fromstring(self, instring):
        '''Creates an internal element from astringsource. import 

        @param instring String source for internal element
        '''
        self._setelement(self._etree.XML(instring))

    def render(self, info=None, format='xml', encoding='utf-8'):
        '''String version of the internal element.

        @param info Data to substitute into an XML document (default: None)
        @param format Format of document (default: 'xml')
        @param encoding Encoding type for return string (default: 'utf-8')
        '''
        if info is not None: self.__imod__(info)
        return self._etree.tostring(self._tree, encoding)

    def reset(self):
        '''Returns a Template object to its default state.'''
        self.__init__(self._btree, self._auto, self._max, templates=self._templates)

    # Template variable attribute name        
    mark = property(lambda self: self._mark, _Template._setmark, _XMLMany._delmark)
    # Group delimiter attribute name
    groupmark = property(lambda self: self._groupmark, _Template._setgmark, _XMLMany._delgmark)
www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.