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 xml.dom
20
21
22
23 __DOMImplementation = xml.dom.getDOMImplementation()
26 """Return the DOMImplementation object used for pyxb operations.
27
28 This is primarily used as the default implementation when generating DOM
29 trees from a binding instance. It defaults to whatever
30 xml.dom.getDOMImplementation() returns in your installation (often
31 xml.dom.minidom). It can be overridden with SetDOMImplementation()."""
32
33 global __DOMImplementation
34 return __DOMImplementation
35
41
53
55 """Namespace-aware search for an optional attribute in a node.
56
57 @param attribute_ncname: The local name of the attribute.
58 @type attribute_ncname: C{str} or C{unicode}
59
60 @keyword attribute_ns: The namespace of the attribute. Defaults to None
61 since most attributes are not in a namespace. Can be provided as either a
62 L{pyxb.namespace.Namespace} instance, or a string URI.
63 @type attribute_ns: C{None} or C{str} or C{unicode} or L{pyxb.namespace.Namespace}
64
65 @return: The value of the attribute, or C{None} if the attribute is not
66 present. (Unless C{None}, the value will always be a (unicode) string.)
67 """
68
69 ns_uri = attribute_ns
70 if isinstance(attribute_ns, pyxb.namespace.Namespace):
71 ns_uri = attribute_ns.uri()
72 attr = node.getAttributeNodeNS(ns_uri, attribute_ncname)
73 if attr is None:
74 return None
75 return attr.value
76
78 """Locate a unique child of the DOM node.
79
80 This function returns the sole child of node which is an ELEMENT_NODE
81 instance and has a tag consistent with the given tag. If multiple nodes
82 with a matching C{tag} are found, or C{absent_ok} is C{False} and no
83 matching tag is found, an exception is raised.
84
85 @param node: An a xml.dom.Node ELEMENT_NODE instance
86 @param tag: the NCName of an element in the namespace
87 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
88 can be found. If C{False}, an exception is raised if no match can be
89 found.
90 @keyword namespace: The namespace to which the child element belongs.
91 Default is the XMLSchema namespace.
92 @rtype: C{xml.dom.Node}
93
94 @raise pyxb.SchemaValidationError: multiple elements are identified
95 @raise pyxb.SchemaValidationError: C{absent_ok} is C{False} and no element is identified.
96 """
97 candidate = None
98 for cn in node.childNodes:
99 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
100 if candidate:
101 raise SchemaValidationError('Multiple %s elements nested in %s' % (name, node.nodeName))
102 candidate = cn
103 if (candidate is None) and not absent_ok:
104 raise SchemaValidationError('Expected %s elements nested in %s' % (name, node.nodeName))
105 return candidate
106
108 """Locate all children of the DOM node that have a particular tag.
109
110 This function returns a list of children of node which are ELEMENT_NODE
111 instances and have a tag consistent with the given tag.
112
113 @param node: An a xml.dom.Node ELEMENT_NODE instance.
114 @param tag: the NCName of an element in the namespace, which defaults to the
115 XMLSchema namespace.
116 @keyword namespace: The namespace to which the child element belongs.
117 Default is the XMLSchema namespace.
118
119 @rtype: C{list(xml.dom.Node)}
120 """
121 matches = []
122 for cn in node.childNodes:
123 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and namespace.nodeIsNamed(cn, tag):
124 matches.append(cn)
125 return matches
126
128 """Locate the first element child of the node.
129
130
131 @param node: An a xml.dom.Node ELEMENT_NODE instance.
132 @keyword absent_ok: If C{True} (default), C{None} is returned if no match
133 can be found. If C{False}, an exception is raised if no match can be
134 found.
135 @keyword require_unique: If C{False} (default), it is acceptable for there
136 to be multiple child elements. If C{True}, presence of multiple child
137 elements raises an exception.
138 @keyword ignore_annotations: If C{True} (default), annotations are skipped
139 wheen looking for the first child element. If C{False}, an annotation
140 counts as an element.
141 @rtype: C{xml.dom.Node}
142
143 @raise SchemaValidationError: C{absent_ok} is C{False} and no child
144 element was identified.
145 @raise SchemaValidationError: C{require_unique} is C{True} and multiple
146 child elements were identified
147 """
148
149 candidate = None
150 for cn in node.childNodes:
151 if xml.dom.Node.ELEMENT_NODE == cn.nodeType:
152 if ignore_annotations and pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation'):
153 continue
154 if require_unique:
155 if candidate:
156 raise SchemaValidationError('Multiple elements nested in %s' % (node.nodeName,))
157 candidate = cn
158 else:
159 return cn
160 if (candidate is None) and not absent_ok:
161 raise SchemaValidationError('No elements nested in %s' % (node.nodeName,))
162 return candidate
163
165 """Return True iff C{node} has an ELEMENT_NODE child that is not an
166 XMLSchema annotation node.
167
168 @rtype: C{bool}
169 """
170 for cn in node.childNodes:
171 if (xml.dom.Node.ELEMENT_NODE == cn.nodeType) and (not pyxb.namespace.XMLSchema.nodeIsNamed(cn, 'annotation')):
172 return True
173 return False
174
176 """Walk all the children, extracting all text content and
177 catenating it into the return value.
178
179 Returns C{None} if no text content (including whitespace) is found.
180
181 This is mainly used to strip comments out of the content of complex
182 elements with simple types.
183
184 @rtype: C{unicode} or C{str}
185 """
186 text = []
187 for cn in node.childNodes:
188 if xml.dom.Node.TEXT_NODE == cn.nodeType:
189 text.append(cn.data)
190 elif xml.dom.Node.CDATA_SECTION_NODE == cn.nodeType:
191 text.append(cn.data)
192 elif xml.dom.Node.COMMENT_NODE == cn.nodeType:
193 pass
194 else:
195 raise BadDocumentError('Non-text node %s found in content' % (cn,))
196 if 0 == len(text):
197 return None
198 return ''.join(text)
199
201 """Class holding information relevant to generating the namespace aspects
202 of a DOM instance."""
203
204 __namespaces = None
205
206
207 __namespacePrefixCounter = None
208
210 """The registered default namespace.
211
212 @rtype: L{pyxb.namespace.Namespace}
213 """
214 return self.__defaultNamespace
215 __defaultNamespace = None
216
218 """Set the default namespace for the generated document.
219
220 Even if invoked post construction, the default namespace will affect
221 the entire document, as all namespace declarations are placed in the
222 document root.
223
224 @param default_namespace: The namespace to be defined as the default
225 namespace in the top-level element of the document. May be provided
226 as a real namespace, or just its URI.
227 @type default_namespace: L{pyxb.namespace.Namespace} or C{str} or
228 C{unicode}.
229 """
230
231 if self.__defaultNamespace is not None:
232 del self.__namespaces[self.__defaultNamespace]
233 if isinstance(default_namespace, basestring):
234 default_namespace = pyxb.namespace.NamespaceForURI(default_namespace, create_if_missing=True)
235 if (default_namespace is not None) and default_namespace.isAbsentNamespace():
236 raise pyxb.UsageError('Default namespace must not be an absent namespace')
237 self.__defaultNamespace = default_namespace
238 if self.__defaultNamespace is not None:
239 self.__namespaces[self.__defaultNamespace] = None
240
242 """Return a map from Namespace instances to the prefix by which they
243 are represented in the DOM document."""
244 return self.__namespacePrefixMap.copy()
245 __namespacePrefixMap = None
246
248 """Add the given namespace as one to be used in this document.
249
250 @param namespace: The namespace to be associated with the document.
251 @type namespace: L{pyxb.namespace.Namespace}
252
253 @keyword prefix: Optional prefix to be used with this namespace. If
254 not provided, a unique prefix is generated or a standard prefix is
255 used, depending on the namespace.
256
257 @keyword add_to_map: If C{False} (default), the prefix is not added to
258 the namespace prefix map. If C{True} it is added. (Often, things
259 added to the prefix map are preserved across resets, which is often
260 not desired for specific prefix/namespace pairs).
261
262 @todo: ensure multiple namespaces do not share the same prefix
263 @todo: provide default prefix in L{pyxb.namespace.Namespace}
264 @todo: support multiple prefixes for each namespace
265 """
266 if not isinstance(namespace, pyxb.namespace.Namespace):
267 raise pyxb.UsageError('declareNamespace: must be given a namespace instance')
268 if namespace.isAbsentNamespace():
269 raise pyxb.UsageError('declareNamespace: namespace must not be an absent namespace')
270 if prefix is None:
271 prefix = self.__namespacePrefixMap.get(namespace)
272 if prefix is None:
273 prefix = namespace.prefix()
274 if prefix is None:
275 self.__namespacePrefixCounter += 1
276 prefix = 'ns%d' % (self.__namespacePrefixCounter,)
277 if prefix in self.__prefixes:
278 raise pyxb.LogicError('Prefix %s is already in use' % (prefix,))
279 self.__namespaces[namespace] = prefix
280 self.__prefixes.add(prefix)
281
282 if add_to_map:
283 self.__namespacePrefixMap[namespace] = prefix
284 return prefix
285
304
306 """Return the set of Namespace instances known to this instance."""
307 return self.__namespaces
308
309
310
313
314 - def reset (self, prefix_map=False):
326
327 - def __init__ (self, default_namespace=None, namespace_prefix_map=None, inherit_from=None):
328 """Create a new namespace declaration configuration.
329
330 @keyword default_namespace: Optional L{pyxb.namespace.Namespace}
331 instance that serves as the default namespace (applies to unqualified
332 names).
333
334 @keyword namespace_prefix_map: Optional map from
335 L{pyxb.namespace.Namespace} instances to C{str} values that are to be
336 used as the corresponding namespace prefix when constructing
337 U{qualified names<http://www.w3.org/TR/1999/REC-xml-names-19990114/#dt-qname>}.
338
339 @keyword inherit_from: Optional instance of this class from which
340 defaults are inherited. Inheritance is overridden by values of other
341 keywords in the initializer.
342 """
343 self.__prefixes = set()
344 self.__namespacePrefixCounter = 0
345 self.__namespaces = { }
346 self.__defaultNamespace = None
347 self.__resetNamespacePrefixMap()
348 if inherit_from is not None:
349 if default_namespace is None:
350 default_namespace = inherit_from.defaultNamespace()
351 self.__namespacePrefixMap.update(inherit_from.__namespacePrefixMap)
352 self.__namespacePrefixCount = inherit_from.__namespacePrefixCounter
353 self.__namespaces.update(inherit_from.__namespaces)
354 self.__prefixes.update(inherit_from.__prefixes)
355 if default_namespace is not None:
356 self.setDefaultNamespace(default_namespace)
357 prefixes = set(self.__namespacePrefixMap.values())
358 prefixes.update(self.__prefixes)
359 if namespace_prefix_map is not None:
360 prefixes = set()
361 for (ns, pfx) in namespace_prefix_map.items():
362 ns = pyxb.namespace.NamespaceInstance(ns)
363 if pfx in prefixes:
364 raise pyxb.LogicError('Cannot assign same prefix to multiple namespacess: %s' % (pfx,))
365 prefixes.add(pfx)
366 self.__namespacePrefixMap[ns] = pfx
367
369 """This holds DOM-related information used when generating a DOM tree from
370 a binding instance."""
371
373 """The DOMImplementation object to be used.
374
375 Defaults to L{pyxb.utils.domutils.GetDOMImplementation()}, but can be
376 overridden in the constructor call using the C{implementation}
377 keyword."""
378 return self.__implementation
379 __implementation = None
380
382 """Return the document generated using this instance."""
383 return self.__document
384 __document = None
385
387 """Indicates whether {xsi:type<http://www.w3.org/TR/xmlschema-1/#xsi_type>} should be added to all elements.
388
389 Certain WSDL styles and encodings seem to require explicit notation of
390 the type of each element, even if it was specified in the schema.
391
392 This value can only be set in the constructor."""
393 return self.__requireXSIType
394 __requireXSIType = None
395
397 """Reset this instance to the state it was when created.
398
399 This creates a new root document with no content, and flushes the list
400 of namespaces for the document. The defaultNamespace and
401 requireXSIType are not modified."""
402 self.__document = self.implementation().createDocument(None, None, None)
403 self.__namespaceSupport.reset(**kw)
404
405 @classmethod
407 """Reset the global defaults for default/prefix/namespace informmation."""
408 self.__NamespaceSupport.reset(**kw)
409
410 - def __init__ (self, implementation=None, default_namespace=None, require_xsi_type=False, namespace_prefix_map=None):
411 """Create a new instance used for building a single document.
412
413 @keyword implementation: The C{xml.dom} implementation to use.
414 Defaults to the one selected by L{GetDOMImplementation}.
415
416 @keyword default_namespace: The namespace to configure as the default
417 for the document. If not provided, there is no default namespace.
418 @type default_namespace: L{pyxb.namespace.Namespace}
419
420 @keyword require_xsi_type: If C{True}, an U{xsi:type
421 <http://www.w3.org/TR/xmlschema-1/#xsi_type>} attribute should be
422 placed in every element.
423 @type require_xsi_type: C{bool}
424
425 @keyword namespace_prefix_map: A map from pyxb.namespace.Namespace
426 instances to the preferred prefix to use for the namespace in xmlns
427 declarations. The default one assigns 'xsi' for the XMLSchema
428 instance namespace.
429 @type namespace_prefix_map: C{map} from L{pyxb.namespace.Namespace} to C{str}
430
431 @raise pyxb.LogicError: the same prefix is associated with multiple
432 namespaces in the C{namespace_prefix_map}.
433
434 """
435 if implementation is None:
436 implementation = GetDOMImplementation()
437 self.__implementation = implementation
438 self.__requireXSIType = require_xsi_type
439 self.__namespaceSupport = _BDSNamespaceSupport(default_namespace, namespace_prefix_map, inherit_from=self.__NamespaceSupport)
440 self.reset()
441
442 __namespaceSupport = None
443 __NamespaceSupport = _BDSNamespaceSupport()
444
445
449 @classmethod
453
456 @classmethod
459
463 @classmethod
467
471
475 @classmethod
479
500
502 """Do the final cleanup after generating the tree. This makes sure
503 that the document element includes XML Namespace declarations for all
504 namespaces referenced in the tree.
505
506 @return: The document that has been created.
507 @rtype: C{xml.dom.Document}"""
508 for ( ns, pfx ) in self.__namespaceSupport.namespaces().items():
509 if pfx is None:
510 self.document().documentElement.setAttributeNS(pyxb.namespace.XMLNamespaces.uri(), 'xmlns', ns.uri())
511 else:
512 self.document().documentElement.setAttributeNS(pyxb.namespace.XMLNamespaces.uri(), 'xmlns:%s' % (pfx,), ns.uri())
513 return self.document()
514
547
548
549
550
551
552