1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 """Functions that support activities related to the Document Object Model."""
17
18 import pyxb
19 import pyxb.namespace
20 import pyxb.namespace.resolution
21 import pyxb.utils.saxutils
22 import pyxb.utils.saxdom
23 import pyxb.utils.types_
24 import xml.dom
25 import logging
26
27 _log = logging.getLogger(__name__)
28
29
30
31 __DOMImplementation = xml.dom.getDOMImplementation()
34 """Return the DOMImplementation object used for pyxb operations.
35
36 This is primarily used as the default implementation when generating DOM
37 trees from a binding instance. It defaults to whatever
38 xml.dom.getDOMImplementation() returns in your installation (often
39 xml.dom.minidom). It can be overridden with SetDOMImplementation()."""
40
41 global __DOMImplementation
42 return __DOMImplementation
43
49
69
71 """Namespace-aware search for an optional attribute in a node.
72
73 @param attribute_ncname: The local name of the attribute.
74 @type attribute_ncname: C{str} or C{unicode}
75
76 @keyword attribute_ns: The namespace of the attribute. Defaults to None
77 since most attributes are not in a namespace. Can be provided as either a
78 L{pyxb.namespace.Namespace} instance, or a string URI.
79 @type attribute_ns: C{None} or C{str} or C{unicode} or L{pyxb.namespace.Namespace}
80
81 @return: The value of the attribute, or C{None} if the attribute is not
82 present. (Unless C{None}, the value will always be a (unicode) string.)
83 """
84
85 ns_uri = attribute_ns
86 if isinstance(attribute_ns, pyxb.namespace.Namespace):
87 ns_uri = attribute_ns.uri()
88 attr = node.getAttributeNodeNS(ns_uri, attribute_ncname)
89 if attr is None:
90 return None
91 return attr.value
92
94 """Like L{NodeAttribute} but where the content is a QName that must be
95 resolved in the context of the node.
96
97 @param attribute_ncname: as in L{NodeAttribute}
98 @keyword attribute_ns: as in L{NodeAttribute}
99
100 @return: The expanded name to which the value of the attribute resolves
101 given current namespaces, or C{None} if the attribute is not present
102 @rtype: L{pyxb.namespace.ExpandedName}
103 """
104 attr = NodeAttribute(node, attribute_ncname, attribute_ns)
105 if attr is None:
106 return None
107 nsc = pyxb.namespace.resolution.NamespaceContext.GetNodeContext(node)
108 return nsc.interpretQName(attr)
109
111 """Locate a unique child of the DOM node.
112
113 This function returns the sole child of node which is an ELEMENT_NODE
114 instance and has a tag consistent with the given tag. If multiple nodes
115 with a matching C{tag} are found, or C{absent_ok} is C{False} and no
116 matching tag is found, an exception is raised.
117
118 @param node: An a xml.dom.Node ELEMENT_NODE instance
119 @param tag: the NCName of an element in the namespace
120 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
121 can be found. If C{False}, an exception is raised if no match can be
122 found.
123 @keyword namespace: The namespace to which the child element belongs.
124 Default is the XMLSchema namespace.
125 @rtype: C{xml.dom.Node}
126
127 @raise pyxb.SchemaValidationError: multiple elements are identified
128 @raise pyxb.SchemaValidationError: C{absent_ok} is C{False} and no element is identified.
129 """
130 candidate = None
131 for cn in node.childNodes:
132 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
133 if candidate:
134 raise pyxb.SchemaValidationError('Multiple %s elements nested in %s' % (tag, node.nodeName))
135 candidate = cn
136 if (candidate is None) and not absent_ok:
137 raise pyxb.SchemaValidationError('Expected %s elements nested in %s' % (tag, node.nodeName))
138 return candidate
139
141 """Locate all children of the DOM node that have a particular tag.
142
143 This function returns a list of children of node which are ELEMENT_NODE
144 instances and have a tag consistent with the given tag.
145
146 @param node: An a xml.dom.Node ELEMENT_NODE instance.
147 @param tag: the NCName of an element in the namespace, which defaults to the
148 XMLSchema namespace.
149 @keyword namespace: The namespace to which the child element belongs.
150 Default is the XMLSchema namespace.
151
152 @rtype: C{list(xml.dom.Node)}
153 """
154 matches = []
155 for cn in node.childNodes:
156 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
157 matches.append(cn)
158 return matches
159
161 """Locate the first element child of the node.
162
163
164 @param node: An a xml.dom.Node ELEMENT_NODE instance.
165 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
166 can be found. If C{False}, an exception is raised if no match can be
167 found.
168 @keyword require_unique: If C{False} (default), it is acceptable for there
169 to be multiple child elements. If C{True}, presence of multiple child
170 elements raises an exception.
171 @keyword ignore_annotations: If C{True} (default), annotations are skipped
172 wheen looking for the first child element. If C{False}, an annotation
173 counts as an element.
174 @rtype: C{xml.dom.Node}
175
176 @raise SchemaValidationError: C{absent_ok} is C{False} and no child
177 element was identified.
178 @raise SchemaValidationError: C{require_unique} is C{True} and multiple
179 child elements were identified
180 """
181
182 candidate = None
183 for cn in node.childNodes:
184 if xml.dom.Node.ELEMENT_NODE == cn.nodeType:
185 if ignore_annotations and pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation'):
186 continue
187 if require_unique:
188 if candidate:
189 raise pyxb.SchemaValidationError('Multiple elements nested in %s' % (node.nodeName,))
190 candidate = cn
191 else:
192 return cn
193 if (candidate is None) and not absent_ok:
194 raise pyxb.SchemaValidationError('No elements nested in %s' % (node.nodeName,))
195 return candidate
196
198 """Return True iff C{node} has an ELEMENT_NODE child that is not an
199 XMLSchema annotation node.
200
201 @rtype: C{bool}
202 """
203 for cn in node.childNodes:
204 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and (not pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation')):
205 return True
206 return False
207
209 """Walk all the children, extracting all text content and
210 catenating it into the return value.
211
212 Returns C{None} if no text content (including whitespace) is found.
213
214 This is mainly used to strip comments out of the content of complex
215 elements with simple types.
216
217 @rtype: C{unicode} or C{str}
218 """
219 text = []
220 for cn in node.childNodes:
221 if xml.dom.Node.TEXT_NODE == cn.nodeType:
222 text.append(cn.data)
223 elif xml.dom.Node.CDATA_SECTION_NODE == cn.nodeType:
224 text.append(cn.data)
225 elif xml.dom.Node.COMMENT_NODE == cn.nodeType:
226 pass
227 else:
228 raise pyxb.NonElementValidationError(cn)
229 if 0 == len(text):
230 return None
231 return ''.join(text)
232
234 """Class holding information relevant to generating the namespace aspects
235 of a DOM instance."""
236
237 __namespaces = None
238
239
240 __namespacePrefixCounter = None
241
243 """The registered default namespace.
244
245 @rtype: L{pyxb.namespace.Namespace}
246 """
247 return self.__defaultNamespace
248 __defaultNamespace = None
249
251 """Set the default namespace for the generated document.
252
253 Even if invoked post construction, the default namespace will affect
254 the entire document, as all namespace declarations are placed in the
255 document root.
256
257 @param default_namespace: The namespace to be defined as the default
258 namespace in the top-level element of the document. May be provided
259 as a real namespace, or just its URI.
260 @type default_namespace: L{pyxb.namespace.Namespace} or C{str} or
261 C{unicode}.
262 """
263
264 if isinstance(default_namespace, basestring):
265 default_namespace = pyxb.namespace.NamespaceForURI(default_namespace, create_if_missing=True)
266 if (default_namespace is not None) and default_namespace.isAbsentNamespace():
267 raise pyxb.UsageError('Default namespace must not be an absent namespace')
268 self.__defaultNamespace = default_namespace
269
271 """Return a map from Namespace instances to the prefix by which they
272 are represented in the DOM document."""
273 return self.__namespacePrefixMap.copy()
274 __namespacePrefixMap = None
275
317
319 """Return the prefix to be used for the given namespace.
320
321 This will L{declare <declareNamespace>} the namespace if it has not
322 yet been observed.
323
324 @param namespace: The namespace for which a prefix is needed. If the
325 provided namespace is C{None} or an absent namespace, the C{None}
326 value will be returned as the corresponding prefix.
327
328 @keyword enable_default_namespace: Normally if the namespace is the default
329 namespace C{None} is returned to indicate this. If this keyword is
330 C{False} then we need a namespace prefix even if this is the default.
331 """
332
333 if (namespace is None) or namespace.isAbsentNamespace():
334 return None
335 if isinstance(namespace, basestring):
336 namespace = pyxb.namespace.NamespaceForURI(namespace, create_if_missing=True)
337 if (self.__defaultNamespace == namespace) and enable_default_namespace:
338 return None
339 ns = self.__namespaces.get(namespace)
340 if ns is None:
341 ns = self.declareNamespace(namespace)
342 return ns
343
345 """Return the set of Namespace instances known to this instance."""
346 return self.__namespaces
347
348
349
352
353 - def reset (self, prefix_map=False):
363
364 - def __init__ (self, default_namespace=None, namespace_prefix_map=None, inherit_from=None):
365 """Create a new namespace declaration configuration.
366
367 @keyword default_namespace: Optional L{pyxb.namespace.Namespace}
368 instance that serves as the default namespace (applies to unqualified
369 names).
370
371 @keyword namespace_prefix_map: Optional map from
372 L{pyxb.namespace.Namespace} instances to C{str} values that are to be
373 used as the corresponding namespace prefix when constructing
374 U{qualified names<http://www.w3.org/TR/1999/REC-xml-names-19990114/#dt-qname>}.
375
376 @keyword inherit_from: Optional instance of this class from which
377 defaults are inherited. Inheritance is overridden by values of other
378 keywords in the initializer.
379 """
380 self.__prefixes = set()
381 self.__namespacePrefixCounter = 0
382 self.__namespaces = { }
383 self.__defaultNamespace = None
384 self.__resetNamespacePrefixMap()
385 if inherit_from is not None:
386 if default_namespace is None:
387 default_namespace = inherit_from.defaultNamespace()
388 self.__namespacePrefixMap.update(inherit_from.__namespacePrefixMap)
389 self.__namespacePrefixCount = inherit_from.__namespacePrefixCounter
390 self.__namespaces.update(inherit_from.__namespaces)
391 self.__prefixes.update(inherit_from.__prefixes)
392 if default_namespace is not None:
393 self.setDefaultNamespace(default_namespace)
394 prefixes = set(self.__namespacePrefixMap.itervalues())
395 prefixes.update(self.__prefixes)
396 if namespace_prefix_map is not None:
397 prefixes = set()
398 for (ns, pfx) in namespace_prefix_map.iteritems():
399 ns = pyxb.namespace.NamespaceInstance(ns)
400 if pfx in prefixes:
401 raise pyxb.LogicError('Cannot assign same prefix to multiple namespacess: %s' % (pfx,))
402 prefixes.add(pfx)
403 self.__namespacePrefixMap[ns] = pfx
404
406 """This holds DOM-related information used when generating a DOM tree from
407 a binding instance."""
408
410 """The DOMImplementation object to be used.
411
412 Defaults to L{pyxb.utils.domutils.GetDOMImplementation()}, but can be
413 overridden in the constructor call using the C{implementation}
414 keyword."""
415 return self.__implementation
416 __implementation = None
417
419 """Return the document generated using this instance."""
420 return self.__document
421 __document = None
422
424 """Indicates whether {xsi:type<http://www.w3.org/TR/xmlschema-1/#xsi_type>} should be added to all elements.
425
426 Certain WSDL styles and encodings seem to require explicit notation of
427 the type of each element, even if it was specified in the schema.
428
429 This value can only be set in the constructor."""
430 return self.__requireXSIType
431 __requireXSIType = None
432
434 """Reset this instance to the state it was when created.
435
436 This creates a new root document with no content, and flushes the list
437 of namespaces for the document. The defaultNamespace and
438 requireXSIType are not modified."""
439 self.__document = self.implementation().createDocument(None, None, None)
440 self.__namespaceSupport.reset(**kw)
441
442 @classmethod
444 """Reset the global defaults for default/prefix/namespace informmation."""
445 cls.__NamespaceSupport.reset(**kw)
446
447 - def __init__ (self, implementation=None, default_namespace=None, require_xsi_type=False, namespace_prefix_map=None):
448 """Create a new instance used for building a single document.
449
450 @keyword implementation: The C{xml.dom} implementation to use.
451 Defaults to the one selected by L{GetDOMImplementation}.
452
453 @keyword default_namespace: The namespace to configure as the default
454 for the document. If not provided, there is no default namespace.
455 @type default_namespace: L{pyxb.namespace.Namespace}
456
457 @keyword require_xsi_type: If C{True}, an U{xsi:type
458 <http://www.w3.org/TR/xmlschema-1/#xsi_type>} attribute should be
459 placed in every element.
460 @type require_xsi_type: C{bool}
461
462 @keyword namespace_prefix_map: A map from pyxb.namespace.Namespace
463 instances to the preferred prefix to use for the namespace in xmlns
464 declarations. The default one assigns 'xsi' for the XMLSchema
465 instance namespace.
466 @type namespace_prefix_map: C{map} from L{pyxb.namespace.Namespace} to C{str}
467
468 @raise pyxb.LogicError: the same prefix is associated with multiple
469 namespaces in the C{namespace_prefix_map}.
470
471 """
472 if implementation is None:
473 implementation = GetDOMImplementation()
474 self.__implementation = implementation
475 self.__requireXSIType = require_xsi_type
476 self.__namespaceSupport = _BDSNamespaceSupport(default_namespace, namespace_prefix_map, inherit_from=self.__NamespaceSupport)
477 self.reset()
478
479 __namespaceSupport = None
480 __NamespaceSupport = _BDSNamespaceSupport()
481
482
486 @classmethod
490
493 @classmethod
496
500 @classmethod
504
508
512 @classmethod
516
518 """Add an attribute to the given element.
519
520 @param element: The element to which the attribute should be added
521 @type element: C{xml.dom.Element}
522 @param expanded_name: The name of the attribute. This may be a local
523 name if the attribute is not in a namespace.
524 @type expanded_name: L{pyxb.namespace.Namespace} or C{str} or C{unicode}
525 @param value: The value of the attribute
526 @type value: C{str} or C{unicode}
527 """
528 name = expanded_name
529 namespace = None
530 if isinstance(name, pyxb.namespace.ExpandedName):
531 name = expanded_name.localName()
532 namespace = expanded_name.namespace()
533
534 prefix = self.namespacePrefix(namespace, enable_default_namespace=False)
535 if prefix is not None:
536 name = '%s:%s' % (prefix, name)
537 element.setAttributeNS(namespace, name, value)
538
553
555 """Create a new element node in the tree.
556
557 @param expanded_name: The name of the element. A plain string
558 indicates a name in no namespace.
559 @type expanded_name: L{pyxb.namespace.ExpandedName} or C{str} or C{unicode}
560
561 @keyword parent: The node in the tree that will serve as the child's
562 parent. If C{None}, the document element is used. (If there is no
563 document element, then this call creates it as a side-effect.)
564
565 @return: A newly created DOM element
566 @rtype: C{xml.dom.Element}
567 """
568
569 if parent is None:
570 parent = self.document().documentElement
571 if parent is None:
572 parent = self.__document
573 if isinstance(expanded_name, (str, unicode)):
574 expanded_name = pyxb.namespace.ExpandedName(None, expanded_name)
575 if not isinstance(expanded_name, pyxb.namespace.ExpandedName):
576 raise pyxb.LogicError('Invalid type %s for expanded name' % (type(expanded_name),))
577 ns = expanded_name.namespace()
578 name = expanded_name.localName()
579 ns_uri = xml.dom.EMPTY_NAMESPACE
580 pfx = self.namespacePrefix(ns)
581 if pfx is not None:
582 ns_uri = ns.uri()
583 name = '%s:%s' % (pfx, name)
584 element = self.__document.createElementNS(ns_uri, name)
585 return parent.appendChild(element)
586
588 """Convert namespace information from a DOM node to text for new DOM node.
589
590 The namespaceURI and nodeName are extracted and parsed. The namespace
591 (if any) is registered within the document, along with any prefix from
592 the node name. A pair is returned where the first element is the
593 namespace URI or C{None}, and the second is a QName to be used for the
594 expanded name within this document.
595
596 @param node: An xml.dom.Node instance, presumably from a wildcard match.
597 @rtype: C{( str, str )}"""
598 ns = None
599 if node.namespaceURI is not None:
600 ns = pyxb.namespace.NamespaceForURI(node.namespaceURI, create_if_missing=True)
601 if node.ELEMENT_NODE == node.nodeType:
602 name = node.tagName
603 elif node.ATTRIBUTE_NODE == node.nodeType:
604 name = node.name
605
606
607 if isinstance(name, tuple):
608 name = name[1]
609 else:
610 raise pyxb.UsageError('Unable to determine name from DOM node %s' % (node,))
611 pfx = None
612 local_name = name
613 if 0 < name.find(':'):
614 (pfx, local_name) = name.split(':', 1)
615 if ns is None:
616 raise pyxb.LogicError('QName with prefix but no available namespace')
617 ns_uri = None
618 node_name = local_name
619 if ns is not None:
620 ns_uri = ns.uri()
621 self.declareNamespace(ns, pfx)
622 if pfx is None:
623 pfx = self.namespacePrefix(ns)
624 if pfx is not None:
625 node_name = '%s:%s' % (pfx, local_name)
626 return (ns_uri, node_name)
627
648
650 """Create a deep copy of the node in the target implementation.
651
652 Used when converting a DOM instance from one implementation (e.g.,
653 L{pyxb.utils.saxdom}) into another (e.g., L{xml.dom.minidom})."""
654 new_doc = self.implementation().createDocument(None, None, None)
655 return self._deepClone(node, new_doc)
656
658 """Add the child to the parent.
659
660 @note: If the child and the parent use different DOM implementations,
661 this operation will clone the child into a new instance, and give that
662 to the parent.
663
664 @param child: The value to be appended
665 @type child: C{xml.dom.Node}
666 @param parent: The new parent of the child
667 @type parent: C{xml.dom.Node}
668 @rtype: C{xml.dom.Node}"""
669
670
671
672 if isinstance(child, pyxb.utils.saxdom.Node):
673 child = self.cloneIntoImplementation(child)
674 return parent.appendChild(child)
675
676 - def appendTextChild (self, text, parent):
677 """Add the text to the parent as a text node."""
678 return parent.appendChild(self.document().createTextNode(text))
679
680
681
682
683
684