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.utils.saxutils
21 import pyxb.utils.saxdom
22 import xml.dom
23 import logging
24
25 _log = logging.getLogger(__name__)
26
27
28
29 __DOMImplementation = xml.dom.getDOMImplementation()
32 """Return the DOMImplementation object used for pyxb operations.
33
34 This is primarily used as the default implementation when generating DOM
35 trees from a binding instance. It defaults to whatever
36 xml.dom.getDOMImplementation() returns in your installation (often
37 xml.dom.minidom). It can be overridden with SetDOMImplementation()."""
38
39 global __DOMImplementation
40 return __DOMImplementation
41
47
59
61 """Namespace-aware search for an optional attribute in a node.
62
63 @param attribute_ncname: The local name of the attribute.
64 @type attribute_ncname: C{str} or C{unicode}
65
66 @keyword attribute_ns: The namespace of the attribute. Defaults to None
67 since most attributes are not in a namespace. Can be provided as either a
68 L{pyxb.namespace.Namespace} instance, or a string URI.
69 @type attribute_ns: C{None} or C{str} or C{unicode} or L{pyxb.namespace.Namespace}
70
71 @return: The value of the attribute, or C{None} if the attribute is not
72 present. (Unless C{None}, the value will always be a (unicode) string.)
73 """
74
75 ns_uri = attribute_ns
76 if isinstance(attribute_ns, pyxb.namespace.Namespace):
77 ns_uri = attribute_ns.uri()
78 attr = node.getAttributeNodeNS(ns_uri, attribute_ncname)
79 if attr is None:
80 return None
81 return attr.value
82
84 """Locate a unique child of the DOM node.
85
86 This function returns the sole child of node which is an ELEMENT_NODE
87 instance and has a tag consistent with the given tag. If multiple nodes
88 with a matching C{tag} are found, or C{absent_ok} is C{False} and no
89 matching tag is found, an exception is raised.
90
91 @param node: An a xml.dom.Node ELEMENT_NODE instance
92 @param tag: the NCName of an element in the namespace
93 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
94 can be found. If C{False}, an exception is raised if no match can be
95 found.
96 @keyword namespace: The namespace to which the child element belongs.
97 Default is the XMLSchema namespace.
98 @rtype: C{xml.dom.Node}
99
100 @raise pyxb.SchemaValidationError: multiple elements are identified
101 @raise pyxb.SchemaValidationError: C{absent_ok} is C{False} and no element is identified.
102 """
103 candidate = None
104 for cn in node.childNodes:
105 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
106 if candidate:
107 raise pyxb.SchemaValidationError('Multiple %s elements nested in %s' % (tag, node.nodeName))
108 candidate = cn
109 if (candidate is None) and not absent_ok:
110 raise pyxb.SchemaValidationError('Expected %s elements nested in %s' % (tag, node.nodeName))
111 return candidate
112
114 """Locate all children of the DOM node that have a particular tag.
115
116 This function returns a list of children of node which are ELEMENT_NODE
117 instances and have a tag consistent with the given tag.
118
119 @param node: An a xml.dom.Node ELEMENT_NODE instance.
120 @param tag: the NCName of an element in the namespace, which defaults to the
121 XMLSchema namespace.
122 @keyword namespace: The namespace to which the child element belongs.
123 Default is the XMLSchema namespace.
124
125 @rtype: C{list(xml.dom.Node)}
126 """
127 matches = []
128 for cn in node.childNodes:
129 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
130 matches.append(cn)
131 return matches
132
134 """Locate the first element child of the node.
135
136
137 @param node: An a xml.dom.Node ELEMENT_NODE instance.
138 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
139 can be found. If C{False}, an exception is raised if no match can be
140 found.
141 @keyword require_unique: If C{False} (default), it is acceptable for there
142 to be multiple child elements. If C{True}, presence of multiple child
143 elements raises an exception.
144 @keyword ignore_annotations: If C{True} (default), annotations are skipped
145 wheen looking for the first child element. If C{False}, an annotation
146 counts as an element.
147 @rtype: C{xml.dom.Node}
148
149 @raise SchemaValidationError: C{absent_ok} is C{False} and no child
150 element was identified.
151 @raise SchemaValidationError: C{require_unique} is C{True} and multiple
152 child elements were identified
153 """
154
155 candidate = None
156 for cn in node.childNodes:
157 if xml.dom.Node.ELEMENT_NODE == cn.nodeType:
158 if ignore_annotations and pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation'):
159 continue
160 if require_unique:
161 if candidate:
162 raise pyxb.SchemaValidationError('Multiple elements nested in %s' % (node.nodeName,))
163 candidate = cn
164 else:
165 return cn
166 if (candidate is None) and not absent_ok:
167 raise pyxb.SchemaValidationError('No elements nested in %s' % (node.nodeName,))
168 return candidate
169
171 """Return True iff C{node} has an ELEMENT_NODE child that is not an
172 XMLSchema annotation node.
173
174 @rtype: C{bool}
175 """
176 for cn in node.childNodes:
177 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and (not pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation')):
178 return True
179 return False
180
182 """Walk all the children, extracting all text content and
183 catenating it into the return value.
184
185 Returns C{None} if no text content (including whitespace) is found.
186
187 This is mainly used to strip comments out of the content of complex
188 elements with simple types.
189
190 @rtype: C{unicode} or C{str}
191 """
192 text = []
193 for cn in node.childNodes:
194 if xml.dom.Node.TEXT_NODE == cn.nodeType:
195 text.append(cn.data)
196 elif xml.dom.Node.CDATA_SECTION_NODE == cn.nodeType:
197 text.append(cn.data)
198 elif xml.dom.Node.COMMENT_NODE == cn.nodeType:
199 pass
200 else:
201 raise pyxb.BadDocumentError('Non-text node %s found in content' % (cn,))
202 if 0 == len(text):
203 return None
204 return ''.join(text)
205
207 """Class holding information relevant to generating the namespace aspects
208 of a DOM instance."""
209
210 __namespaces = None
211
212
213 __namespacePrefixCounter = None
214
216 """The registered default namespace.
217
218 @rtype: L{pyxb.namespace.Namespace}
219 """
220 return self.__defaultNamespace
221 __defaultNamespace = None
222
224 """Set the default namespace for the generated document.
225
226 Even if invoked post construction, the default namespace will affect
227 the entire document, as all namespace declarations are placed in the
228 document root.
229
230 @param default_namespace: The namespace to be defined as the default
231 namespace in the top-level element of the document. May be provided
232 as a real namespace, or just its URI.
233 @type default_namespace: L{pyxb.namespace.Namespace} or C{str} or
234 C{unicode}.
235 """
236
237 if self.__defaultNamespace is not None:
238 del self.__namespaces[self.__defaultNamespace]
239 if isinstance(default_namespace, basestring):
240 default_namespace = pyxb.namespace.NamespaceForURI(default_namespace, create_if_missing=True)
241 if (default_namespace is not None) and default_namespace.isAbsentNamespace():
242 raise pyxb.UsageError('Default namespace must not be an absent namespace')
243 self.__defaultNamespace = default_namespace
244 if self.__defaultNamespace is not None:
245 self.__namespaces[self.__defaultNamespace] = None
246
248 """Return a map from Namespace instances to the prefix by which they
249 are represented in the DOM document."""
250 return self.__namespacePrefixMap.copy()
251 __namespacePrefixMap = None
252
294
313
315 """Return the set of Namespace instances known to this instance."""
316 return self.__namespaces
317
318
319
322
323 - def reset (self, prefix_map=False):
335
336 - def __init__ (self, default_namespace=None, namespace_prefix_map=None, inherit_from=None):
337 """Create a new namespace declaration configuration.
338
339 @keyword default_namespace: Optional L{pyxb.namespace.Namespace}
340 instance that serves as the default namespace (applies to unqualified
341 names).
342
343 @keyword namespace_prefix_map: Optional map from
344 L{pyxb.namespace.Namespace} instances to C{str} values that are to be
345 used as the corresponding namespace prefix when constructing
346 U{qualified names<http://www.w3.org/TR/1999/REC-xml-names-19990114/#dt-qname>}.
347
348 @keyword inherit_from: Optional instance of this class from which
349 defaults are inherited. Inheritance is overridden by values of other
350 keywords in the initializer.
351 """
352 self.__prefixes = set()
353 self.__namespacePrefixCounter = 0
354 self.__namespaces = { }
355 self.__defaultNamespace = None
356 self.__resetNamespacePrefixMap()
357 if inherit_from is not None:
358 if default_namespace is None:
359 default_namespace = inherit_from.defaultNamespace()
360 self.__namespacePrefixMap.update(inherit_from.__namespacePrefixMap)
361 self.__namespacePrefixCount = inherit_from.__namespacePrefixCounter
362 self.__namespaces.update(inherit_from.__namespaces)
363 self.__prefixes.update(inherit_from.__prefixes)
364 if default_namespace is not None:
365 self.setDefaultNamespace(default_namespace)
366 prefixes = set(self.__namespacePrefixMap.values())
367 prefixes.update(self.__prefixes)
368 if namespace_prefix_map is not None:
369 prefixes = set()
370 for (ns, pfx) in namespace_prefix_map.items():
371 ns = pyxb.namespace.NamespaceInstance(ns)
372 if pfx in prefixes:
373 raise pyxb.LogicError('Cannot assign same prefix to multiple namespacess: %s' % (pfx,))
374 prefixes.add(pfx)
375 self.__namespacePrefixMap[ns] = pfx
376
378 """This holds DOM-related information used when generating a DOM tree from
379 a binding instance."""
380
382 """The DOMImplementation object to be used.
383
384 Defaults to L{pyxb.utils.domutils.GetDOMImplementation()}, but can be
385 overridden in the constructor call using the C{implementation}
386 keyword."""
387 return self.__implementation
388 __implementation = None
389
391 """Return the document generated using this instance."""
392 return self.__document
393 __document = None
394
396 """Indicates whether {xsi:type<http://www.w3.org/TR/xmlschema-1/#xsi_type>} should be added to all elements.
397
398 Certain WSDL styles and encodings seem to require explicit notation of
399 the type of each element, even if it was specified in the schema.
400
401 This value can only be set in the constructor."""
402 return self.__requireXSIType
403 __requireXSIType = None
404
406 """Reset this instance to the state it was when created.
407
408 This creates a new root document with no content, and flushes the list
409 of namespaces for the document. The defaultNamespace and
410 requireXSIType are not modified."""
411 self.__document = self.implementation().createDocument(None, None, None)
412 self.__namespaceSupport.reset(**kw)
413
414 @classmethod
416 """Reset the global defaults for default/prefix/namespace informmation."""
417 cls.__NamespaceSupport.reset(**kw)
418
419 - def __init__ (self, implementation=None, default_namespace=None, require_xsi_type=False, namespace_prefix_map=None):
420 """Create a new instance used for building a single document.
421
422 @keyword implementation: The C{xml.dom} implementation to use.
423 Defaults to the one selected by L{GetDOMImplementation}.
424
425 @keyword default_namespace: The namespace to configure as the default
426 for the document. If not provided, there is no default namespace.
427 @type default_namespace: L{pyxb.namespace.Namespace}
428
429 @keyword require_xsi_type: If C{True}, an U{xsi:type
430 <http://www.w3.org/TR/xmlschema-1/#xsi_type>} attribute should be
431 placed in every element.
432 @type require_xsi_type: C{bool}
433
434 @keyword namespace_prefix_map: A map from pyxb.namespace.Namespace
435 instances to the preferred prefix to use for the namespace in xmlns
436 declarations. The default one assigns 'xsi' for the XMLSchema
437 instance namespace.
438 @type namespace_prefix_map: C{map} from L{pyxb.namespace.Namespace} to C{str}
439
440 @raise pyxb.LogicError: the same prefix is associated with multiple
441 namespaces in the C{namespace_prefix_map}.
442
443 """
444 if implementation is None:
445 implementation = GetDOMImplementation()
446 self.__implementation = implementation
447 self.__requireXSIType = require_xsi_type
448 self.__namespaceSupport = _BDSNamespaceSupport(default_namespace, namespace_prefix_map, inherit_from=self.__NamespaceSupport)
449 self.reset()
450
451 __namespaceSupport = None
452 __NamespaceSupport = _BDSNamespaceSupport()
453
454
458 @classmethod
462
465 @classmethod
468
472 @classmethod
476
480
484 @classmethod
488
509
511 """Do the final cleanup after generating the tree. This makes sure
512 that the document element includes XML Namespace declarations for all
513 namespaces referenced in the tree.
514
515 @return: The document that has been created.
516 @rtype: C{xml.dom.Document}"""
517 for ( ns, pfx ) in self.__namespaceSupport.namespaces().items():
518 if pfx is None:
519 self.document().documentElement.setAttributeNS(pyxb.namespace.XMLNamespaces.uri(), 'xmlns', ns.uri())
520 else:
521 self.document().documentElement.setAttributeNS(pyxb.namespace.XMLNamespaces.uri(), 'xmlns:%s' % (pfx,), ns.uri())
522 return self.document()
523
556
558 """Convert namespace information from a DOM node to text for new DOM node.
559
560 The namespaceURI and nodeName are extracted and parsed. The namespace
561 (if any) is registered within the document, along with any prefix from
562 the node name. A pair is returned where the first element is the
563 namespace URI or C{None}, and the second is a QName to be used for the
564 expanded name within this document.
565
566 @param node: An xml.dom.Node instance, presumably from a wildcard match.
567 @rtype: C{( str, str )}"""
568 ns = None
569 if node.namespaceURI is not None:
570 ns = pyxb.namespace.NamespaceForURI(node.namespaceURI, create_if_missing=True)
571 if node.ELEMENT_NODE == node.nodeType:
572 name = node.tagName
573 elif node.ATTRIBUTE_NODE == node.nodeType:
574 name = node.name
575
576
577 if isinstance(name, tuple):
578 name = name[1]
579 else:
580 raise pyxb.UsageError('Unable to determine name from DOM node %s' % (node,))
581 pfx = None
582 local_name = name
583 if 0 < name.find(':'):
584 (pfx, local_name) = name.split(':', 1)
585 if ns is None:
586 raise pyxb.LogicError('QName with prefix but no available namespace')
587 ns_uri = None
588 node_name = local_name
589 if ns is not None:
590 ns_uri = ns.uri()
591 self.declareNamespace(ns, pfx)
592 if pfx is None:
593 pfx = self.namespacePrefix(ns)
594 if pfx is not None:
595 node_name = '%s:%s' % (pfx, local_name)
596 return (ns_uri, node_name)
597
618
620 """Create a deep copy of the node in the target implementation.
621
622 Used when converting a DOM instance from one implementation (e.g.,
623 L{pyxb.utils.saxdom}) into another (e.g., L{xml.dom.minidom})."""
624 new_doc = self.implementation().createDocument(None, None, None)
625 return self._deepClone(node, new_doc)
626
628 """Add the child to the parent.
629
630 @note: If the child and the parent use different DOM implementations,
631 this operation will clone the child into a new instance, and give that
632 to the parent.
633
634 @param child: The value to be appended
635 @type child: C{xml.dom.Node}
636 @param parent: The new parent of the child
637 @type parent: C{xml.dom.Node}
638 @rtype: C{xml.dom.Node}"""
639
640
641
642 if isinstance(child, pyxb.utils.saxdom.Node):
643 child = self.cloneIntoImplementation(child)
644 return parent.appendChild(child)
645
646
647
648
649