1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 """Functions that support activities related to the Document Object Model."""
16
17 import pyxb
18 import pyxb.namespace
19 import pyxb.utils.saxutils
20 import pyxb.utils.saxdom
21 import xml.dom
22
23
24
25 __DOMImplementation = xml.dom.getDOMImplementation()
28 """Return the DOMImplementation object used for pyxb operations.
29
30 This is primarily used as the default implementation when generating DOM
31 trees from a binding instance. It defaults to whatever
32 xml.dom.getDOMImplementation() returns in your installation (often
33 xml.dom.minidom). It can be overridden with SetDOMImplementation()."""
34
35 global __DOMImplementation
36 return __DOMImplementation
37
43
56
58 """Namespace-aware search for an optional attribute in a node.
59
60 @param attribute_ncname: The local name of the attribute.
61 @type attribute_ncname: C{str} or C{unicode}
62
63 @keyword attribute_ns: The namespace of the attribute. Defaults to None
64 since most attributes are not in a namespace. Can be provided as either a
65 L{pyxb.namespace.Namespace} instance, or a string URI.
66 @type attribute_ns: C{None} or C{str} or C{unicode} or L{pyxb.namespace.Namespace}
67
68 @return: The value of the attribute, or C{None} if the attribute is not
69 present. (Unless C{None}, the value will always be a (unicode) string.)
70 """
71
72 ns_uri = attribute_ns
73 if isinstance(attribute_ns, pyxb.namespace.Namespace):
74 ns_uri = attribute_ns.uri()
75 attr = node.getAttributeNodeNS(ns_uri, attribute_ncname)
76 if attr is None:
77 return None
78 return attr.value
79
81 """Locate a unique child of the DOM node.
82
83 This function returns the sole child of node which is an ELEMENT_NODE
84 instance and has a tag consistent with the given tag. If multiple nodes
85 with a matching C{tag} are found, or C{absent_ok} is C{False} and no
86 matching tag is found, an exception is raised.
87
88 @param node: An a xml.dom.Node ELEMENT_NODE instance
89 @param tag: the NCName of an element in the namespace
90 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
91 can be found. If C{False}, an exception is raised if no match can be
92 found.
93 @keyword namespace: The namespace to which the child element belongs.
94 Default is the XMLSchema namespace.
95 @rtype: C{xml.dom.Node}
96
97 @raise pyxb.SchemaValidationError: multiple elements are identified
98 @raise pyxb.SchemaValidationError: C{absent_ok} is C{False} and no element is identified.
99 """
100 candidate = None
101 for cn in node.childNodes:
102 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
103 if candidate:
104 raise pyxb.SchemaValidationError('Multiple %s elements nested in %s' % (name, node.nodeName))
105 candidate = cn
106 if (candidate is None) and not absent_ok:
107 raise pyxb.SchemaValidationError('Expected %s elements nested in %s' % (name, node.nodeName))
108 return candidate
109
111 """Locate all children of the DOM node that have a particular tag.
112
113 This function returns a list of children of node which are ELEMENT_NODE
114 instances and have a tag consistent with the given tag.
115
116 @param node: An a xml.dom.Node ELEMENT_NODE instance.
117 @param tag: the NCName of an element in the namespace, which defaults to the
118 XMLSchema namespace.
119 @keyword namespace: The namespace to which the child element belongs.
120 Default is the XMLSchema namespace.
121
122 @rtype: C{list(xml.dom.Node)}
123 """
124 matches = []
125 for cn in node.childNodes:
126 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
127 matches.append(cn)
128 return matches
129
131 """Locate the first element child of the node.
132
133
134 @param node: An a xml.dom.Node ELEMENT_NODE instance.
135 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
136 can be found. If C{False}, an exception is raised if no match can be
137 found.
138 @keyword require_unique: If C{False} (default), it is acceptable for there
139 to be multiple child elements. If C{True}, presence of multiple child
140 elements raises an exception.
141 @keyword ignore_annotations: If C{True} (default), annotations are skipped
142 wheen looking for the first child element. If C{False}, an annotation
143 counts as an element.
144 @rtype: C{xml.dom.Node}
145
146 @raise SchemaValidationError: C{absent_ok} is C{False} and no child
147 element was identified.
148 @raise SchemaValidationError: C{require_unique} is C{True} and multiple
149 child elements were identified
150 """
151
152 candidate = None
153 for cn in node.childNodes:
154 if xml.dom.Node.ELEMENT_NODE == cn.nodeType:
155 if ignore_annotations and pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation'):
156 continue
157 if require_unique:
158 if candidate:
159 raise pyxb.SchemaValidationError('Multiple elements nested in %s' % (node.nodeName,))
160 candidate = cn
161 else:
162 return cn
163 if (candidate is None) and not absent_ok:
164 raise pyxb.SchemaValidationError('No elements nested in %s' % (node.nodeName,))
165 return candidate
166
168 """Return True iff C{node} has an ELEMENT_NODE child that is not an
169 XMLSchema annotation node.
170
171 @rtype: C{bool}
172 """
173 for cn in node.childNodes:
174 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and (not pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation')):
175 return True
176 return False
177
179 """Walk all the children, extracting all text content and
180 catenating it into the return value.
181
182 Returns C{None} if no text content (including whitespace) is found.
183
184 This is mainly used to strip comments out of the content of complex
185 elements with simple types.
186
187 @rtype: C{unicode} or C{str}
188 """
189 text = []
190 for cn in node.childNodes:
191 if xml.dom.Node.TEXT_NODE == cn.nodeType:
192 text.append(cn.data)
193 elif xml.dom.Node.CDATA_SECTION_NODE == cn.nodeType:
194 text.append(cn.data)
195 elif xml.dom.Node.COMMENT_NODE == cn.nodeType:
196 pass
197 else:
198 raise pyxb.BadDocumentError('Non-text node %s found in content' % (cn,))
199 if 0 == len(text):
200 return None
201 return ''.join(text)
202
204 """Class holding information relevant to generating the namespace aspects
205 of a DOM instance."""
206
207 __namespaces = None
208
209
210 __namespacePrefixCounter = None
211
213 """The registered default namespace.
214
215 @rtype: L{pyxb.namespace.Namespace}
216 """
217 return self.__defaultNamespace
218 __defaultNamespace = None
219
221 """Set the default namespace for the generated document.
222
223 Even if invoked post construction, the default namespace will affect
224 the entire document, as all namespace declarations are placed in the
225 document root.
226
227 @param default_namespace: The namespace to be defined as the default
228 namespace in the top-level element of the document. May be provided
229 as a real namespace, or just its URI.
230 @type default_namespace: L{pyxb.namespace.Namespace} or C{str} or
231 C{unicode}.
232 """
233
234 if self.__defaultNamespace is not None:
235 del self.__namespaces[self.__defaultNamespace]
236 if isinstance(default_namespace, basestring):
237 default_namespace = pyxb.namespace.NamespaceForURI(default_namespace, create_if_missing=True)
238 if (default_namespace is not None) and default_namespace.isAbsentNamespace():
239 raise pyxb.UsageError('Default namespace must not be an absent namespace')
240 self.__defaultNamespace = default_namespace
241 if self.__defaultNamespace is not None:
242 self.__namespaces[self.__defaultNamespace] = None
243
245 """Return a map from Namespace instances to the prefix by which they
246 are represented in the DOM document."""
247 return self.__namespacePrefixMap.copy()
248 __namespacePrefixMap = None
249
251 """Add the given namespace as one to be used in this document.
252
253 @param namespace: The namespace to be associated with the document.
254 @type namespace: L{pyxb.namespace.Namespace}
255
256 @keyword prefix: Optional prefix to be used with this namespace. If
257 not provided, a unique prefix is generated or a standard prefix is
258 used, depending on the namespace.
259
260 @keyword add_to_map: If C{False} (default), the prefix is not added to
261 the namespace prefix map. If C{True} it is added. (Often, things
262 added to the prefix map are preserved across resets, which is often
263 not desired for specific prefix/namespace pairs).
264
265 @todo: ensure multiple namespaces do not share the same prefix
266 @todo: provide default prefix in L{pyxb.namespace.Namespace}
267 @todo: support multiple prefixes for each namespace
268 """
269 if not isinstance(namespace, pyxb.namespace.Namespace):
270 raise pyxb.UsageError('declareNamespace: must be given a namespace instance')
271 if namespace.isAbsentNamespace():
272 raise pyxb.UsageError('declareNamespace: namespace must not be an absent namespace')
273 if prefix is None:
274 prefix = self.__namespacePrefixMap.get(namespace)
275 if prefix is None:
276 prefix = namespace.prefix()
277 if prefix is None:
278 self.__namespacePrefixCounter += 1
279 prefix = 'ns%d' % (self.__namespacePrefixCounter,)
280 if prefix in self.__prefixes:
281 raise pyxb.LogicError('Prefix %s is already in use' % (prefix,))
282 self.__namespaces[namespace] = prefix
283 self.__prefixes.add(prefix)
284
285 if add_to_map:
286 self.__namespacePrefixMap[namespace] = prefix
287 return prefix
288
307
309 """Return the set of Namespace instances known to this instance."""
310 return self.__namespaces
311
312
313
316
317 - def reset (self, prefix_map=False):
329
330 - def __init__ (self, default_namespace=None, namespace_prefix_map=None, inherit_from=None):
331 """Create a new namespace declaration configuration.
332
333 @keyword default_namespace: Optional L{pyxb.namespace.Namespace}
334 instance that serves as the default namespace (applies to unqualified
335 names).
336
337 @keyword namespace_prefix_map: Optional map from
338 L{pyxb.namespace.Namespace} instances to C{str} values that are to be
339 used as the corresponding namespace prefix when constructing
340 U{qualified names<http://www.w3.org/TR/1999/REC-xml-names-19990114/#dt-qname>}.
341
342 @keyword inherit_from: Optional instance of this class from which
343 defaults are inherited. Inheritance is overridden by values of other
344 keywords in the initializer.
345 """
346 self.__prefixes = set()
347 self.__namespacePrefixCounter = 0
348 self.__namespaces = { }
349 self.__defaultNamespace = None
350 self.__resetNamespacePrefixMap()
351 if inherit_from is not None:
352 if default_namespace is None:
353 default_namespace = inherit_from.defaultNamespace()
354 self.__namespacePrefixMap.update(inherit_from.__namespacePrefixMap)
355 self.__namespacePrefixCount = inherit_from.__namespacePrefixCounter
356 self.__namespaces.update(inherit_from.__namespaces)
357 self.__prefixes.update(inherit_from.__prefixes)
358 if default_namespace is not None:
359 self.setDefaultNamespace(default_namespace)
360 prefixes = set(self.__namespacePrefixMap.values())
361 prefixes.update(self.__prefixes)
362 if namespace_prefix_map is not None:
363 prefixes = set()
364 for (ns, pfx) in namespace_prefix_map.items():
365 ns = pyxb.namespace.NamespaceInstance(ns)
366 if pfx in prefixes:
367 raise pyxb.LogicError('Cannot assign same prefix to multiple namespacess: %s' % (pfx,))
368 prefixes.add(pfx)
369 self.__namespacePrefixMap[ns] = pfx
370
372 """This holds DOM-related information used when generating a DOM tree from
373 a binding instance."""
374
376 """The DOMImplementation object to be used.
377
378 Defaults to L{pyxb.utils.domutils.GetDOMImplementation()}, but can be
379 overridden in the constructor call using the C{implementation}
380 keyword."""
381 return self.__implementation
382 __implementation = None
383
385 """Return the document generated using this instance."""
386 return self.__document
387 __document = None
388
390 """Indicates whether {xsi:type<http://www.w3.org/TR/xmlschema-1/#xsi_type>} should be added to all elements.
391
392 Certain WSDL styles and encodings seem to require explicit notation of
393 the type of each element, even if it was specified in the schema.
394
395 This value can only be set in the constructor."""
396 return self.__requireXSIType
397 __requireXSIType = None
398
400 """Reset this instance to the state it was when created.
401
402 This creates a new root document with no content, and flushes the list
403 of namespaces for the document. The defaultNamespace and
404 requireXSIType are not modified."""
405 self.__document = self.implementation().createDocument(None, None, None)
406 self.__namespaceSupport.reset(**kw)
407
408 @classmethod
410 """Reset the global defaults for default/prefix/namespace informmation."""
411 self.__NamespaceSupport.reset(**kw)
412
413 - def __init__ (self, implementation=None, default_namespace=None, require_xsi_type=False, namespace_prefix_map=None):
414 """Create a new instance used for building a single document.
415
416 @keyword implementation: The C{xml.dom} implementation to use.
417 Defaults to the one selected by L{GetDOMImplementation}.
418
419 @keyword default_namespace: The namespace to configure as the default
420 for the document. If not provided, there is no default namespace.
421 @type default_namespace: L{pyxb.namespace.Namespace}
422
423 @keyword require_xsi_type: If C{True}, an U{xsi:type
424 <http://www.w3.org/TR/xmlschema-1/#xsi_type>} attribute should be
425 placed in every element.
426 @type require_xsi_type: C{bool}
427
428 @keyword namespace_prefix_map: A map from pyxb.namespace.Namespace
429 instances to the preferred prefix to use for the namespace in xmlns
430 declarations. The default one assigns 'xsi' for the XMLSchema
431 instance namespace.
432 @type namespace_prefix_map: C{map} from L{pyxb.namespace.Namespace} to C{str}
433
434 @raise pyxb.LogicError: the same prefix is associated with multiple
435 namespaces in the C{namespace_prefix_map}.
436
437 """
438 if implementation is None:
439 implementation = GetDOMImplementation()
440 self.__implementation = implementation
441 self.__requireXSIType = require_xsi_type
442 self.__namespaceSupport = _BDSNamespaceSupport(default_namespace, namespace_prefix_map, inherit_from=self.__NamespaceSupport)
443 self.reset()
444
445 __namespaceSupport = None
446 __NamespaceSupport = _BDSNamespaceSupport()
447
448
452 @classmethod
456
459 @classmethod
462
466 @classmethod
470
474
478 @classmethod
482
503
505 """Do the final cleanup after generating the tree. This makes sure
506 that the document element includes XML Namespace declarations for all
507 namespaces referenced in the tree.
508
509 @return: The document that has been created.
510 @rtype: C{xml.dom.Document}"""
511 for ( ns, pfx ) in self.__namespaceSupport.namespaces().items():
512 if pfx is None:
513 self.document().documentElement.setAttributeNS(pyxb.namespace.XMLNamespaces.uri(), 'xmlns', ns.uri())
514 else:
515 self.document().documentElement.setAttributeNS(pyxb.namespace.XMLNamespaces.uri(), 'xmlns:%s' % (pfx,), ns.uri())
516 return self.document()
517
550
552 """Convert namespace information from a DOM node to text for new DOM node.
553
554 The namespaceURI and nodeName are extracted and parsed. The namespace
555 (if any) is registered within the document, along with any prefix from
556 the node name. A pair is returned where the first element is the
557 namespace URI or C{None}, and the second is a QName to be used for the
558 expanded name within this document.
559
560 @param node: An xml.dom.Node instance, presumably from a wildcard match.
561 @rtype: C{( str, str )}"""
562 ns = None
563 if node.namespaceURI is not None:
564 ns = pyxb.namespace.NamespaceForURI(node.namespaceURI, create_if_missing=True)
565 if node.ELEMENT_NODE == node.nodeType:
566 name = node.tagName
567 elif node.ATTRIBUTE_NODE == node.nodeType:
568 name = node.name
569
570
571 if isinstance(name, tuple):
572 name = name[1]
573 else:
574 raise pyxb.UsageError('Unable to determine name from DOM node %s' % (node,))
575 pfx = None
576 local_name = name
577 if 0 < name.find(':'):
578 (pfx, local_name) = node_name.split(':', 1)
579 if ns is None:
580 raise pyxb.LogicError('QName with prefix but no available namespace')
581 ns_uri = None
582 node_name = local_name
583 if ns is not None:
584 ns_uri = ns.uri()
585 self.declareNamespace(ns, pfx)
586 if pfx is None:
587 pfx = self.namespacePrefix(ns)
588 if pfx is not None:
589 node_name = '%s:%s' % (pfx, local_name)
590 return (ns_uri, node_name)
591
612
614 """Create a deep copy of the node in the target implementation.
615
616 Used when converting a DOM instance from one implementation (e.g.,
617 L{pyxb.utils.saxdom}) into another (e.g., L{xml.dom.minidom})."""
618 new_doc = self.implementation().createDocument(None, None, None)
619 return self._deepClone(node, new_doc)
620
622 """Add the child to the parent.
623
624 @note: If the child and the parent use different DOM implementations,
625 this operation will clone the child into a new instance, and give that
626 to the parent.
627
628 @param child: The value to be appended
629 @type child: C{xml.dom.Node}
630 @param parent: The new parent of the child
631 @type parent: C{xml.dom.Node}
632 @rtype: C{xml.dom.Node}"""
633
634
635
636 if isinstance(child, pyxb.utils.saxdom.Node):
637 child = self.cloneIntoImplementation(child)
638 return parent.appendChild(child)
639
640
641
642
643