source: TI12-security/trunk/WSSecurity/ndg/wssecurity/common/utils/utils.py @ 6392

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/WSSecurity/ndg/wssecurity/common/utils/utils.py@6392
Revision 6392, 43.4 KB checked in by pjkersha, 10 years ago (diff)
Line 
1"""4Suite XML substitution for default DOM parsing and outputting for
2ZSI based services.
3
4This code is included for NDG Security code from pyGridWare package by
5permission of the authors.  See copyright notice below.
6"""
7__author__ = "Joshua R. Boverhof, LBNL"
8__date__ = "04/03/09"
9__copyright__ = (
10"Copyright (c) 2004, The Regents of the University of California, through "
11"Lawrence Berkeley National Laboratory (subject to receipt of any required "
12"approvals from the U.S. Dept. of Energy).  All rights reserved."
13)
14
15__license__ = "BSD - see LICENSE file in top-level directory"
16__contact__ = "Philip.Kershaw@stfc.ac.uk"
17__revision__ = '$Id: $'
18from cStringIO import StringIO
19from string import join, strip, split
20
21from xml.dom import Node
22from Ft.Xml.Domlette import NonvalidatingReaderBase, NonvalidatingReader, \
23    CanonicalPrint
24import Ft.Xml.Domlette
25from Ft.Xml import XPath
26
27from ZSI.wstools.c14n import Canonicalize
28from ZSI.wstools.Namespaces import SCHEMA, SOAP, XMLNS, DSIG
29from ZSI.wstools.Utility import DOMException, SplitQName
30from ZSI.wstools.Utility import NamespaceError, MessageInterface, ElementProxy
31
32
33class DomletteReader(NonvalidatingReaderBase):
34    '''Used with ZSI.parse.ParsedSoap
35    '''
36    fromString = NonvalidatingReaderBase.parseString
37    fromStream = NonvalidatingReaderBase.parseStream
38
39    def __init__(self, *arg, **kw):
40        NonvalidatingReaderBase.__init__(self, *arg, **kw)
41        self._context = None
42        self.processorNss = None
43
44
45class DomletteElementProxy(ElementProxy):
46    expression_dict = {}
47
48    def __init__(self, sw, message=None):
49        '''Initialize.
50           sw -- SoapWriter
51        '''
52        ElementProxy.__init__(self, sw, message)
53        self._dom = DOM
54        self._context = None
55
56    def evaluate(self, expression, processorNss=None):
57        '''expression -- XPath statement or compiled expression.
58        '''
59        if isinstance(expression, basestring):
60            if not self.expression_dict.has_key(expression):
61                self.expression_dict[expression] =  XPath.Compile(expression)
62            expression = self.expression_dict[expression]
63
64        context = self._context
65        if context is None:
66            context = XPath.Context.Context(self.node, processorNss=processorNss or self.processorNss)
67        result = expression.evaluate(context)
68        if type(result) in (list,tuple):
69            #return map(lambda node: DomletteElementProxy(self.sw,node), result)
70            l = []
71            for node in result:
72                item = node
73                if node.nodeType == Node.ELEMENT_NODE:
74                    item = DomletteElementProxy(self.sw,node)
75                #elif node.nodeType == Node.TEXT_NODE:
76                #   item = node.nodeValue
77                # probably dont want to wrap other stuff...
78                l.append(item)
79            result = l
80        else:
81            if node.nodeType == Node.ELEMENT_NODE:
82                result = DomletteElementProxy(self.sw,result)
83
84        return result
85       
86    def isContextInitialized(self, processorNss=None):
87        return self._context is not None
88    def setContext(self, processorNss=None):
89        self._context = XPath.Context.Context(self.node, processorNss=processorNss or self.processorNss)
90
91    #############################################
92    # Methods for checking/setting the
93    # classes (namespaceURI,name) node.
94    #############################################
95    def checkNode(self, namespaceURI=None, localName=None):
96        '''
97            namespaceURI -- namespace of element
98            localName -- local name of element
99        '''
100        namespaceURI = namespaceURI or self.namespaceURI
101        localName = localName or self.name
102        check = False
103        if localName and self.node:
104            check = self._dom.isElement(self.node, localName, namespaceURI)
105        if not check:
106            raise NamespaceError, 'unexpected node type %s, expecting %s' %(self.node, localName)
107
108    def setNode(self, node=None):
109        if node:
110            if isinstance(node, DomletteElementProxy):
111                self.node = node._getNode()
112            else:
113                self.node = node
114        elif self.node:
115            node = self._dom.getElement(self.node, self.name, self.namespaceURI, default=None)
116            if not node:
117                raise NamespaceError, 'cant find element (%s,%s)' %(self.namespaceURI,self.name)
118            self.node = node
119        else:
120            #self.node = self._dom.create(self.node, self.name, self.namespaceURI, default=None)
121            self.createDocument(self.namespaceURI, localName=self.name, doctype=None)
122       
123        self.checkNode()
124
125    #############################################
126    # Wrapper Methods for direct DOM Element Node access
127    #############################################
128    def _getNode(self):
129        return self.node
130
131    def _getElements(self):
132        return self._dom.getElements(self.node, name=None)
133
134    def _getOwnerDocument(self):
135        return self.node.ownerDocument or self.node
136
137    def _getUniquePrefix(self):
138        '''I guess we need to resolve all potential prefixes
139        because when the current node is attached it copies the
140        namespaces into the parent node.
141        '''
142        while 1:
143            self._indx += 1
144            prefix = 'ns%d' %self._indx
145            try:
146                self._dom.findNamespaceURI(prefix, self._getNode())
147            except (AttributeError, DOMException), ex:
148                break
149        return prefix
150
151    def _getPrefix(self, node, nsuri):
152        '''
153        Keyword arguments:
154            node -- DOM Element Node
155            nsuri -- namespace of attribute value
156        '''
157        try:
158            if node and (node.nodeType == node.ELEMENT_NODE) and \
159                (nsuri == self._dom.findDefaultNS(node)):
160                return None
161        except DOMException, ex:
162            pass
163        if nsuri == XMLNS.XML:
164            return self._xml_prefix
165        if node.nodeType == Node.ELEMENT_NODE:
166            for attr in node.attributes.values():
167                if attr.namespaceURI == XMLNS.BASE \
168                   and nsuri == attr.value:
169                        return attr.localName
170            else:
171                if node.parentNode:
172                    return self._getPrefix(node.parentNode, nsuri)
173        raise NamespaceError, 'namespaceURI "%s" is not defined' %nsuri
174
175    def _appendChild(self, node):
176        '''
177        Keyword arguments:
178            node -- DOM Element Node
179        '''
180        if node is None:
181            raise TypeError, 'node is None'
182        self.node.appendChild(node)
183
184    def _insertBefore(self, newChild, refChild):
185        '''
186        Keyword arguments:
187            child -- DOM Element Node to insert
188            refChild -- DOM Element Node
189        '''
190        self.node.insertBefore(newChild, refChild)
191
192    def _setAttributeNS(self, namespaceURI, qualifiedName, value):
193        '''
194        Keyword arguments:
195            namespaceURI -- namespace of attribute
196            qualifiedName -- qualified name of new attribute value
197            value -- value of attribute
198        '''
199        self.node.setAttributeNS(namespaceURI, qualifiedName, value)
200
201    #############################################
202    #General Methods
203    #############################################
204    def isFault(self):
205        '''check to see if this is a soap:fault message.
206        '''
207        return False
208
209    def getPrefix(self, namespaceURI):
210        try:
211            prefix = self._getPrefix(node=self.node, nsuri=namespaceURI)
212        except NamespaceError, ex:
213            prefix = self._getUniquePrefix() 
214            self.setNamespaceAttribute(prefix, namespaceURI)
215        return prefix
216
217    def getDocument(self):
218        return self._getOwnerDocument()
219
220    def setDocument(self, document):
221        self.node = document
222
223    def importFromString(self, xmlString):
224        doc = self._dom.loadDocument(StringIO(xmlString))
225        node = self._dom.getElement(doc, name=None)
226        clone = self.importNode(node)
227        self._appendChild(clone)
228
229    def importNode(self, node):
230        if isinstance(node, DomletteElementProxy):
231            node = node._getNode()
232        return self._dom.importNode(self._getOwnerDocument(), node, deep=1)
233
234    def loadFromString(self, data):
235        self.node = self._dom.loadDocument(StringIO(data))
236
237#    def canonicalize(self, algorithm=DSIG.C14N, unsuppressedPrefixes=[]):
238#        if algorithm == DSIG.C14N_EXCL:
239#            return Canonicalize(self.node, unsuppressedPrefixes=unsuppressedPrefixes)
240#        else:
241#            return Canonicalize(self.node)
242
243    def canonicalize(self, algorithm=DSIG.C14N, unsuppressedPrefixes=[]):
244        '''4Suite-XML based canonicalization'''
245        f = StringIO()
246        if algorithm == DSIG.C14N_EXCL:
247            CanonicalPrint(self.node, stream=f, exclusive=True,
248                           inclusivePrefixes=unsuppressedPrefixes)
249        else:
250            CanonicalPrint(self.node, stream=f)
251
252        c14n = f.getvalue()
253        return c14n
254
255    def toString(self):
256        s = StringIO()
257        FastPrint(self.node, output=s)
258        return s.getvalue()
259
260    def createDocument(self, namespaceURI, localName, doctype=None):
261        prefix = self._soap_env_prefix
262        if namespaceURI == self.reserved_ns[prefix]:
263            qualifiedName = '%s:%s' %(prefix,localName)
264        elif namespaceURI is localName is None:
265            self.node = self._dom.createDocument(None,None,None)
266            return
267        else:
268            raise KeyError, 'only support creation of document in %s' %self.reserved_ns[prefix]
269
270        qualifiedName = '%s:%s' %(prefix,localName)
271        document = self._dom.createDocument(nsuri=namespaceURI, qname=qualifiedName, doctype=doctype)
272        self.node = document.childNodes[0]
273
274        #set up reserved namespace attributes
275        for prefix,nsuri in self.reserved_ns.items():
276            self._setAttributeNS(namespaceURI=self._xmlns_nsuri, 
277                qualifiedName='%s:%s' %(self._xmlns_prefix,prefix), 
278                value=nsuri)
279
280    #############################################
281    #Methods for attributes
282    #############################################
283    def hasAttribute(self, namespaceURI, localName):
284        return self._dom.hasAttr(self._getNode(), name=localName, nsuri=namespaceURI)
285
286    def setAttributeType(self, namespaceURI, localName):
287        '''set xsi:type
288        Keyword arguments:
289            namespaceURI -- namespace of attribute value
290            localName -- name of new attribute value
291
292        '''
293        self.logger.debug('setAttributeType: (%s,%s)', namespaceURI, localName)
294        value = localName
295        if namespaceURI:
296            value = '%s:%s' %(self.getPrefix(namespaceURI),localName)
297        self._setAttributeNS(self._xsi_nsuri, '%s:type' %self._xsi_prefix, value)
298
299    def createAttributeNS(self, namespace, name, value):
300        document = self._getOwnerDocument()
301        attrNode = document.createAttributeNS(namespace, name, value)
302
303    def setAttributeNS(self, namespaceURI, localName, value):
304        '''
305        Keyword arguments:
306            namespaceURI -- namespace of attribute to create, None is for
307                attributes in no namespace.
308            localName -- local name of new attribute
309            value -- value of new attribute
310        ''' 
311        prefix = None
312        if namespaceURI:
313            try:
314                prefix = self.getPrefix(namespaceURI)
315            except KeyError, ex:
316                prefix = 'ns2'
317                self.setNamespaceAttribute(prefix, namespaceURI)
318        qualifiedName = localName
319        if prefix:
320            qualifiedName = '%s:%s' %(prefix, localName)
321        self._setAttributeNS(namespaceURI, qualifiedName, value)
322
323    def setNamespaceAttribute(self, prefix, namespaceURI):
324        '''
325        Keyword arguments:
326            prefix -- xmlns prefix
327            namespaceURI -- value of prefix
328        '''
329        self._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI)
330
331    #############################################
332    #Methods for elements
333    #############################################
334    def createElementNS(self, namespace, qname):
335        '''
336        Keyword arguments:
337            namespace -- namespace of element to create
338            qname -- qualified name of new element
339        '''
340        document = self._getOwnerDocument()
341        node = document.createElementNS(namespace, qname)
342        return DomletteElementProxy(self.sw, node)
343
344    def createAppendSetElement(self, namespaceURI, localName, prefix=None):
345        '''Create a new element (namespaceURI,name), append it
346           to current node, then set it to be the current node.
347        Keyword arguments:
348            namespaceURI -- namespace of element to create
349            localName -- local name of new element
350            prefix -- if namespaceURI is not defined, declare prefix.  defaults
351                to 'ns1' if left unspecified.
352        '''
353        node = self.createAppendElement(namespaceURI, localName, prefix=None)
354        node=node._getNode()
355        self._setNode(node._getNode())
356
357    def createAppendElement(self, namespaceURI, localName, prefix=None):
358        '''Create a new element (namespaceURI,name), append it
359           to current node, and return the newly created node.
360        Keyword arguments:
361            namespaceURI -- namespace of element to create
362            localName -- local name of new element
363            prefix -- if namespaceURI is not defined, declare prefix.  defaults
364                to 'ns1' if left unspecified.
365        '''
366        declare = False
367        qualifiedName = localName
368        if namespaceURI:
369            try:
370                prefix = self.getPrefix(namespaceURI)
371            except:
372                declare = True
373                prefix = prefix or self._getUniquePrefix()
374            if prefix: 
375                qualifiedName = '%s:%s' %(prefix, localName)
376        node = self.createElementNS(namespaceURI, qualifiedName)
377        if declare:
378            node._setAttributeNS(XMLNS.BASE, 'xmlns:%s' %prefix, namespaceURI)
379        self._appendChild(node=node._getNode())
380        return node
381
382    def createInsertBefore(self, namespaceURI, localName, refChild):
383        qualifiedName = localName
384        prefix = self.getPrefix(namespaceURI)
385        if prefix: 
386            qualifiedName = '%s:%s' %(prefix, localName)
387        node = self.createElementNS(namespaceURI, qualifiedName)
388        self._insertBefore(newChild=node._getNode(), refChild=refChild._getNode())
389        return node
390
391    def getElement(self, namespaceURI, localName):
392        '''
393        Keyword arguments:
394            namespaceURI -- namespace of element
395            localName -- local name of element
396        '''
397        node = self._dom.getElement(self.node, localName, namespaceURI, default=None)
398        if node:
399            return DomletteElementProxy(self.sw, node)
400        return None
401
402    def getElements(self, namespaceURI=None, localName=None):
403        '''
404         Keyword arguments:
405            namespaceURI -- namespace of element
406            localName -- local name of element
407        '''
408        nodes = self._dom.getElements(self.node,localName, namespaceURI)
409        nodesList = []
410        if nodes:
411            for node in nodes:
412                nodesList.append(DomletteElementProxy(self.sw, node))
413
414        return nodesList
415                                 
416    def getAttributeValue(self, namespaceURI, localName):
417        '''
418        Keyword arguments:
419            namespaceURI -- namespace of attribute
420            localName -- local name of attribute
421        '''
422        if self.hasAttribute(namespaceURI, localName):
423            attr = self.node.getAttributeNodeNS(namespaceURI,localName)
424            return attr.value
425        return None
426
427    def getValue(self):
428        return self._dom.getElementText(self.node, preserve_ws=True)   
429
430    #############################################
431    #Methods for text nodes
432    #############################################
433    def createAppendTextNode(self, pyobj):
434        node = self.createTextNode(pyobj)
435        self._appendChild(node=node._getNode())
436        return node
437
438    def createTextNode(self, pyobj):
439        document = self._getOwnerDocument()
440        node = document.createTextNode(pyobj)
441        return DomletteElementProxy(self.sw, node)
442
443    #############################################
444    #Methods for retrieving namespaceURI's
445    #############################################
446    def findNamespaceURI(self, qualifiedName):
447        parts = SplitQName(qualifiedName)
448        element = self._getNode()
449        if len(parts) == 1:
450            return (self._dom.findTargetNS(element), value)
451        return self._dom.findNamespaceURI(parts[0], element)
452
453    def resolvePrefix(self, prefix):
454        element = self._getNode()
455        return self._dom.findNamespaceURI(prefix, element)
456
457    def getSOAPEnvURI(self):
458        return self._soap_env_nsuri
459
460    def isEmpty(self):
461        return not self.node
462
463
464class DOM:
465    """The DOM singleton defines a number of XML related constants and
466       provides a number of utility methods for DOM related tasks. It
467       also provides some basic abstractions so that the rest of the
468       package need not care about actual DOM implementation in use."""
469
470    # Namespace stuff related to the SOAP specification.
471
472    NS_SOAP_ENV_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'
473    NS_SOAP_ENC_1_1 = 'http://schemas.xmlsoap.org/soap/encoding/'
474
475    NS_SOAP_ENV_1_2 = 'http://www.w3.org/2001/06/soap-envelope'
476    NS_SOAP_ENC_1_2 = 'http://www.w3.org/2001/06/soap-encoding'
477
478    NS_SOAP_ENV_ALL = (NS_SOAP_ENV_1_1, NS_SOAP_ENV_1_2)
479    NS_SOAP_ENC_ALL = (NS_SOAP_ENC_1_1, NS_SOAP_ENC_1_2)
480
481    NS_SOAP_ENV = NS_SOAP_ENV_1_1
482    NS_SOAP_ENC = NS_SOAP_ENC_1_1
483
484    _soap_uri_mapping = {
485        NS_SOAP_ENV_1_1 : '1.1',
486        NS_SOAP_ENV_1_2 : '1.2',
487    }
488
489    SOAP_ACTOR_NEXT_1_1 = 'http://schemas.xmlsoap.org/soap/actor/next'
490    SOAP_ACTOR_NEXT_1_2 = 'http://www.w3.org/2001/06/soap-envelope/actor/next'
491    SOAP_ACTOR_NEXT_ALL = (SOAP_ACTOR_NEXT_1_1, SOAP_ACTOR_NEXT_1_2)
492   
493    def SOAPUriToVersion(self, uri):
494        """Return the SOAP version related to an envelope uri."""
495        value = self._soap_uri_mapping.get(uri)
496        if value is not None:
497            return value
498        raise ValueError(
499            'Unsupported SOAP envelope uri: %s' % uri
500            )
501
502    def GetSOAPEnvUri(self, version):
503        """Return the appropriate SOAP envelope uri for a given
504           human-friendly SOAP version string (e.g. '1.1')."""
505        attrname = 'NS_SOAP_ENV_%s' % join(split(version, '.'), '_')
506        value = getattr(self, attrname, None)
507        if value is not None:
508            return value
509        raise ValueError(
510            'Unsupported SOAP version: %s' % version
511            )
512
513    def GetSOAPEncUri(self, version):
514        """Return the appropriate SOAP encoding uri for a given
515           human-friendly SOAP version string (e.g. '1.1')."""
516        attrname = 'NS_SOAP_ENC_%s' % join(split(version, '.'), '_')
517        value = getattr(self, attrname, None)
518        if value is not None:
519            return value
520        raise ValueError(
521            'Unsupported SOAP version: %s' % version
522            )
523
524    def GetSOAPActorNextUri(self, version):
525        """Return the right special next-actor uri for a given
526           human-friendly SOAP version string (e.g. '1.1')."""
527        attrname = 'SOAP_ACTOR_NEXT_%s' % join(split(version, '.'), '_')
528        value = getattr(self, attrname, None)
529        if value is not None:
530            return value
531        raise ValueError(
532            'Unsupported SOAP version: %s' % version
533            )
534
535
536    # Namespace stuff related to XML Schema.
537
538    NS_XSD_99 = 'http://www.w3.org/1999/XMLSchema'
539    NS_XSI_99 = 'http://www.w3.org/1999/XMLSchema-instance'   
540
541    NS_XSD_00 = 'http://www.w3.org/2000/10/XMLSchema'
542    NS_XSI_00 = 'http://www.w3.org/2000/10/XMLSchema-instance'   
543
544    NS_XSD_01 = 'http://www.w3.org/2001/XMLSchema'
545    NS_XSI_01 = 'http://www.w3.org/2001/XMLSchema-instance'
546
547    NS_XSD_ALL = (NS_XSD_99, NS_XSD_00, NS_XSD_01)
548    NS_XSI_ALL = (NS_XSI_99, NS_XSI_00, NS_XSI_01)
549
550    NS_XSD = NS_XSD_01
551    NS_XSI = NS_XSI_01
552
553    _xsd_uri_mapping = {
554        NS_XSD_99 : NS_XSI_99,
555        NS_XSD_00 : NS_XSI_00,
556        NS_XSD_01 : NS_XSI_01,
557    }
558
559    for key, value in _xsd_uri_mapping.items():
560        _xsd_uri_mapping[value] = key
561
562
563    def InstanceUriForSchemaUri(self, uri):
564        """Return the appropriate matching XML Schema instance uri for
565           the given XML Schema namespace uri."""
566        return self._xsd_uri_mapping.get(uri)
567
568    def SchemaUriForInstanceUri(self, uri):
569        """Return the appropriate matching XML Schema namespace uri for
570           the given XML Schema instance namespace uri."""
571        return self._xsd_uri_mapping.get(uri)
572
573
574    # Namespace stuff related to WSDL.
575
576    NS_WSDL_1_1 = 'http://schemas.xmlsoap.org/wsdl/'
577    NS_WSDL_ALL = (NS_WSDL_1_1,)
578    NS_WSDL = NS_WSDL_1_1
579
580    NS_SOAP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/'
581    NS_HTTP_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/http/'
582    NS_MIME_BINDING_1_1 = 'http://schemas.xmlsoap.org/wsdl/mime/'
583
584    NS_SOAP_BINDING_ALL = (NS_SOAP_BINDING_1_1,)
585    NS_HTTP_BINDING_ALL = (NS_HTTP_BINDING_1_1,)
586    NS_MIME_BINDING_ALL = (NS_MIME_BINDING_1_1,)
587
588    NS_SOAP_BINDING = NS_SOAP_BINDING_1_1
589    NS_HTTP_BINDING = NS_HTTP_BINDING_1_1
590    NS_MIME_BINDING = NS_MIME_BINDING_1_1
591
592    NS_SOAP_HTTP_1_1 = 'http://schemas.xmlsoap.org/soap/http'
593    NS_SOAP_HTTP_ALL = (NS_SOAP_HTTP_1_1,)
594    NS_SOAP_HTTP = NS_SOAP_HTTP_1_1
595   
596
597    _wsdl_uri_mapping = {
598        NS_WSDL_1_1 : '1.1',
599    }
600   
601    def WSDLUriToVersion(self, uri):
602        """Return the WSDL version related to a WSDL namespace uri."""
603        value = self._wsdl_uri_mapping.get(uri)
604        if value is not None:
605            return value
606        raise ValueError(
607            'Unsupported SOAP envelope uri: %s' % uri
608            )
609
610    def GetWSDLUri(self, version):
611        attr = 'NS_WSDL_%s' % join(split(version, '.'), '_')
612        value = getattr(self, attr, None)
613        if value is not None:
614            return value
615        raise ValueError(
616            'Unsupported WSDL version: %s' % version
617            )
618
619    def GetWSDLSoapBindingUri(self, version):
620        attr = 'NS_SOAP_BINDING_%s' % join(split(version, '.'), '_')
621        value = getattr(self, attr, None)
622        if value is not None:
623            return value
624        raise ValueError(
625            'Unsupported WSDL version: %s' % version
626            )
627
628    def GetWSDLHttpBindingUri(self, version):
629        attr = 'NS_HTTP_BINDING_%s' % join(split(version, '.'), '_')
630        value = getattr(self, attr, None)
631        if value is not None:
632            return value
633        raise ValueError(
634            'Unsupported WSDL version: %s' % version
635            )
636
637    def GetWSDLMimeBindingUri(self, version):
638        attr = 'NS_MIME_BINDING_%s' % join(split(version, '.'), '_')
639        value = getattr(self, attr, None)
640        if value is not None:
641            return value
642        raise ValueError(
643            'Unsupported WSDL version: %s' % version
644            )
645
646    def GetWSDLHttpTransportUri(self, version):
647        attr = 'NS_SOAP_HTTP_%s' % join(split(version, '.'), '_')
648        value = getattr(self, attr, None)
649        if value is not None:
650            return value
651        raise ValueError(
652            'Unsupported WSDL version: %s' % version
653            )
654
655
656    # Other xml namespace constants.
657    NS_XMLNS     = 'http://www.w3.org/2000/xmlns/'
658
659
660
661    def isElement(self, node, name, nsuri=None):
662        """Return true if the given node is an element with the given
663           name and optional namespace uri."""
664        if node.nodeType != node.ELEMENT_NODE:
665            return 0
666        return node.localName == name and \
667               (nsuri is None or self.nsUriMatch(node.namespaceURI, nsuri))
668
669    def getElement(self, node, name, nsuri=None, default=join):
670        """Return the first child of node with a matching name and
671           namespace uri, or the default if one is provided."""
672        nsmatch = self.nsUriMatch
673        ELEMENT_NODE = node.ELEMENT_NODE
674        for child in node.childNodes:
675            if child.nodeType == ELEMENT_NODE:
676                if ((child.localName == name or name is None) and
677                    (nsuri is None or nsmatch(child.namespaceURI, nsuri))
678                    ):
679                    return child
680        if default is not join:
681            return default
682        raise KeyError, name
683
684    def getElementById(self, node, id, default=join):
685        """Return the first child of node matching an id reference."""
686        attrget = self.getAttr
687        ELEMENT_NODE = node.ELEMENT_NODE
688        for child in node.childNodes:
689            if child.nodeType == ELEMENT_NODE:
690                if attrget(child, 'id') == id:
691                    return child
692        if default is not join:
693            return default
694        raise KeyError, name
695
696    def getMappingById(self, document, depth=None, element=None,
697                       mapping=None, level=1):
698        """Create an id -> element mapping of those elements within a
699           document that define an id attribute. The depth of the search
700           may be controlled by using the (1-based) depth argument."""
701        if document is not None:
702            element = document.documentElement
703            mapping = {}
704        attr = element._attrs.get('id', None)
705        if attr is not None:
706            mapping[attr.value] = element
707        if depth is None or depth > level:
708            level = level + 1
709            ELEMENT_NODE = element.ELEMENT_NODE
710            for child in element.childNodes:
711                if child.nodeType == ELEMENT_NODE:
712                    self.getMappingById(None, depth, child, mapping, level)
713        return mapping       
714
715    def getElements(self, node, name, nsuri=None):
716        """Return a sequence of the child elements of the given node that
717           match the given name and optional namespace uri."""
718        nsmatch = self.nsUriMatch
719        result = []
720        ELEMENT_NODE = node.ELEMENT_NODE
721        for child in node.childNodes:
722            if child.nodeType == ELEMENT_NODE:
723                if ((child.localName == name or name is None) and (
724                    (nsuri is None) or nsmatch(child.namespaceURI, nsuri))):
725                    result.append(child)
726        return result
727
728    def hasAttr(self, node, name, nsuri=None):
729        """Return true if element has attribute with the given name and
730           optional nsuri. If nsuri is not specified, returns true if an
731           attribute exists with the given name with any namespace."""
732        return node.hasAttributeNS(nsuri, name)
733
734    def getAttr(self, node, name, nsuri=None, default=join):
735        """Return the value of the attribute named 'name' with the
736           optional nsuri, or the default if one is specified. If
737           nsuri is not specified, an attribute that matches the
738           given name will be returned regardless of namespace."""
739        result = node.getAttributeNS(nsuri,name)
740        if result is not None and result != '':
741            return result
742        if default is not join:
743            return default
744        return ''
745
746    def getAttrs(self, node):
747        """Return a Collection of all attributes
748        """
749        attrs = {}
750        for k,v in node._attrs.items():
751            attrs[k] = v.value
752        return attrs
753
754    def getElementText(self, node, preserve_ws=None):
755        """Return the text value of an xml element node. Leading and trailing
756           whitespace is stripped from the value unless the preserve_ws flag
757           is passed with a true value."""
758        result = []
759        for child in node.childNodes:
760            nodetype = child.nodeType
761            if nodetype == child.TEXT_NODE or \
762               nodetype == child.CDATA_SECTION_NODE:
763                result.append(child.nodeValue)
764        value = join(result, '')
765        if preserve_ws is None:
766            value = strip(value)
767        return value
768
769    def findNamespaceURI(self, prefix, node):
770        """Find a namespace uri given a prefix and a context node."""
771        attrkey = (self.NS_XMLNS, prefix)
772        DOCUMENT_NODE = node.DOCUMENT_NODE
773        ELEMENT_NODE = node.ELEMENT_NODE
774        while 1:
775            if node.nodeType != ELEMENT_NODE:
776                node = node.parentNode
777                continue
778            #result = node._attrsNS.get(attrkey, None)
779            #if result is not None:
780            #    return result.value
781            result = node.getAttributeNS(*attrkey)
782            if result != '':
783                return result
784
785            if hasattr(node, '__imported__'):
786                raise DOMException('Value for prefix %s not found.' % prefix)
787            node = node.parentNode
788            if node.nodeType == DOCUMENT_NODE:
789                raise DOMException('Value for prefix %s not found.' % prefix)
790
791    def findDefaultNS(self, node):
792        """Return the current default namespace uri for the given node."""
793        attrkey = (self.NS_XMLNS, 'xmlns')
794        DOCUMENT_NODE = node.DOCUMENT_NODE
795        ELEMENT_NODE = node.ELEMENT_NODE
796        while 1:
797            if node.nodeType != ELEMENT_NODE:
798                node = node.parentNode
799                continue
800            #result = node._attrsNS.get(attrkey, None)
801            #if result is not None:
802            #    return result.value
803            result = node.getAttributeNS(*attrkey)
804            if result != '':
805                return result
806
807            if hasattr(node, '__imported__'):
808                raise DOMException('Cannot determine default namespace.')
809            node = node.parentNode
810            if node.nodeType == DOCUMENT_NODE:
811                raise DOMException('Cannot determine default namespace.')
812
813    def findTargetNS(self, node):
814        """Return the defined target namespace uri for the given node."""
815        attrget = self.getAttr
816        attrkey = (self.NS_XMLNS, 'xmlns')
817        DOCUMENT_NODE = node.DOCUMENT_NODE
818        ELEMENT_NODE = node.ELEMENT_NODE
819        while 1:
820            if node.nodeType != ELEMENT_NODE:
821                node = node.parentNode
822                continue
823            result = attrget(node, 'targetNamespace', default=None)
824            if result is not None:
825                return result
826            node = node.parentNode
827            if node.nodeType == DOCUMENT_NODE:
828                raise DOMException('Cannot determine target namespace.')
829
830    def getTypeRef(self, element):
831        """Return (namespaceURI, name) for a type attribue of the given
832           element, or None if the element does not have a type attribute."""
833        typeattr = self.getAttr(element, 'type', default=None)
834        if typeattr is None:
835            return None
836        parts = typeattr.split(':', 1)
837        if len(parts) == 2:
838            nsuri = self.findNamespaceURI(parts[0], element)
839        else:
840            nsuri = self.findDefaultNS(element)
841        return (nsuri, parts[1])
842
843    def importNode(self, document, node, deep=0):
844        """Implements (well enough for our purposes) DOM node import."""
845        nodetype = node.nodeType
846        if nodetype in (node.DOCUMENT_NODE, node.DOCUMENT_TYPE_NODE):
847            raise DOMException('Illegal node type for importNode')
848        if nodetype == node.ENTITY_REFERENCE_NODE:
849            deep = 0
850        clone = node.cloneNode(deep)
851        self._setOwnerDoc(document, clone)
852        clone.__imported__ = 1
853        return clone
854
855    def _setOwnerDoc(self, document, node):
856        node.ownerDocument = document
857        for child in node.childNodes:
858            self._setOwnerDoc(document, child)
859
860    def nsUriMatch(self, value, wanted, strict=0, tt=type(())):
861        """Return a true value if two namespace uri values match."""
862        if value == wanted or (type(wanted) is tt) and value in wanted:
863            return 1
864        if not strict:
865            wanted = type(wanted) is tt and wanted or (wanted,)
866            value = value[-1:] != '/' and value or value[:-1]
867            for item in wanted:
868                if item == value or item[:-1] == value:
869                    return 1
870        return 0
871
872    def createDocument(self, nsuri, qname, doctype=None):
873        """Create a new writable DOM document object."""
874        #impl = xml.dom.minidom.getDOMImplementation()
875        impl = Ft.Xml.Domlette.implementation
876        return impl.createDocument(nsuri, qname, doctype)
877
878    def loadDocument(self, data):
879        """Load an xml file from a file-like object and return a DOM
880           document instance."""
881        #return xml.dom.minidom.parse(data)
882        return NonvalidatingReader.parseStream(data)
883
884    def loadFromURL(self, url):
885        """Load an xml file from a URL and return a DOM document."""
886        file = urlopen(url)
887        try:     result = self.loadDocument(file)
888        finally: file.close()
889        return result
890
891    def unlink(self, document):
892        """When you are finished with a DOM, you should clean it up.
893        This is necessary because some versions of Python do not support
894        garbage collection of objects that refer to each other in a cycle.
895        Until this restriction is removed from all versions of Python, it
896        is safest to write your code as if cycles would not be cleaned up."""
897        #if hasattr(document, 'unlink'):
898        #    document.unlink()
899        return
900
901DOM = DOM()
902
903
904"""
905Some code from c14n modified to not do sorting. 
906~30% faster than c14n.Canonicalize
907~12% faster than Ft.Xml.Domlette.Print
908
909    --Ft.Xml.Domlette.Print(self.node, stream=s)
910    This function produces XML with a different canonical form
911    from the source.
912"""
913_attrs = lambda E: (E.attributes and E.attributes.values()) or []
914_children = lambda E: E.childNodes or []
915_IN_XML_NS = lambda n: n.namespaceURI == XMLNS.XML
916_LesserElement, _Element, _GreaterElement = range(3)
917
918def _utilized(n, node, other_attrs, unsuppressedPrefixes):
919    '''_utilized(n, node, other_attrs, unsuppressedPrefixes) -> boolean
920    Return true if that nodespace is utilized within the node'''
921
922    if n.startswith('xmlns:'):
923        n = n[6:]
924    elif n.startswith('xmlns'):
925        n = n[5:]
926    if n == node.prefix or n in unsuppressedPrefixes: return 1
927    for attr in other_attrs:
928        if n == attr.prefix: return 1
929    return 0
930
931_in_subset = lambda subset, node: not subset or node in subset
932
933class _implementation:
934    '''Implementation class for C14N. This accompanies a node during it's
935    processing and includes the parameters and processing state.'''
936
937    # Handler for each node type; populated during module instantiation.
938    handlers = {}
939
940    def __init__(self, node, write, **kw):
941        '''Create and run the implementation.'''
942
943        self.write = write
944        self.subset = kw.get('subset')
945        if self.subset:
946            self.comments = kw.get('comments', 1)
947        else:
948            self.comments = kw.get('comments', 0)
949        self.unsuppressedPrefixes = kw.get('unsuppressedPrefixes')
950        nsdict = kw.get('nsdict', { 'xml': XMLNS.XML, 'xmlns': XMLNS.BASE })
951
952        # Processing state.
953        self.state = (nsdict, ['xml'], [])
954
955        #ATTRIBUTE_NODE
956        #CDATA_SECTION_NODE
957        #COMMENT_NODE
958        #DOCUMENT_FRAGMENT_NODE
959        #DOCUMENT_NODE
960        #DOCUMENT_TYPE_NODE
961        #ELEMENT_NODE
962        #ENTITY_NODE
963        #ENTITY_REFERENCE_NODE
964        #NOTATION_NODE
965        #PROCESSING_INSTRUCTION_NODE
966        #TEXT_NODE
967        #TREE_POSITION_SAME_NODE
968        if node.nodeType == Node.DOCUMENT_NODE:
969            self._do_document(node)
970        elif node.nodeType == Node.ELEMENT_NODE:
971            self.documentOrder = _Element        # At document element
972            if self.unsuppressedPrefixes is not None:
973                self._do_element(node)
974            else:
975                inherited = self._inherit_context(node)
976                self._do_element(node, inherited)
977        elif node.nodeType == Node.DOCUMENT_TYPE_NODE:
978            pass
979        else:
980            raise TypeError, str(node)
981
982
983    def _inherit_context(self, node):
984        '''_inherit_context(self, node) -> list
985        Scan ancestors of attribute and namespace context.  Used only
986        for single element node canonicalization, not for subset
987        canonicalization.'''
988
989        # Collect the initial list of xml:foo attributes.
990        xmlattrs = filter(_IN_XML_NS, _attrs(node))
991
992        # Walk up and get all xml:XXX attributes we inherit.
993        inherited, parent = [], node.parentNode
994        while parent and parent.nodeType == Node.ELEMENT_NODE:
995            for a in filter(_IN_XML_NS, _attrs(parent)):
996                n = a.localName
997                if n not in xmlattrs:
998                    xmlattrs.append(n)
999                    inherited.append(a)
1000            parent = parent.parentNode
1001        return inherited
1002
1003
1004    def _do_document(self, node):
1005        '''_do_document(self, node) -> None
1006        Process a document node. documentOrder holds whether the document
1007        element has been encountered such that PIs/comments can be written
1008        as specified.'''
1009
1010        self.documentOrder = _LesserElement
1011        for child in node.childNodes:
1012            if child.nodeType == Node.ELEMENT_NODE:
1013                self.documentOrder = _Element        # At document element
1014                self._do_element(child)
1015                self.documentOrder = _GreaterElement # After document element
1016            elif child.nodeType == Node.PROCESSING_INSTRUCTION_NODE:
1017                self._do_pi(child)
1018            elif child.nodeType == Node.COMMENT_NODE:
1019                self._do_comment(child)
1020            elif child.nodeType == Node.DOCUMENT_TYPE_NODE:
1021                pass
1022            else:
1023                raise TypeError, str(child)
1024    handlers[Node.DOCUMENT_NODE] = _do_document
1025
1026
1027    def _do_text(self, node):
1028        '''_do_text(self, node) -> None
1029        Process a text or CDATA node.  Render various special characters
1030        as their C14N entity representations.'''
1031        if not _in_subset(self.subset, node): return
1032        s = node.data \
1033                .replace("&", "&") \
1034                .replace("<", "&lt;") \
1035                .replace(">", "&gt;") \
1036                .replace("\015", "&#xD;")
1037        if s: self.write(s)
1038    handlers[Node.TEXT_NODE] = _do_text
1039    handlers[Node.CDATA_SECTION_NODE] = _do_text
1040
1041
1042    def _do_pi(self, node):
1043        '''_do_pi(self, node) -> None
1044        Process a PI node. Render a leading or trailing #xA if the
1045        document order of the PI is greater or lesser (respectively)
1046        than the document element.
1047        '''
1048        if not _in_subset(self.subset, node): return
1049        W = self.write
1050        if self.documentOrder == _GreaterElement: W('\n')
1051        W('<?')
1052        W(node.nodeName)
1053        s = node.data
1054        if s:
1055            W(' ')
1056            W(s)
1057        W('?>')
1058        if self.documentOrder == _LesserElement: W('\n')
1059    handlers[Node.PROCESSING_INSTRUCTION_NODE] = _do_pi
1060
1061
1062    def _do_comment(self, node):
1063        '''_do_comment(self, node) -> None
1064        Process a comment node. Render a leading or trailing #xA if the
1065        document order of the comment is greater or lesser (respectively)
1066        than the document element.
1067        '''
1068        if not _in_subset(self.subset, node): return
1069        if self.comments:
1070            W = self.write
1071            if self.documentOrder == _GreaterElement: W('\n')
1072            W('<!--')
1073            W(node.data)
1074            W('-->')
1075            if self.documentOrder == _LesserElement: W('\n')
1076    handlers[Node.COMMENT_NODE] = _do_comment
1077
1078
1079    def _do_attr(self, n, value):
1080        ''''_do_attr(self, node) -> None
1081        Process an attribute.'''
1082
1083        W = self.write
1084        W(' ')
1085        W(n)
1086        W('="')
1087        s = value \
1088            .replace("&", "&amp;") \
1089            .replace("<", "&lt;") \
1090            .replace('"', '&quot;') \
1091            .replace('\011', '&#x9') \
1092            .replace('\012', '&#xA') \
1093            .replace('\015', '&#xD')
1094        W(s)
1095        W('"')
1096
1097    def _do_element(self, node, initial_other_attrs = []):
1098        '''_do_element(self, node, initial_other_attrs = []) -> None
1099        Process an element (and its children).'''
1100
1101        # Get state (from the stack) make local copies.
1102        #       ns_parent -- NS declarations in parent
1103        #       ns_rendered -- NS nodes rendered by ancestors
1104        #       xml_attrs -- Attributes in XML namespace from parent
1105        #       ns_local -- NS declarations relevant to this element
1106        ns_parent, ns_rendered, xml_attrs = \
1107                self.state[0], self.state[1][:], self.state[2][:]
1108        ns_local = ns_parent.copy()
1109
1110        # Divide attributes into NS, XML, and others.
1111        other_attrs = initial_other_attrs[:]
1112        in_subset = _in_subset(self.subset, node)
1113        for a in _attrs(node):
1114            if a.namespaceURI == XMLNS.BASE:
1115                n = a.nodeName
1116                if n == "xmlns:": n = "xmlns"        # DOM bug workaround
1117                ns_local[n] = a.nodeValue
1118            elif a.namespaceURI == XMLNS.XML:
1119                if self.unsuppressedPrefixes is None or in_subset:
1120                    xml_attrs.append(a)
1121            else:
1122                other_attrs.append(a)
1123
1124        # Render the node
1125        W, name = self.write, None
1126        if in_subset:
1127            name = node.nodeName
1128            W('<')
1129            W(name)
1130
1131            # Create list of NS attributes to render.
1132            ns_to_render = []
1133            for n,v in ns_local.items():
1134                pval = ns_parent.get(n)
1135
1136                # If default namespace is XMLNS.BASE or empty, skip
1137                if n == "xmlns" \
1138                and v in [ XMLNS.BASE, '' ] and pval in [ XMLNS.BASE, '' ]:
1139                    continue
1140
1141                # "omit namespace node with local name xml, which defines
1142                # the xml prefix, if its string value is
1143                # http://www.w3.org/XML/1998/namespace."
1144                if n == "xmlns:xml" \
1145                and v in [ 'http://www.w3.org/XML/1998/namespace' ]:
1146                    continue
1147
1148                # If different from parent, or parent didn't render
1149                # and if not exclusive, or this prefix is needed or
1150                # not suppressed
1151                if (v != pval or n not in ns_rendered) \
1152                  and (self.unsuppressedPrefixes is None or \
1153                  _utilized(n, node, other_attrs, self.unsuppressedPrefixes)):
1154                    ns_to_render.append((n, v))
1155
1156            # Sort and render the ns, marking what was rendered.
1157            #ns_to_render.sort(_sorter_ns)
1158            for n,v in ns_to_render:
1159                self._do_attr(n, v)
1160                ns_rendered.append(n)
1161
1162            # Add in the XML attributes (don't pass to children, since
1163            # we're rendering them), sort, and render.
1164            other_attrs.extend(xml_attrs)
1165            xml_attrs = []
1166            #other_attrs.sort(_sorter)
1167            for a in other_attrs:
1168                self._do_attr(a.nodeName, a.value)
1169            W('>')
1170
1171        # Push state, recurse, pop state.
1172        state, self.state = self.state, (ns_local, ns_rendered, xml_attrs)
1173        for c in _children(node):
1174            _implementation.handlers[c.nodeType](self, c)
1175        self.state = state
1176
1177        if name: W('</%s>' % name)
1178    handlers[Node.ELEMENT_NODE] = _do_element
1179
1180
1181def FastPrint(node, output=None, **kw):
1182    """FastPrint(node, output=None, **kw) -> UTF-8
1183   
1184    Output a DOM document/element node and all descendents.
1185    Return the text; if output is specified then output.write will
1186    be called to output the text and None will be returned
1187    Keyword parameters:
1188        comments: keep comments if non-zero (default is 0)
1189       
1190    """
1191    if output:
1192        _implementation(node, output.write, **kw)
1193    else:
1194        s = StringIO.StringIO()
1195        _implementation(node, s.write, **kw)
1196        return s.getvalue()
1197
1198
1199if __name__ == '__main__': print _copyright
Note: See TracBrowser for help on using the repository browser.