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