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 logging
19 import xml.dom
20
21 import pyxb
22 import pyxb.namespace
23 import pyxb.namespace.resolution
24 import pyxb.utils.saxutils
25 import pyxb.utils.saxdom
26 from pyxb.utils import six
27 from pyxb.utils.six.moves import xrange
28
29 _log = logging.getLogger(__name__)
30
31
32
33
34
35 __DOMImplementation = xml.dom.getDOMImplementation(None, (('core', '2.0'), ('xml', '2.0')))
38 """Return the DOMImplementation object used for pyxb operations.
39
40 This is primarily used as the default implementation when generating DOM
41 trees from a binding instance. It defaults to whatever
42 xml.dom.getDOMImplementation() returns in your installation (often
43 xml.dom.minidom). It can be overridden with SetDOMImplementation()."""
44
45 global __DOMImplementation
46 return __DOMImplementation
47
53
73
75 """Namespace-aware search for an optional attribute in a node.
76
77 @param attribute_ncname: The local name of the attribute.
78 @type attribute_ncname: C{str} or C{unicode}
79
80 @keyword attribute_ns: The namespace of the attribute. Defaults to None
81 since most attributes are not in a namespace. Can be provided as either a
82 L{pyxb.namespace.Namespace} instance, or a string URI.
83 @type attribute_ns: C{None} or C{str} or C{unicode} or L{pyxb.namespace.Namespace}
84
85 @return: The value of the attribute, or C{None} if the attribute is not
86 present. (Unless C{None}, the value will always be a (unicode) string.)
87 """
88
89 ns_uri = attribute_ns
90 if isinstance(attribute_ns, pyxb.namespace.Namespace):
91 ns_uri = attribute_ns.uri()
92 attr = node.getAttributeNodeNS(ns_uri, attribute_ncname)
93 if attr is None:
94 return None
95 return attr.value
96
98 """Like L{NodeAttribute} but where the content is a QName that must be
99 resolved in the context of the node.
100
101 @param attribute_ncname: as in L{NodeAttribute}
102 @keyword attribute_ns: as in L{NodeAttribute}
103
104 @return: The expanded name to which the value of the attribute resolves
105 given current namespaces, or C{None} if the attribute is not present
106 @rtype: L{pyxb.namespace.ExpandedName}
107 """
108 attr = NodeAttribute(node, attribute_ncname, attribute_ns)
109 if attr is None:
110 return None
111 nsc = pyxb.namespace.NamespaceContext.GetNodeContext(node)
112 return nsc.interpretQName(attr)
113
115 """Locate a unique child of the DOM node.
116
117 This function returns the sole child of node which is an ELEMENT_NODE
118 instance and has a tag consistent with the given tag. If multiple nodes
119 with a matching C{tag} are found, or C{absent_ok} is C{False} and no
120 matching tag is found, an exception is raised.
121
122 @param node: An a xml.dom.Node ELEMENT_NODE instance
123 @param tag: the NCName of an element in the namespace
124 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
125 can be found. If C{False}, an exception is raised if no match can be
126 found.
127 @keyword namespace: The namespace to which the child element belongs.
128 Default is the XMLSchema namespace.
129 @rtype: C{xml.dom.Node}
130
131 @raise pyxb.SchemaValidationError: multiple elements are identified
132 @raise pyxb.SchemaValidationError: C{absent_ok} is C{False} and no element is identified.
133 """
134 candidate = None
135 for cn in node.childNodes:
136 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
137 if candidate:
138 raise pyxb.SchemaValidationError('Multiple %s elements nested in %s' % (tag, node.nodeName))
139 candidate = cn
140 if (candidate is None) and not absent_ok:
141 raise pyxb.SchemaValidationError('Expected %s elements nested in %s' % (tag, node.nodeName))
142 return candidate
143
145 """Locate all children of the DOM node that have a particular tag.
146
147 This function returns a list of children of node which are ELEMENT_NODE
148 instances and have a tag consistent with the given tag.
149
150 @param node: An a xml.dom.Node ELEMENT_NODE instance.
151 @param tag: the NCName of an element in the namespace, which defaults to the
152 XMLSchema namespace.
153 @keyword namespace: The namespace to which the child element belongs.
154 Default is the XMLSchema namespace.
155
156 @rtype: C{list(xml.dom.Node)}
157 """
158 matches = []
159 for cn in node.childNodes:
160 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
161 matches.append(cn)
162 return matches
163
165 """Locate the first element child of the node.
166
167
168 @param node: An a xml.dom.Node ELEMENT_NODE instance.
169 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
170 can be found. If C{False}, an exception is raised if no match can be
171 found.
172 @keyword require_unique: If C{False} (default), it is acceptable for there
173 to be multiple child elements. If C{True}, presence of multiple child
174 elements raises an exception.
175 @keyword ignore_annotations: If C{True} (default), annotations are skipped
176 wheen looking for the first child element. If C{False}, an annotation
177 counts as an element.
178 @rtype: C{xml.dom.Node}
179
180 @raise SchemaValidationError: C{absent_ok} is C{False} and no child
181 element was identified.
182 @raise SchemaValidationError: C{require_unique} is C{True} and multiple
183 child elements were identified
184 """
185
186 candidate = None
187 for cn in node.childNodes:
188 if xml.dom.Node.ELEMENT_NODE == cn.nodeType:
189 if ignore_annotations and pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation'):
190 continue
191 if require_unique:
192 if candidate:
193 raise pyxb.SchemaValidationError('Multiple elements nested in %s' % (node.nodeName,))
194 candidate = cn
195 else:
196 return cn
197 if (candidate is None) and not absent_ok:
198 raise pyxb.SchemaValidationError('No elements nested in %s' % (node.nodeName,))
199 return candidate
200
202 """Return True iff C{node} has an ELEMENT_NODE child that is not an
203 XMLSchema annotation node.
204
205 @rtype: C{bool}
206 """
207 for cn in node.childNodes:
208 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and (not pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation')):
209 return True
210 return False
211
213 """Walk all the children, extracting all text content and
214 catenating it into the return value.
215
216 Returns C{None} if no text content (including whitespace) is found.
217
218 This is mainly used to strip comments out of the content of complex
219 elements with simple types.
220
221 @rtype: C{unicode} or C{str}
222 """
223 text = []
224 for cn in node.childNodes:
225 if xml.dom.Node.TEXT_NODE == cn.nodeType:
226 text.append(cn.data)
227 elif xml.dom.Node.CDATA_SECTION_NODE == cn.nodeType:
228 text.append(cn.data)
229 elif xml.dom.Node.COMMENT_NODE == cn.nodeType:
230 pass
231 else:
232 raise pyxb.NonElementValidationError(cn)
233 if 0 == len(text):
234 return None
235 return ''.join(text)
236
238 """This holds DOM-related information used when generating a DOM tree from
239 a binding instance."""
240
242 """The DOMImplementation object to be used.
243
244 Defaults to L{pyxb.utils.domutils.GetDOMImplementation()}, but can be
245 overridden in the constructor call using the C{implementation}
246 keyword."""
247 return self.__implementation
248 __implementation = None
249
251 """Return the document generated using this instance."""
252 return self.__document
253 __document = None
254
256 """Indicates whether {xsi:type<http://www.w3.org/TR/xmlschema-1/#xsi_type>} should be added to all elements.
257
258 Certain WSDL styles and encodings seem to require explicit notation of
259 the type of each element, even if it was specified in the schema.
260
261 This value can only be set in the constructor."""
262 return self.__requireXSIType
263 __requireXSIType = None
264
278
279 @classmethod
283
284 - def __init__ (self, implementation=None, default_namespace=None, require_xsi_type=False, namespace_prefix_map=None):
285 """Create a new instance used for building a single document.
286
287 @keyword implementation: The C{xml.dom} implementation to use.
288 Defaults to the one selected by L{GetDOMImplementation}.
289
290 @keyword default_namespace: The namespace to configure as the default
291 for the document. If not provided, there is no default namespace.
292 @type default_namespace: L{pyxb.namespace.Namespace}
293
294 @keyword require_xsi_type: If C{True}, an U{xsi:type
295 <http://www.w3.org/TR/xmlschema-1/#xsi_type>} attribute should be
296 placed in every element.
297 @type require_xsi_type: C{bool}
298
299 @keyword namespace_prefix_map: A map from pyxb.namespace.Namespace
300 instances to the preferred prefix to use for the namespace in xmlns
301 declarations. The default one assigns 'xsi' for the XMLSchema
302 instance namespace.
303 @type namespace_prefix_map: C{map} from L{pyxb.namespace.Namespace} to C{str}
304
305 @raise pyxb.LogicError: the same prefix is associated with multiple
306 namespaces in the C{namespace_prefix_map}.
307
308 """
309 if implementation is None:
310 implementation = GetDOMImplementation()
311 self.__implementation = implementation
312 self.__requireXSIType = require_xsi_type
313 self.__namespaceContext = pyxb.namespace.NamespaceContext(parent_context=self.__NamespaceContext,
314 in_scope_namespaces=namespace_prefix_map)
315 if default_namespace is not None:
316 self.__namespaceContext.setDefaultNamespace(default_namespace)
317 self.reset()
318
319
320 __NamespaceContext = pyxb.namespace.NamespaceContext()
321
322
323 __namespaceContext = None
324
325
326
327
328
329 __referencedNamespacePrefixes = None
330
334 @classmethod
338
341 @classmethod
344
348 @classmethod
352
354 """Return the prefix to be used for the given namespace.
355
356 This will L{declare <declareNamespace>} the namespace if it has not
357 yet been observed. It will also ensure the mapping from the returned
358 prefix to C{namespace} is recorded for addition as an xmlns directive
359 in the final document.
360
361 @param namespace: The namespace for which a prefix is needed. If the
362 provided namespace is C{None} or an absent namespace, the C{None}
363 value will be returned as the corresponding prefix.
364
365 @keyword enable_default_namespace: Normally if the namespace is the default
366 namespace C{None} is returned to indicate this. If this keyword is
367 C{False} then we need a namespace prefix even if this is the default.
368 """
369 if (namespace is None) or namespace.isAbsentNamespace():
370 return None
371 if isinstance(namespace, six.string_types):
372 namespace = pyxb.namespace.NamespaceForURI(namespace, create_if_missing=True)
373 if (self.defaultNamespace() == namespace) and enable_default_namespace:
374 return None
375 pfx = self.__namespaceContext.prefixForNamespace(namespace)
376 if pfx is None:
377 pfx = self.__namespaceContext.declareNamespace(namespace)
378 self.__referencedNamespacePrefixes.add((namespace, pfx))
379 return pfx
380
381 - def qnameAsText (self, qname, enable_default_namespace=True):
382 assert isinstance(qname, pyxb.namespace.ExpandedName)
383 name = qname.localName()
384 prefix = self.namespacePrefix(qname.namespace(), enable_default_namespace=enable_default_namespace)
385 if prefix is not None:
386 name = '%s:%s' % (prefix, name)
387 return name
388
389 - def valueAsText (self, value, enable_default_namespace=True):
390 """Represent a simple type value as XML text.
391
392 This is essentially what C{value.xsdLiteral()} does, but this one
393 handles any special cases such as QName values where the lexical
394 representation cannot be done in isolation of external information
395 such as namespace declarations."""
396 from pyxb.binding.basis import simpleTypeDefinition, STD_list
397 if isinstance(value, pyxb.namespace.ExpandedName):
398 return self.qnameAsText(value, enable_default_namespace=enable_default_namespace)
399 if isinstance(value, STD_list):
400 return ' '.join([ self.valueAsText(_v, enable_default_namespace=enable_default_namespace) for _v in value ])
401 if isinstance(value, simpleTypeDefinition):
402 return value.xsdLiteral()
403 assert value is not None
404 return six.text_type(value)
405
407 """Add an attribute to the given element.
408
409 @param element: The element to which the attribute should be added
410 @type element: C{xml.dom.Element}
411 @param expanded_name: The name of the attribute. This may be a local
412 name if the attribute is not in a namespace.
413 @type expanded_name: L{pyxb.namespace.Namespace} or C{str} or C{unicode}
414 @param value: The value of the attribute
415 @type value: C{str} or C{unicode}
416 """
417 name = expanded_name
418 ns_uri = xml.dom.EMPTY_NAMESPACE
419 if isinstance(name, pyxb.namespace.ExpandedName):
420 ns_uri = expanded_name.namespaceURI()
421
422 name = self.qnameAsText(expanded_name, enable_default_namespace=False)
423 element.setAttributeNS(ns_uri, name, self.valueAsText(value))
424
426 """Manually add an XMLNS declaration to the document element.
427
428 @param namespace: a L{pyxb.namespace.Namespace} instance
429
430 @param prefix: the prefix by which the namespace is known. If
431 C{None}, the default prefix as previously declared will be used; if
432 C{''} (empty string) a declaration for C{namespace} as the default
433 namespace will be generated.
434
435 @return: C{prefix} as used in the added declaration.
436 """
437 if not isinstance(namespace, pyxb.namespace.Namespace):
438 raise pyxb.UsageError('addXMLNSdeclaration: must be given a namespace instance')
439 if namespace.isAbsentNamespace():
440 raise pyxb.UsageError('addXMLNSdeclaration: namespace must not be an absent namespace')
441 if prefix is None:
442 prefix = self.namespacePrefix(namespace)
443 if not prefix:
444 an = 'xmlns'
445 else:
446 an = 'xmlns:' + prefix
447 element.setAttributeNS(pyxb.namespace.XMLNamespaces.uri(), an, namespace.uri())
448 return prefix
449
451 """Do the final cleanup after generating the tree. This makes sure
452 that the document element includes XML Namespace declarations for all
453 namespaces referenced in the tree.
454
455 @return: The document that has been created.
456 @rtype: C{xml.dom.Document}"""
457 ns = self.defaultNamespace()
458 if ns is not None:
459 self.addXMLNSDeclaration(self.document().documentElement, ns, '')
460 for (ns, pfx) in self.__referencedNamespacePrefixes:
461 self.addXMLNSDeclaration(self.document().documentElement, ns, pfx)
462 return self.document()
463
465 """Create a new element node in the tree.
466
467 @param expanded_name: The name of the element. A plain string
468 indicates a name in no namespace.
469 @type expanded_name: L{pyxb.namespace.ExpandedName} or C{str} or C{unicode}
470
471 @keyword parent: The node in the tree that will serve as the child's
472 parent. If C{None}, the document element is used. (If there is no
473 document element, then this call creates it as a side-effect.)
474
475 @return: A newly created DOM element
476 @rtype: C{xml.dom.Element}
477 """
478
479 if parent is None:
480 parent = self.document().documentElement
481 if parent is None:
482 parent = self.__document
483 if isinstance(expanded_name, six.string_types):
484 expanded_name = pyxb.namespace.ExpandedName(None, expanded_name)
485 if not isinstance(expanded_name, pyxb.namespace.ExpandedName):
486 raise pyxb.LogicError('Invalid type %s for expanded name' % (type(expanded_name),))
487 ns = expanded_name.namespace()
488 ns_uri = xml.dom.EMPTY_NAMESPACE
489 name = expanded_name.localName()
490 if ns is not None:
491 ns_uri = ns.uri()
492 name = self.qnameAsText(expanded_name)
493 element = self.__document.createElementNS(ns_uri, name)
494 return parent.appendChild(element)
495
497 """Convert namespace information from a DOM node to text for new DOM node.
498
499 The namespaceURI and nodeName are extracted and parsed. The namespace
500 (if any) is registered within the document, along with any prefix from
501 the node name. A pair is returned where the first element is the
502 namespace URI or C{None}, and the second is a QName to be used for the
503 expanded name within this document.
504
505 @param node: An xml.dom.Node instance, presumably from a wildcard match.
506 @rtype: C{( str, str )}"""
507 ns = None
508 if node.namespaceURI is not None:
509 ns = pyxb.namespace.NamespaceForURI(node.namespaceURI, create_if_missing=True)
510 if node.ELEMENT_NODE == node.nodeType:
511 name = node.tagName
512 elif node.ATTRIBUTE_NODE == node.nodeType:
513 name = node.name
514
515
516 if isinstance(name, tuple):
517 name = name[1]
518 else:
519 raise pyxb.UsageError('Unable to determine name from DOM node %s' % (node,))
520 pfx = None
521 local_name = name
522 if 0 < name.find(':'):
523 (pfx, local_name) = name.split(':', 1)
524 if ns is None:
525 raise pyxb.LogicError('QName with prefix but no available namespace')
526 ns_uri = None
527 node_name = local_name
528 if ns is not None:
529 ns_uri = ns.uri()
530 self.declareNamespace(ns, pfx)
531 node_name = self.qnameAsText(ns.createExpandedName(local_name))
532 return (ns_uri, node_name)
533
554
556 """Create a deep copy of the node in the target implementation.
557
558 Used when converting a DOM instance from one implementation (e.g.,
559 L{pyxb.utils.saxdom}) into another (e.g., L{xml.dom.minidom})."""
560 new_doc = self.implementation().createDocument(None, None, None)
561 return self._deepClone(node, new_doc)
562
564 """Add the child to the parent.
565
566 @note: If the child and the parent use different DOM implementations,
567 this operation will clone the child into a new instance, and give that
568 to the parent.
569
570 @param child: The value to be appended
571 @type child: C{xml.dom.Node}
572 @param parent: The new parent of the child
573 @type parent: C{xml.dom.Node}
574 @rtype: C{xml.dom.Node}"""
575
576
577
578 if isinstance(child, (pyxb.utils.saxdom.Node, xml.dom.minidom.Node)):
579 child = self.cloneIntoImplementation(child)
580 return parent.appendChild(child)
581
582 - def appendTextChild (self, text, parent):
583 """Add the text to the parent as a text node."""
584 return parent.appendChild(self.document().createTextNode(self.valueAsText(text)))
585
586
587
588
589