Metadata-Version: 1.0
Name: z3c.schema2xml
Version: 1.0
Summary: Convert schema-described Zope 3 objects to XML and back
Home-page: UNKNOWN
Author: Martijn Faassen, Jan-Wijbrand Kolman
Author-email: faassen@startifact.com
License: ZPL
Description: Schema To XML
        *************
        
        Introduction
        ============
        
        This package can convert objects described by Zope 3 schema to simple
        XML structures. It's also able to convert this XML back into objects.
        The export and import processes are completely schema-driven; any
        attribute not described in the schema is not seen by this system at
        all.
        
        This system can be used to create export and import systems for Zope 3
        applications. It could also be used to provide XML representations of
        objects for other purposes, such as XSLT transformations, or even just
        to get a full-text representation for index purposes.
        
        The package lies on ``lxml`` for the serialization to XML. 
        
        Serialization
        =============
        
        Let's first define a simple Zope 3 schema::
        
            >>> from zope import interface, schema
            >>> class IName(interface.Interface):
            ...     first_name = schema.TextLine(title=u'First name')
            ...     last_name = schema.TextLine(title=u'Last name')
        
        Let's now make a class that implements this schema::
        
            >>> from zope.interface import implements
            >>> class Name(object):
            ...     implements(IName)
            ...     def __init__(self, first_name, last_name):
            ...         self.first_name = first_name
            ...         self.last_name = last_name
        
        Let's make an instance of the class::
        
            >>> name = Name('Karel', 'Titulaer')
        
        Now let's serialize it to XML:
        
            >>> from z3c.schema2xml import serialize
            >>> print serialize('container', IName, name)
            <container>
              <first_name>Karel</first_name>
              <last_name>Titulaer</last_name>
            </container>
        
        This also works for other kinds of fields::
        
            >>> from zope import interface, schema
            >>> class IAddress(interface.Interface):
            ...     street_name = schema.TextLine(title=u'Street name')
            ...     number = schema.Int(title=u'House number')
            >>> class Address(object):
            ...     implements(IAddress)
            ...     def __init__(self, street_name, number):
            ...         self.street_name = street_name
            ...         self.number = number
            >>> address = Address('Hofplein', 42)
            >>> print serialize('container', IAddress, address)
            <container>
              <street_name>Hofplein</street_name>
              <number>42</number>
            </container>
        
        If a field is not filled in, the serialization will result in an empty
        element::
        
            >>> address2 = Address(None, None)
            >>> print serialize('container', IAddress, address2)
            <container>
              <street_name/>
              <number/>
            </container>
        
        If a schema defines an Object field with its own schema, the serialization
        can also handle this::
        
            >>> class IPerson(interface.Interface):
            ...     name = schema.Object(title=u"Name", schema=IName)
            ...     address = schema.Object(title=u"Address", schema=IAddress)
        
            >>> class Person(object):
            ...     implements(IPerson)
            ...     def __init__(self, name, address):
            ...         self.name = name
            ...         self.address = address
        
            >>> person = Person(name, address)
            >>> print serialize('person', IPerson, person)
            <person>
              <name>
                <first_name>Karel</first_name>
                <last_name>Titulaer</last_name>
              </name>
              <address>
                <street_name>Hofplein</street_name>
                <number>42</number>
              </address>
            </person>
        
        A schema can also define a List field with elements with their own
        schema. Let's make an object and serialize it::
        
            >>> class ICommission(interface.Interface):
            ...     members = schema.List(
            ...         title=u"Commission",
            ...         value_type=schema.Object(__name__='person',
            ...         schema=IPerson))
        
        Note that we have to explicitly specify __name__ for the field that's
        used for value_type here, otherwise we have no name to serialize to
        XML with.
        
            >>> class Commission(object):
            ...     implements(ICommission)
            ...     def __init__(self, members):
            ...         self.members = members
        
            >>> commission = Commission(
            ...     [person, Person(Name('Chriet', 'Titulaer'), Address('Ruimteweg', 3))])
            >>> print serialize('commission', ICommission, commission)
            <commission>
              <members>
                <person>
                  <name>
                    <first_name>Karel</first_name>
                    <last_name>Titulaer</last_name>
                  </name>
                  <address>
                    <street_name>Hofplein</street_name>
                    <number>42</number>
                  </address>
                </person>
                <person>
                  <name>
                    <first_name>Chriet</first_name>
                    <last_name>Titulaer</last_name>
                  </name>
                  <address>
                    <street_name>Ruimteweg</street_name>
                    <number>3</number>
                  </address>
                </person>
              </members>
            </commission>
        
        We get an adapter lookop failure whenever we try to serialize a field type for
        which there's no an serializer::
        
            >>> class IWithNonSerializableField(interface.Interface):
            ...     field = schema.Field(title=u"Commission")
            >>> class NotSerializable(object):
            ...     implements(IWithNonSerializableField)
            ...     def __init__(self, value):
            ...         self.field = value
            >>> not_serializable = NotSerializable(None)
            >>> serialize('noway', IWithNonSerializableField, not_serializable)
            Traceback (most recent call last):
             ...
            TypeError: ('Could not adapt', <zope.schema._bootstrapfields.Field object at ...>, <InterfaceClass z3c.schema2xml._schema2xml.IXMLGenerator>)
        
        Deserialization
        ===============
        
        Now we want to deserialize XML according to a schema to an object that
        provides this schema.
        
            >>> from z3c.schema2xml import deserialize
            >>> xml = '''
            ...  <container>
            ...    <first_name>Karel</first_name>
            ...    <last_name>Titulaer</last_name>
            ...  </container>
            ...  '''
            >>> name = Name('', '')
            >>> deserialize(xml, IName, name)
            >>> name.first_name
            u'Karel'
            >>> name.last_name
            u'Titulaer'
        
        The order of the fields in XML does not matter::
        
            >>> xml = '''
            ...  <container>
            ...    <last_name>Titulaer</last_name>
            ...    <first_name>Karel</first_name>
            ...  </container>
            ...  '''
            >>> name = Name('', '')
            >>> deserialize(xml, IName, name)
            >>> name.first_name
            u'Karel'
            >>> name.last_name
            u'Titulaer'
        
        After deserialization, the object alsoProvides the schema interface::
        
            >>> IName.providedBy(name)
            True
        
        This also works for other kinds of fields::
        
            >>> xml = '''
            ...  <container>
            ...    <street_name>Hofplein</street_name>
            ...    <number>42</number>
            ...  </container>
            ...  '''
            >>> address = Address('', 0)
            >>> deserialize(xml, IAddress, address)
            >>> address.street_name
            u'Hofplein'
            >>> address.number
            42
        
        If a schema defines an Object field with its own schema, the serialization
        can also handle this::
        
            >>> xml = '''
            ...  <person>
            ...    <name>
            ...      <first_name>Karel</first_name>
            ...      <last_name>Titulaer</last_name>
            ...    </name>
            ...    <address>
            ...      <street_name>Hofplein</street_name>
            ...      <number>42</number>
            ...    </address>
            ...  </person>
            ...  '''
            >>> person = Person(Name('', ''), Address('', 0))
            >>> deserialize(xml, IPerson, person)
            >>> person.name.first_name
            u'Karel'
            >>> person.name.last_name
            u'Titulaer'
            >>> person.address.street_name
            u'Hofplein'
            >>> person.address.number
            42
            >>> IPerson.providedBy(person)
            True
            >>> IName.providedBy(person.name)
            True
            >>> IAddress.providedBy(person.address)
            True
        
        Again the order in which the fields come in XML shouldn't matter::
        
            >>> xml = '''
            ...  <person>
            ...    <address>
            ...      <number>42</number>
            ...      <street_name>Hofplein</street_name>
            ...    </address>
            ...    <name>
            ...      <last_name>Titulaer</last_name>
            ...      <first_name>Karel</first_name>
            ...    </name>
            ...  </person>
            ...  '''
            >>> person = Person(Name('', ''), Address('', 0))
            >>> deserialize(xml, IPerson, person)
            >>> person.name.first_name
            u'Karel'
            >>> person.name.last_name
            u'Titulaer'
            >>> person.address.street_name
            u'Hofplein'
            >>> person.address.number
            42
            >>> IPerson.providedBy(person)
            True
            >>> IName.providedBy(person.name)
            True
            >>> IAddress.providedBy(person.address)
            True
        
            >>> xml = '''
            ... <commission>
            ...  <members>
            ...    <person>
            ...      <name>
            ...        <first_name>Karel</first_name>
            ...        <last_name>Titulaer</last_name>
            ...      </name>
            ...      <address>
            ...        <street_name>Hofplein</street_name>
            ...        <number>42</number>
            ...      </address>
            ...    </person>
            ...    <person>
            ...      <name>
            ...        <first_name>Chriet</first_name>
            ...        <last_name>Titulaer</last_name>
            ...     </name>
            ...      <address>
            ...        <street_name>Ruimteweg</street_name>
            ...        <number>3</number>
            ...      </address>
            ...    </person>
            ...  </members>
            ... </commission>
            ... '''
        
            >>> commission = Commission([])
            >>> deserialize(xml, ICommission, commission)
            >>> len(commission.members)
            2
            >>> member = commission.members[0]
            >>> member.name.first_name
            u'Karel'
            >>> member.address.street_name
            u'Hofplein'
            >>> member = commission.members[1]
            >>> member.name.first_name
            u'Chriet'
            >>> member.address.street_name
            u'Ruimteweg'
        
        Whenever the XML element is empty, the resulting value should be None:
        
            >>> from z3c.schema2xml import deserialize
            >>> xml = '''
            ...  <container>
            ...    <first_name></first_name>
            ...    <last_name/>
            ...  </container>
            ...  '''
            >>> name = Name('', '')
            >>> deserialize(xml, IName, name)
            >>> name.first_name is None
            True
            >>> name.last_name is None
            True
        
        For all kinds of fields, like strings and ints...::
        
            >>> xml = '''
            ...  <container>
            ...    <street_name/>
            ...    <number/>
            ...  </container>
            ...  '''
            >>> address = Address('', 0)
            >>> deserialize(xml, IAddress, address)
            >>> address.street_name is None
            True
            >>> address.number is None
            True
        
        ...and the fields of subobjects (but not the subobject themselves!)::
        
            >>> xml = '''
            ...  <person>
            ...    <name>
            ...      <first_name/>
            ...      <last_name/>
            ...    </name>
            ...    <address>
            ...      <street_name/>
            ...      <number/>
            ...    </address>
            ...  </person>
            ...  '''
            >>> person = Person(Name('', ''), Address('', 0))
            >>> deserialize(xml, IPerson, person)
            >>> person.name.first_name is None
            True
            >>> person.name.last_name is None
            True
            >>> IPerson.providedBy(person)
            True
            >>> IName.providedBy(person.name)
            True
            >>> person.address is None
            False
            >>> person.address.street_name is None
            True
            >>> person.address.number is None
            True
            >>> IAddress.providedBy(person.address)
            True
        
        Similarly, where a sequence is expected the value should be an empty sequence:
        
            >>> xml = '''
            ... <commission>
            ...   <members/>
            ... </commission>
            ... '''
            >>> commission = Commission([])
            >>> deserialize(xml, ICommission, commission)
            >>> len(commission.members)
            0
        
        TextLine, Int, Object and List have just been tested. Now follow tests
        for the other field types that have a serializer.
        
        Datetime
        ========
        
        Datetime objects::
        
            >>> from datetime import datetime
            >>> class IWithDatetime(interface.Interface):
            ...     datetime = schema.Datetime(title=u'Date and time')
            >>> class WithDatetime(object):
            ...     implements(IWithDatetime)
            ...     def __init__(self, datetime):
            ...         self.datetime = datetime
            >>> with_datetime = WithDatetime(datetime(2006, 12, 31))
            >>> xml = serialize('container', IWithDatetime, with_datetime)
            >>> print xml
            <container>
              <datetime>2006-12-31T00:00:00</datetime>
            </container>
            >>> new_datetime = WithDatetime(None)
            >>> deserialize(xml, IWithDatetime, new_datetime)
            >>> new_datetime.datetime.year
            2006
            >>> new_datetime.datetime.month
            12
            >>> new_datetime.datetime.day
            31
        
        Let's try it with the field not filled in::
        
            >>> with_datetime = WithDatetime(None)
            >>> xml = serialize('container', IWithDatetime, with_datetime)
            >>> print xml
            <container>
              <datetime/>
            </container>
            >>> new_datetime= WithDatetime(None)
            >>> deserialize(xml, IWithDatetime, new_datetime)
            >>> new_datetime.datetime is None
            True
        
        Choice
        ======
        
        Choice fields. For now, we only work with Choice fields that have 
        text values::
        
        
            >>> from zc.sourcefactory.basic import BasicSourceFactory
            >>> class ChoiceSource(BasicSourceFactory):
            ...     def getValues(self):
            ...         return [u'alpha', u'beta']
            >>> class IWithChoice(interface.Interface):
            ...     choice = schema.Choice(title=u'Choice', required=False,
            ...                            source=ChoiceSource())
            >>> class WithChoice(object):
            ...     implements(IWithChoice)
            ...     def __init__(self, choice):
            ...         self.choice = choice
            >>> with_choice = WithChoice('alpha')
            >>> xml = serialize('container', IWithChoice, with_choice)
            >>> print xml
            <container>
              <choice>alpha</choice>
            </container>
            >>> new_choice = WithChoice(None)
            >>> deserialize(xml, IWithChoice, new_choice)
            >>> new_choice.choice
            'alpha'
            >>> with_choice = WithChoice(None)
            >>> xml = serialize('container', IWithChoice, with_choice)
            >>> print xml
            <container>
              <choice/>
            </container>
            >>> deserialize(xml, IWithChoice, new_choice)
            >>> new_choice.choice is None
            True
        
        Set
        ===
        
        Set fields are very similar to List fields::
        
            >>> class IWithSet(interface.Interface):
            ...     set = schema.Set(title=u'Set', required=False,
            ...                      value_type=schema.Choice(__name__='choice',
            ...                                               source=ChoiceSource()))
            >>> class WithSet(object):
            ...     implements(IWithSet)
            ...     def __init__(self, set):
            ...         self.set = set
            >>> with_set = WithSet(set(['alpha']))
            >>> xml = serialize('container', IWithSet, with_set)
            >>> print xml
            <container>
              <set>
                <choice>alpha</choice>
              </set>
            </container>
            >>> with_set = WithSet(set(['alpha', 'beta']))
            >>> xml = serialize('container', IWithSet, with_set)
            >>> print xml
            <container>
              <set>
                <choice>alpha</choice>
                <choice>beta</choice>
              </set>
            </container>
            >>> new_set = WithSet(None)
            >>> deserialize(xml, IWithSet, new_set)
            >>> new_set.set
            set(['alpha', 'beta'])
        
        CHANGES
        *******
        
        1.0 (2008-12-05)
        ================
        
        * Changed dependency on ``grokcore.component`` so that this becomes
          useful in straight Zope 3 applications as well.
        
        * Run tests against lxml 2.0.9.
        
        0.10 (2008-03-10)
        =================
        
        * First checkin in svn.zope.org.
        
        Download
        ********
        
Platform: UNKNOWN
