########################################################################
# $Header: /var/local/cvsroot/4Suite/Ft/Rdf/Serializers/Dom.py,v 1.45 2005/03/23 07:08:35 mbrown Exp $
"""
RDF model-to-DOM serialization, DOM-to-RDF model deserialization
where the serialized DOM conforms to RDF/XML syntax (1999 Recommendation)

Copyright 2005 Fourthought, Inc. (USA).
Detailed license and copyright information: http://4suite.org/COPYRIGHT
Project home, documentation, distributions: http://4suite.org/
"""

import cStringIO, posixpath, os
from xml.dom import Node

from Ft.Lib import Set, Uri
from Ft.Rdf import RDF_MS_BASE, RDF_SCHEMA_BASE, DAML_OIL_NS, OLD_DAML_OIL_NS, OWL_NS
from Ft.Rdf import OBJECT_TYPE_RESOURCE, OBJECT_TYPE_LITERAL
from Ft.Rdf import RdfException, ParseException
from Ft.Rdf.Resource import Resource
from Ft.Rdf.Statement import Statement
from Ft.Xml import XML_NAMESPACE, XMLNS_NAMESPACE, EMPTY_NAMESPACE, EMPTY_PREFIX
from Ft.Xml import Domlette
from Ft.Xml.Lib import Language
from Ft.Xml.XPath import Evaluate, Compile, Context, Util

VALID_PARSETYPES = [
    (EMPTY_NAMESPACE, u'Literal'),
    (EMPTY_NAMESPACE, u'Resource'),
    (DAML_OIL_NS, u'collection'),
    (OLD_DAML_OIL_NS, u'collection'),
    (EMPTY_NAMESPACE, u'Collection')
    ]

CONTAINERS = ['Bag', 'Seq', 'Alt']

RDF_SUBJ_PREDICATE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#subject"
RDF_OBJ_PREDICATE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#object"
RDF_PRED_PREDICATE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#predicate"
RDF_TYPE_PREDICATE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"
RDF_VALUE_PREDICATE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#value"

RDF_PREDICATES = [
                  RDF_SUBJ_PREDICATE,
                  RDF_OBJ_PREDICATE,
                  RDF_PRED_PREDICATE,
                  RDF_TYPE_PREDICATE,
                  RDF_VALUE_PREDICATE,
                  ]


class Serializer:
    """Serialize or deserialize a model based on the XML serialization in the RDF Model and Syntax recommendation."""
    def __init__(self, reify=1):
        #self.reify = 0
        return

    def serialize(self, model, nsMap=None, selectUri=None, localResources=[],
                  implementation=None, stmts=None):
        """Construct a DOM representing statements in the model."""
        implementation = implementation or Domlette.implementation
        doc = implementation.createDocument(RDF_MS_BASE, 'rdf:RDF', None)

        nsMap = nsMap or {}
        if RDF_MS_BASE not in nsMap:
            nsMap[RDF_MS_BASE] = 'rdf'

        if stmts is None:
            stmts = model.statements()
            stmts = filter(lambda x: x.uri != RDF_SCHEMA_BASE, stmts)

            if selectUri:
                stmts = filter(lambda x, sel=selectUri: x.uri == sel, stmts)
        else:
            stmts = filter(lambda x: x.uri != RDF_SCHEMA_BASE, stmts)

        inlineResources = filter(lambda x, s=len(RDF_MS_BASE):
                                 x.predicate[:s] == x.object[:s] == RDF_MS_BASE
                                 and x.predicate[s:] == 'type'
                                 and x.object[s:] in CONTAINERS,
                                 stmts)

        inlineResources = map(lambda s: s.subject, inlineResources)

        for resource in inlineResources:
            used = self.exportInlineResource(doc, nsMap, resource, stmts)
            stmts = Set.Not(stmts, used)

        sorted = self.__sortStatementsForDump(stmts)
        for subject in sorted:
            data = sorted[subject]
            id = ''
            if subject in localResources:
                id = subject
                if id[:1] == '#': id = id[1:]
                subject = ''
            self.exportDescription(doc, nsMap, data, subject, id)
        return doc

    def exportInlineResource(self, doc, nsMap, subject, origStmts):
        #Get all statements with this as a subject
        stmts = filter(lambda x, s=subject: x.subject == s, origStmts)
        simpleStatements = {}
        containerStatements = {}
        used = []
        type = "Description"
        for stmt in stmts:
            predNs, predLocal = self.splitUri(stmt.predicate)
            if (predNs, predLocal) == (RDF_MS_BASE, 'type'):
                #This has already been handled
                ns, testType = self.splitUri(stmt.object)
                if testType != 'Bag':
                    type = testType
                used.append(stmt)
            elif predNs == RDF_MS_BASE:
                #Must be a statement reference
                used.append(stmt)
                if predLocal[0] == '_':
                    #See what this points to:
                    nextUsed = filter(lambda x, s=stmt.object:
                                      x.subject == s,
                                      origStmts)
                    nextUsedType = filter(lambda x, n=RDF_MS_BASE+'Statement':
                                          x.object==n,
                                          nextUsed)
                    if len(nextUsedType):
                        #Yep, remove the reification
                        used += nextUsed
                    else:
                        if type == 'Description':
                            type = 'Bag'
                        containerStatements[predLocal[1:]] = (stmt.object, stmt.objectType)
                else:
                    #FIXME: We should serialize this back out as a regular statement
                    pass
            else:
                if stmt.predicate not in simpleStatements:
                    simpleStatements[stmt.predicate] = []
                simpleStatements[stmt.predicate].append((stmt.object, stmt.objectType))
                used.append(stmt)

        if type == 'Description':
            self.exportDescription(doc, nsMap, simpleStatements, id=subject)
        else:
            self.exportContainer(doc, doc.documentElement, nsMap, type,
                                 containerStatements, id=subject)
            if len(simpleStatements):
                self.exportDescription(doc, nsMap, simpleStatements, id=subject)
        return used

    def exportContainer(self, doc, parent, nsMap, type, stmts, id):
        cont = doc.createElementNS(RDF_MS_BASE, u'rdf:%s' % type)
        cont.setAttributeNS(RDF_MS_BASE, u'rdf:ID', id)

        keys = stmts.keys()
        keys.sort()

        for k in keys:
            li = doc.createElementNS(RDF_MS_BASE, u"rdf:li")
            if stmts[k][1] == OBJECT_TYPE_RESOURCE:
                li.setAttributeNS(EMPTY_NAMESPACE, u'resource', stmts[k][0])
            else:
                li.appendChild(doc.createTextNode(unicode(stmts[k][0])))
            cont.appendChild(li)

        parent.appendChild(cont)
        return

    def exportDescription(self, doc, nsMap, stmts, subject='', id=''):
        desc = doc.createElementNS(RDF_MS_BASE, u'rdf:Description')
        if subject:
            desc.setAttributeNS(RDF_MS_BASE, u'rdf:about', subject)
        if id:
            desc.setAttributeNS(RDF_MS_BASE, u'rdf:ID', id)
        doc.documentElement.appendChild(desc)
        self.exportSimpleStatements(doc, desc, nsMap, stmts)
        return

    def exportSimpleStatements(self, doc, parent, nsMap, stmts):
        for pred in stmts:
            scheme, authority, path, query, fragment = Uri.SplitUriRef(pred)
            ns = None
            if fragment is not None:    #fragment
                ns = Uri.UnsplitUriRef((scheme, authority, path, query, None)) + '#'
                if ns in nsMap:
                    prefix = nsMap[ns]
                else:
                    prefix = 'ns' + str(len(nsMap))
                    nsMap[ns] = prefix
                predicateName = prefix and prefix + ':' + fragment or fragment
            elif path and not query:    #tail of path
                p1, p2 = posixpath.split(path)
                if p1:
                    ns = Uri.UnsplitUriRef((scheme, authority, p1 + '/', None, None))
                    if ns in nsMap:
                        prefix = nsMap[ns]
                    else:
                        prefix = 'ns' + str(len(nsMap))
                        nsMap[ns] = prefix
                    predicateName = prefix and prefix + ':' + p2 or p2
                else:
                    prefix = EMPTY_PREFIX
                    predicateName = p2
            else:
                #FIXME: Need to specify the whole URI, then
                predicateName = pred
                pass

            for obj in stmts[pred]:
                predicate = doc.createElementNS(ns, unicode(predicateName))
                parent.appendChild(predicate)
                if obj[1] == OBJECT_TYPE_RESOURCE:
                    predicate.setAttributeNS(RDF_MS_BASE, u'rdf:resource', obj[0])
                else:
                    object = doc.createTextNode(unicode(obj[0]))
                    predicate.appendChild(object)

        return

    def isResource(self, res):
        return Uri.MatchesUriSyntax(res)

    def __sortStatementsForDump(self, statements):
        sorted = {}
        for statement in statements:
            s = statement.subject
            p = statement.predicate
            o = statement.object
            ot = statement.objectType
            if s not in sorted:
                sorted[s] = {}
            if p not in sorted[s]:
                sorted[s][p] = []
            sorted[s][p].append((o, ot))
        return sorted

    #6.1
    def deserialize(self, model, node, scope=None):
        """Generate RDF statements from an XML serialization and insert these into a Model."""
        rdf_nodes = node.xpath('.//rdf:RDF', explicitNss={'rdf': RDF_MS_BASE})
        if not rdf_nodes: rdf_nodes = [node]
        for r in rdf_nodes:
            for child in r.childNodes:
                if child.nodeType != Node.ELEMENT_NODE: continue
                self.readObject(model, child, scope)
        return

    #6.2
    def readObject(self, model, node, scope):
        if node.namespaceURI == RDF_MS_BASE and node.localName in ['Bag', 'Seq', 'Alt']:
            return self.readContainer(model, node, scope), OBJECT_TYPE_RESOURCE
        else:
            return self.readDescription(model, node, scope), OBJECT_TYPE_RESOURCE

    #6.3
    def readDescription(self, model, node, scope):
        try:
            suri =  node.xmlBase is None and scope or node.xmlBase
        except:
            suri = scope

        id, about, about_each, about_each_prefix = self.readIdAboutAttr(scope, node)
        bagId = self.readBagIdAttr(node)
        if bagId is not None:
            raise ParseException(ParseException.FEATURE_NOT_SUPPORTED, 'bagID')
        if about_each_prefix is not None:
            raise ParseException(ParseException.FEATURE_NOT_SUPPORTED, 'aboutEachPrefix')
        propertyAttrs = self.readPropertyAttrs(node)
        descUri = ''
        #reify = 0
        if id is not None:
            descUri = self.uriFromParts(suri, '#', id)
            type_ = self.readTypeAttr(scope, node)
            if type_:
                stmt = Statement(descUri, RDF_MS_BASE + 'type', type_,
                                 scope=scope, objectType=OBJECT_TYPE_RESOURCE)
                model.add(stmt)
            subjects = [descUri]
        elif about is not None:
            subjects = [about]
        elif about_each is not None:
            #To add support, retrieve bag according to URI and add each item to subjects
            raise ParseException(ParseException.FEATURE_NOT_SUPPORTED, 'aboutEach')
        else:
            subjects = [model.generateBnode()]

        returnReference = subjects[0]

        if (node.namespaceURI, node.localName ) != (RDF_MS_BASE, 'Description'):
            #typed node: an implicit "rdf:Description" + rdf:type
            type = self.uriFromParts(suri, node.namespaceURI, node.localName)
            new_stmt = Statement(returnReference, RDF_MS_BASE+'type', type,
                                 scope=scope, objectType=OBJECT_TYPE_RESOURCE)
            model.add(new_stmt)

        try:
            suri =  node.xmlBase is None and scope or node.xmlBase
        except:
            suri = scope

        for (namespaceURI, localName), (value, otype) in propertyAttrs.items():
            for s in subjects:
                predicate = self.uriFromParts(suri, namespaceURI, localName)
                model.add(Statement(s, predicate, value,
                                    scope=scope,
                                    objectType=otype)
                          )

        self.readResource(model, node.childNodes, scope, subjects)
        return returnReference

    #6.3
    def readResource(self, model, nodeList, scope, subjects=None):
        subjects = subjects or [model.generateBnode()]
        #print "readResource", nodeList, subjects
        statements = []
        for child in nodeList:
            if child.nodeType == Node.ELEMENT_NODE:
                statements.extend(self.readPropertyElt(model, child, subjects,
                                                       scope)
                                  )

        #When bagID is supported "statements" should be used to reify added stataments

##        if reify:
##            stCtr = 1
##            for st in statements:
##                uri = st.reify(model)
##                predicate = RDF_MS_BASE + '_%s' % stCtr
##                stCtr = stCtr + 1
##                stmt = Statement(descUri, predicate, uri, scope=scope)
##                model.add(stmt)

        return subjects[0]

    #6.4
    def readContainer(self, model, node, scope):
        id, about, about_each, about_each_prefix = self.readIdAboutAttr(
            scope, node
            )
        memberAttrs = self.readMemberAttrs(node)
        members = [ m for m in node.childNodes
                      if (m.nodeType == Node.ELEMENT_NODE
                          and m.namespaceURI == RDF_MS_BASE
                          and m.localName == 'li') ]

        #Make sure the the proper members are there based on type
        if node.localName == 'Bag':
            #A bag can have member* or memberAttrs*
            if len(memberAttrs) and len(members):
                raise ParseException(ParseException.INVALID_CONTAINER_PROPERTY)
        elif node.localName == 'Seq':
            #A seq can have member* or memberAttrs*
            if len(memberAttrs) and len(members):
                raise ParseException(ParseException.INVALID_CONTAINER_PROPERTY)
        elif node.localName == 'Alt':
            #A alt can have member+ or memberAttrs?
            if len(memberAttrs):
                if len(members):
                    raise ParseException(ParseException.INVALID_CONTAINER_PROPERTY)
                elif len(memberAttrs) > 1:
                    raise Exception("Only one memberAttr allowed in an Alt")
            elif not len(members):
                raise Exception("More than one member required on an Alt")
        else:
            raise Exception(INVALID_CONTAINER_TYPE, node.localName)

        try:
            suri =  node.xmlBase is None and scope or node.xmlBase
        except:
            suri = scope
        #if this doesn't have an id then we need to make one up
        if about:
            id = about
        elif id:
            origId = id
            if not self.isResource(id):
                id = self.uriFromParts(suri, '#', id)
        else:
            id = model.generateBnode()

        stmts = [Statement(id, RDF_MS_BASE+'type', RDF_MS_BASE+node.localName,
                           scope=scope, objectType=OBJECT_TYPE_RESOURCE)]
        for attrId,value in memberAttrs.items():
            pred = RDF_MS_BASE + '_%s' % attrId
            if self.isResource(value):
                stmts.append(Statement(id, pred, value, scope=scope,
                             objectType=OBJECT_TYPE_RESOURCE))
            else:
                stmts.append(Statement(id, pred, value, scope=scope,
                             objectType=OBJECT_TYPE_LITERAL))

        model.add(stmts)

        ctr = 1
        for m in members:
            object = self.readResourceAttr(scope, m)
            obj_type = OBJECT_TYPE_RESOURCE
            children = m.childNodes
            if object and len(children):
                raise Exception("%s:%s can have rdf:resource or a child node, but not both." % (node.namespaceURI, node.localName))
            if not object and not len(children):
                raise Exception("%s:%s must have rdf:resource or a child node" % (node.namespaceURI, node.localName))
            #if len(children) > 0 and len(children) != 1 and children[0].nodeType != Node.TEXT_NODE:
            if not object:
                elems = [ e for e in children if e.nodeType == Node.ELEMENT_NODE ]
                if len(elems) == 1:
                    object, obj_type = self.readObject(model, elems[0], scope)
                elif not elems:
                    chunks = [ c.data for c in children ]
                    object = ''.join(chunks)
                    obj_type = OBJECT_TYPE_LITERAL
                else:
                    raise Exception("%s:%s can only have one child element." % (node.namespaceURI, node.localName))
            predicate = RDF_MS_BASE + '_%d' % ctr
            ctr = ctr + 1
            stmt = Statement(id, predicate, object, scope=scope, objectType=obj_type)
            model.add(stmt)
        return id

    #6.5, 6.6
    def readIdAboutAttr(self, scope, node):
        defaultNsAllowed = node.namespaceURI == RDF_MS_BASE
        id = self.readRdfKeyAttribute(node, 'ID', defaultNsAllowed)
        if id is not None:
            self.validateIdSymbol(id)

        try:
            suri = node.xmlBase is None and scope or node.xmlBase
        except:
            suri = scope
        about = self.readRdfKeyAttribute(node, 'about', defaultNsAllowed)
        if about is not None:
            # For now, we'll use BaseJoin just in case suri is not absolute.
            about = Uri.BaseJoin(suri, about)
            #about = urlparse.urljoin(suri, about)

        about_each = self.readRdfKeyAttribute(node, 'aboutEach', defaultNsAllowed)
        if about_each is not None:
            # For now, we'll use BaseJoin just in case suri is not absolute.
            about_each = Uri.BaseJoin(suri, about_each)
            #about_each = urlparse.urljoin(suri, about_each)

        about_each_prefix = self.readRdfKeyAttribute(node, 'aboutEachPrefix', defaultNsAllowed)
        if about_each_prefix is not None:
            # For now, we'll use BaseJoin just in case suri is not absolute.
            about_each_prefix = Uri.BaseJoin(suri, about_each_prefix)
            #about_each_prefix = urlparse.urljoin(suri, about_each_prefix)

        if (id != None) + \
           (about != None) + \
           (about_each != None)  + \
           (about_each_prefix != None) > 1:
            raise Exception("Only one id, about, aboutEach or aboutEachPrefix allowed on %s:%s" % (node.namespaceURI,node.localName))

        return (id, about, about_each, about_each_prefix)

    #6.6
    def readIdAttr(self, node):
        id = self.readRdfKeyAttribute(node, 'ID')
        if id is not None:
            self.validateIdSymbol(id)
        return id

    #6.9
    def readBagIdAttr(self, node):
        bid = self.readRdfKeyAttribute(node, 'bagID')
        if bid is not None:
            self.validateIdSymbol(bid)
        return bid

    #6.10
    def readPropertyAttrs(self, node):
        #http://www.w3.org/TR/rdf-syntax-grammar/#emptyPropertyElt
        #Says all property attrs besides rdf:type are literals
        props = {}
        for attr in node.attributes.values():
            if attr.namespaceURI == XMLNS_NAMESPACE:
                continue
            if attr.namespaceURI == RDF_MS_BASE:
                if attr.localName == 'type':
                    self.validateUriReference(attr.value)
                    props[(RDF_MS_BASE, "type")] = (attr.value, OBJECT_TYPE_RESOURCE)
                elif attr.localName == 'value':
                    props[(RDF_MS_BASE, "value")] = (attr.value, Language(node) or OBJECT_TYPE_LITERAL)
            elif attr.namespaceURI == XML_NAMESPACE:
                #Section 6 says xml:lang can be ignored as a property attr
                if attr.localName == 'lang':
                    continue
            elif attr.namespaceURI:
                props[(attr.namespaceURI, attr.localName)] = (attr.value, Language(node) or OBJECT_TYPE_LITERAL)
            elif attr.localName not in ['about', 'aboutEach', 'ID', 'BagID',
                                        'resource', 'parseType',
                                        'aboutEachPrefix']:
                props[(attr.namespaceURI, attr.localName)] = (attr.value, Language(node) or OBJECT_TYPE_LITERAL)
        #print "readPropertyAttrs result:", props
        return props

    #6.11
    def readTypeAttr(self, scope, node):
        try:
            suri =  node.xmlBase is None and scope or node.xmlBase
        except:
            suri = scope
        type_ = self.readRdfKeyAttribute(node, 'type')
        if type_ is not None:
            # For now, we'll use BaseJoin just in case suri is not absolute.
            type_ = Uri.BaseJoin(suri, type_)
            #type_ = urlparse.urljoin(suri, type_)
        return type_

    #6.12
    def readPropertyElt(self, model, node, subjects, scope):
        #< propName idAttr? > value </propName>
        #< propName idAttr? parseLiteral > literal </ propName >
        #< propName idAttr? parseResource > propertyElt*  </  propName >
        #< propName idRefAttr? bagIdAttr? propAttr* />

        statements = []
        predicate = None
        object = None
        parseType = self.readParseType(node)

        #The id representing the reification of the statement
        id = self.readIdAttr(node)

        ns, localName = self.readPropName(node)
        #print "propertyElt", subjects, ns, localName

        predicate = self.uriFromParts('', ns, localName)

        #if ns == RDF_MS_BASE and predicate not in RDF_PREDICATES:
        #    raise ParseException(ParseException.INVALID_PREDICATE, predicate)

        force_type = None
        if predicate in [DAML_OIL_NS + "oneOf", OLD_DAML_OIL_NS + "oneOf", OWL_NS + "oneOf"]:
            #daml:oneOf / owl:oneOf are a special sort of property elements, which coerces
            #its members to be of rdf:type of the subject
            force_type = subjects[0]

        if predicate in [OWL_NS + 'unionOf',OWL_NS + 'intersectionOf', OWL_NS + 'complementOf']:
            #owl descriptors (based on set operators) require their members
            #to be owl classes
            force_type = OWL_NS+'Class'

        reify = 0
        #Properties and values expressed in XML attribute form within
        #an empty XML element E by productions [6.10] and [6.12] are
        #equivalent to the same properties and values expressed as XML
        #content of a single Description element D which would become
        #the content of E.
        propertyAttrs = self.readPropertyAttrs(node)
        try:
            suri =  node.xmlBase is None and scope or node.xmlBase
        except:
            suri = scope
        obj_type = OBJECT_TYPE_LITERAL
        if propertyAttrs:
            if node.hasChildNodes():
                raise ParseException(ParseException.NONEMPTY_PROPELT_WITH_PROPATTRS, node.nodeName)
            if id:
                subj = self.uriFromParts(suri, '#', id)
            else:
                subj = self.readResourceAttr(scope, node) or \
                       model.generateBnode()
            for (a_nsuri, a_lname), (a_value, otype) in propertyAttrs.items():
                pred = self.uriFromParts(suri, a_nsuri, a_lname)
                stmt = Statement(subj, pred, a_value, scope=scope,
                                 objectType=otype)
                model.add(stmt)
                statements.append(stmt)
            object = subj
        else:
            object = self.readResourceAttr(scope, node)
            if object:
                obj_type = OBJECT_TYPE_RESOURCE
                if node.hasChildNodes():
                    raise ParseException(ParseException.NONEMPTY_PROPELT_WITH_RESATTR, node.nodeName)
            else:
                object, obj_type = self.readValue(
                    model, node, scope, parseType, predicate, forceType=force_type
                    )
                #try:
                #    object = self.readValue(model, node, scope, parseType, predicate)
                #except ParseException:
                #    raise ParseException(ParseException.MULTIPLE_VALUE_OBJECTS, predicate)
            if id:
                reify = 1

        #Note the difference.  An resource object cannot be
        # an empty string (invalid URI Ref), but a literal object can.
        if object is None:
            return statements

        for s in subjects:
            stmt = Statement(s, predicate, object, scope=scope,
                             objectType=obj_type)
            model.add(stmt)
            statements.append(stmt)
            if reify:
                r_subj = Statement(id, RDF_SUBJ_PREDICATE, s, scope=suri, objectType=OBJECT_TYPE_RESOURCE)
                r_pred = Statement(id, RDF_OBJ_PREDICATE, object, scope=suri, objectType=OBJECT_TYPE_RESOURCE)
                r_obj = Statement(id, RDF_PRED_PREDICATE, predicate, scope=suri, objectType=obj_type)
                model.add([r_subj, r_pred, r_obj])
        return statements

    #6.14
    def readPropName(self,node):
        propName = node.tagName
        return (node.namespaceURI, node.localName)

    #6.17
    def readValue(self, model, node, scope, parseType, predicate, forceType=None):
        # value          ::= obj | string
        #print 'readValue', node, parseType
        obj_type = OBJECT_TYPE_RESOURCE
        if parseType == (EMPTY_NAMESPACE, u'Resource'):
            id, about, about_each, about_each_prefix = self.readIdAboutAttr(
                scope, node
            )
            if id:
                return self.readResource(model, node.childNodes, scope, subjects=[id]), OBJECT_TYPE_RESOURCE
            elif about:
                return self.readResource(model, node.childNodes, scope, subjects=[about]), OBJECT_TYPE_RESOURCE
            else:
                return self.readResource(model, node.childNodes, scope), OBJECT_TYPE_RESOURCE
        elif parseType == (EMPTY_NAMESPACE, u'Literal'):
            stream = cStringIO.StringIO()
            for child in node.childNodes:
                Domlette.Print(child, stream)
            return stream.getvalue(), Language(node) or OBJECT_TYPE_LITERAL
        elif parseType in [(DAML_OIL_NS, 'collection'), (OLD_DAML_OIL_NS, 'collection')]:
            elementNodes = filter(lambda x: x.nodeType == Node.ELEMENT_NODE,
                                  node.childNodes)
            try:
                return self.readDamlCollection(model, elementNodes, scope,
                                               node.xmlBase, forceType), OBJECT_TYPE_RESOURCE
            #FIXME: This exception could catch too much
            except AttributeError:
                return self.readDamlCollection(model, elementNodes, scope,
                                               scope, forceType), OBJECT_TYPE_RESOURCE

        elif parseType == (EMPTY_NAMESPACE, u'Collection'):
            elementNodes = filter(lambda x: x.nodeType == Node.ELEMENT_NODE,
                                  node.childNodes)
            try:
                return self.readCollection(model, elementNodes, scope,
                                               node.xmlBase, forceType), OBJECT_TYPE_RESOURCE
            #FIXME: see above
            except AttributeError:
                return self.readCollection(model, elementNodes, scope,
                                               scope, forceType), OBJECT_TYPE_RESOURCE

        elementNodes = filter(lambda x: x.nodeType == Node.ELEMENT_NODE,
                              node.childNodes)
        if len(elementNodes) == 1:
            # object, possibly typed node
            obj_elem = elementNodes[0]
            if obj_elem.namespaceURI == RDF_MS_BASE and obj_elem.localName in CONTAINERS:
                return self.readContainer(model, obj_elem, scope), OBJECT_TYPE_RESOURCE
            else:
                return self.readDescription(model, obj_elem, scope), OBJECT_TYPE_RESOURCE
        elif elementNodes:
            # error
            raise ParseException(ParseException.MULTIPLE_VALUE_OBJECTS, predicate)
        else:
            # string
            textNodes = filter(lambda x: x.nodeType == Node.TEXT_NODE,
                               node.childNodes)
            if not len(textNodes):
                return None, Language(node) or OBJECT_TYPE_LITERAL
            rt = ''
            for t in textNodes:
                rt = rt + self.readString(t.data)
            return rt, Language(node) or OBJECT_TYPE_LITERAL
    #2.16
    def readCollection(self, model, nodeList, scope, xmlBase,
                               forceType=None):
        if not nodeList:
            return OWL_NS + 'Nothing'
        list = model.generateBnode()
        first, first_obj_type = self.readObject(model, nodeList[0], scope)
        if forceType:
            #Remove redundant forced type statements
            model.removePattern(first,RDF_MS_BASE + 'type',forceType)

            #Set forced type statements
            stmts = [
                Statement(first, RDF_MS_BASE + 'type', forceType,
                          scope=scope, objectType=OBJECT_TYPE_RESOURCE),
            ]
            model.add(stmts)

        rest = self.readCollection(model, nodeList[1:], scope, xmlBase,
                                       forceType)
        stmts = [
            Statement(list, RDF_MS_BASE + 'type', RDF_MS_BASE + 'List',
                      scope=scope, objectType=OBJECT_TYPE_RESOURCE),
            Statement(list, RDF_MS_BASE + 'first', first,
                      scope=scope, objectType=first_obj_type),
            Statement(list, RDF_MS_BASE + 'rest', rest,
                      scope=scope, objectType=OBJECT_TYPE_RESOURCE),
            ]
        model.add(stmts)
        return list

    def readDamlCollection(self, model, nodeList, scope, xmlBase,
                           forceType=None):
        suri =  xmlBase
        if not nodeList:
            return DAML_OIL_NS + 'nil'
        list = model.generateBnode()
        first, first_obj_type = self.readObject(model, nodeList[0], scope)
        if forceType:
            #Remove redundant daml:Thing type stataments
            stmts = [Statement(first, RDF_MS_BASE + 'type', DAML_OIL_NS + 'Thing', scope=suri, objectType=OBJECT_TYPE_RESOURCE),
                     Statement(first, RDF_MS_BASE + 'type', OLD_DAML_OIL_NS + 'Thing', scope=suri, objectType=OBJECT_TYPE_RESOURCE)]
            model.remove(stmts)
            #Handle daml:oneOf special case for enum typing
            stmts = [
                Statement(first, RDF_MS_BASE + 'type', forceType,
                          scope=scope, objectType=OBJECT_TYPE_RESOURCE),
            ]
            model.add(stmts)

        rest = self.readDamlCollection(model, nodeList[1:], scope, xmlBase,
                                       forceType)
        stmts = [
            Statement(list, RDF_MS_BASE + 'type', DAML_OIL_NS + 'List',
                      scope=suri, objectType=OBJECT_TYPE_RESOURCE),
            Statement(list, DAML_OIL_NS + 'first', first,
                      scope=suri, objectType=first_obj_type),
            Statement(list, DAML_OIL_NS + 'rest', rest,
                      scope=scope, objectType=OBJECT_TYPE_RESOURCE),
            ]
        model.add(stmts)
        return list

    #6.18
    def readResourceAttr(self, scope, node):
        #RDF M&S technically doesn't allow the qualified form,
        #But user/implementor consensus allows this under the
        #vague XML NS concept of global attrs
        try:
            suri = node.xmlBase is None and scope or node.xmlBase
        except:
            suri = scope
        resource = self.readRdfKeyAttribute(node, 'resource')
        if resource is not None:
            # For now, we'll use BaseJoin just in case suri is not absolute.
            resource = Uri.BaseJoin(suri, resource)
            #resource = urlparse.urljoin(suri, resource)
        return resource

    def readRdfKeyAttribute(self, node, local, defaultNsAllowed=1):
        """
        Returns None if the attribute is not on the element in null or
        RDF namespace, otherwise returns the attribute value
        Note that RDF M&S technically doesn't allow the qualified form,
        But user/implementor consensu allows this under the
        XML NS concept of global attrs
        """
        val = None
        local = unicode(local)
        if node.hasAttributeNS(RDF_MS_BASE, local):
            val = node.getAttributeNS(RDF_MS_BASE, local)
        elif defaultNsAllowed and node.hasAttributeNS(EMPTY_NAMESPACE, local):
            val = node.getAttributeNS(EMPTY_NAMESPACE, local)
        return val

    #6.24
    def readString(self, st):
        self.validateString(st)
        return st

    #6.31
    def readMemberAttrs(self, node):
        attrs = {}
        for attr in node.attributes.values():
            if attr.namespaceURI == XMLNS_NAMESPACE:
                continue
            if (attr.namespaceURI, attr.localName[0]) == (RDF_MS_BASE, '_'):
                attrs[attr.localName[1:]] = attr.value
        return attrs

    # 6.32, 6.33
    def readParseType(self, node):
        parse_type = None
        if node.hasAttributeNS(EMPTY_NAMESPACE, u'parseType') or node.hasAttributeNS(RDF_MS_BASE, u'parseType'):
            parse_type_attr = node.getAttributeNS(EMPTY_NAMESPACE, u'parseType') or node.getAttributeNS(RDF_MS_BASE, u'parseType')
            parse_type = Util.ExpandQName(parse_type_attr, refNode=node)
            #FIXME: Only necessary until we fix no ns == None everywhere
            #if not parse_type[0]: parse_type[0] = None
            if parse_type not in VALID_PARSETYPES:
                # See paragarph 3 following BNF in section 6
                # for defaulting of parseType
                parse_type = (EMPTY_NAMESPACE, u'Literal')
        return parse_type

    def uriFromParts(self, baseUri, part1, part2):
        """
        From RDF-MS, part 6:

        '...the expansion of the namespace-qualified tag name
        (Generic Identifier) of E. This expansion is generated by
        concatenating the namespace name given in the namespace
        declaration with the LocalPart of the qualified name.'
        ...
        'URI-References are resolved to resource identifiers by first
        resolving the URI-reference to absolute form as specified by
        [RFC 2396] using the base URI of the document in which the RDF
        statements appear.'
        """
        initial = (part1 or "") + (part2 or "")
        # For now, we'll use BaseJoin just in case suri is not absolute.
        resolved = Uri.BaseJoin((baseUri or ''), initial)
        #resolved = urlparse.urljoin((baseUri or ""), initial)
        return resolved

    def splitUri(self, uri):
        """
        If there's a fragment, returns (base+'#', fragment);
        otherwise returns ('', uri)
        """
        index = uri.find('#')
        if index == -1:
            return ('', uri)
        return (uri[:index+1], uri[index+1:])

    def validateIdSymbol(self, idSym):
        #FIXME Validate?
        return 1

    def validateUriReference(self, ref):
        #FIXME Validate?
        return 1

    def validateString(self, st):
        #FIXME Validate?
        return 1

