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