Package pyxb :: Package utils :: Module domutils
[hide private]
[frames] | no frames]

Source Code for Module pyxb.utils.domutils

  1  # Copyright 2009, Peter A. Bigot 
  2  # 
  3  # Licensed under the Apache License, Version 2.0 (the "License"); you may 
  4  # not use this file except in compliance with the License. You may obtain a 
  5  # copy of the License at: 
  6  # 
  7  #            http://www.apache.org/licenses/LICENSE-2.0 
  8  # 
  9  # Unless required by applicable law or agreed to in writing, software 
 10  # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 
 11  # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 
 12  # License for the specific language governing permissions and limitations 
 13  # under the License. 
 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  # The DOM implementation to be used for all processing.  Default is whatever 
 22  # your Python install uses.  If it's minidom, it should work. 
 23  __DOMImplementation = xml.dom.getDOMImplementation() 
24 25 -def 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
36 -def SetDOMImplementation (dom_implementation):
37 """Override the default DOMImplementation object.""" 38 global __DOMImplementation 39 __DOMImplementation = dom_implementation 40 return __DOMImplementation
41
42 # Unfortunately, the DOMImplementation interface doesn't provide a parser. So 43 # abstract this in case somebody wants to substitute a different one. Haven't 44 # decided how to express that yet. 45 -def StringToDOM (text, **kw):
46 """Convert string to a DOM instance. 47 48 @see: L{pyxb._SetXMLStyle}.""" 49 if pyxb.XMLStyle_minidom == pyxb._XMLStyle: 50 return xml.dom.minidom.parseString(text) 51 import saxdom 52 return saxdom.parseString(text, **kw)
53
54 -def NodeAttribute (node, attribute_ncname, attribute_ns=None):
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
77 -def LocateUniqueChild (node, tag, absent_ok=True, namespace=pyxb.namespace.XMLSchema):
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
107 -def LocateMatchingChildren (node, tag, namespace=pyxb.namespace.XMLSchema):
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
127 -def LocateFirstChildElement (node, absent_ok=True, require_unique=False, ignore_annotations=True):
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
164 -def HasNonAnnotationChild (node):
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
175 -def ExtractTextContent (node):
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
200 -class _BDSNamespaceSupport (object):
201 """Class holding information relevant to generating the namespace aspects 202 of a DOM instance.""" 203 # Namespace declarations required on the top element 204 __namespaces = None 205 206 # Integer counter to help generate unique namespace prefixes 207 __namespacePrefixCounter = None 208
209 - def defaultNamespace (self):
210 """The registered default namespace. 211 212 @rtype: L{pyxb.namespace.Namespace} 213 """ 214 return self.__defaultNamespace
215 __defaultNamespace = None 216
217 - def setDefaultNamespace (self, default_namespace):
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
241 - def namespacePrefixMap (self):
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
247 - def declareNamespace (self, namespace, prefix=None, add_to_map=False):
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 #print '%x declared namespace %s as %s' % (id(self), namespace, prefix) 282 if add_to_map: 283 self.__namespacePrefixMap[namespace] = prefix 284 return prefix
285
286 - def namespacePrefix (self, namespace):
287 """Return the prefix to be used for the given namespace. 288 289 This will L{declare <declareNamespace>} the namespace if it has not 290 yet been observed. 291 292 @param namespace: The namespace for which a prefix is needed. If the 293 provided namespace is C{None} or an absent namespace, the C{None} 294 value will be returned as the corresponding prefix. 295 """ 296 297 if (namespace is None) or namespace.isAbsentNamespace(): 298 return None 299 if isinstance(namespace, basestring): 300 namespace = pyxb.namespace.NamespaceForURI(namespace, create_if_missing=True) 301 if not (namespace in self.__namespaces): 302 return self.declareNamespace(namespace) 303 return self.__namespaces[namespace]
304
305 - def namespaces (self):
306 """Return the set of Namespace instances known to this instance.""" 307 return self.__namespaces
308 309 # Restore the namespace map to its default, which is the undeclared 310 # namespace for XML schema instances (C{xsi}
311 - def __resetNamespacePrefixMap (self):
313
314 - def reset (self, prefix_map=False):
315 """Reset this instance to the state it was when created. 316 317 This flushes the list of namespaces for the document. The 318 defaultNamespace is not modified.""" 319 self.__namespaces = { } 320 if self.__defaultNamespace is not None: 321 self.__namespaces[self.__defaultNamespace] = None 322 self.__prefixes = set() 323 self.__namespacePrefixCounter = 0 324 if prefix_map: 325 self.__resetNamespacePrefixMap()
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
368 -class BindingDOMSupport (object):
369 """This holds DOM-related information used when generating a DOM tree from 370 a binding instance.""" 371
372 - def implementation (self):
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
381 - def document (self):
382 """Return the document generated using this instance.""" 383 return self.__document
384 __document = None 385
386 - def requireXSIType (self):
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
396 - def reset (self, **kw):
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
406 - def Reset (self, **kw):
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 # Namespace declarations required on the top element
446 - def defaultNamespace (self):
447 """The default namespace for this instance""" 448 return self.__namespaceSupport.defaultNamespace()
449 @classmethod
450 - def DefaultNamespace (cls):
451 """The global default namespace (used on instance creation if not overridden)""" 452 return cls.__NamespaceSupport.defaultNamespace()
453
454 - def setDefaultNamespace (self, default_namespace):
455 return self.__namespaceSupport.setDefaultNamespace(default_namespace)
456 @classmethod
457 - def SetDefaultNamespace (cls, default_namespace):
458 return cls.__NamespaceSupport.setDefaultNamespace(default_namespace)
459
460 - def declareNamespace (self, namespace, prefix=None):
461 """Declare a namespace within this instance only.""" 462 return self.__namespaceSupport.declareNamespace(namespace, prefix)
463 @classmethod
464 - def DeclareNamespace (cls, namespace, prefix=None):
465 """Declare a namespace that will be added to each created instance.""" 466 return cls.__NamespaceSupport.declareNamespace(namespace, prefix, add_to_map=True)
467
468 - def namespacePrefix (self, namespace):
469 """Obtain the prefix for the given namespace using this instance's configuration.""" 470 return self.__namespaceSupport.namespacePrefix(namespace)
471
472 - def namespacePrefixMap (self):
473 """Get the map from namespaces to prefixes for this instance""" 474 return self.__namespaceSupport.namespacePrefixMap().copy()
475 @classmethod
476 - def NamespacePrefixMap (cls):
477 """Get the map of default namespace-to-prefix mappings""" 478 return cls.__NamespaceSupport.namespacePrefixMap().copy()
479
480 - def addAttribute (self, element, expanded_name, value):
481 """Add an attribute to the given element. 482 483 @param element: The element to which the attribute should be added 484 @type element: C{xml.dom.Element} 485 @param expanded_name: The name of the attribute. This may be a local 486 name if the attribute is not in a namespace. 487 @type expanded_name: L{pyxb.namespace.Namespace} or C{str} or C{unicode} 488 @param value: The value of the attribute 489 @type value: C{str} or C{unicode} 490 """ 491 name = expanded_name 492 namespace = None 493 if isinstance(name, pyxb.namespace.ExpandedName): 494 name = expanded_name.localName() 495 namespace = expanded_name.namespace() 496 prefix = self.namespacePrefix(namespace) 497 if prefix is not None: 498 name = '%s:%s' % (prefix, name) 499 element.setAttributeNS(namespace, name, value)
500
501 - def finalize (self):
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
515 - def createChildElement (self, expanded_name, parent=None):
516 """Create a new element node in the tree. 517 518 @param expanded_name: The name of the element. A plain string 519 indicates a name in no namespace. 520 @type expanded_name: L{pyxb.namespace.ExpandedName} or C{str} or C{unicode} 521 522 @keyword parent: The node in the tree that will serve as the child's 523 parent. If C{None}, the document element is used. (If there is no 524 document element, then this call creates it as a side-effect.) 525 526 @return: A newly created DOM element 527 @rtype: C{xml.dom.Element} 528 """ 529 530 if parent is None: 531 parent = self.document().documentElement 532 if parent is None: 533 parent = self.__document 534 if isinstance(expanded_name, (str, unicode)): 535 expanded_name = pyxb.namespace.ExpandedName(None, expanded_name) 536 if not isinstance(expanded_name, pyxb.namespace.ExpandedName): 537 raise pyxb.LogicError('Invalid type %s for expanded name' % (type(expanded_name),)) 538 ns = expanded_name.namespace() 539 name = expanded_name.localName() 540 pfx = self.namespacePrefix(ns) 541 if pfx is not None: 542 ns_uri = ns.uri() 543 name = '%s:%s' % (pfx, name) 544 ns_uri = xml.dom.EMPTY_NAMESPACE 545 element = self.__document.createElementNS(ns_uri, name) 546 return parent.appendChild(element)
547 548 549 ## Local Variables: 550 ## fill-column:78 551 ## End: 552