Package pyxb :: Package binding :: Module saxer |
[hide private]
[frames] | no frames]

# Source Code for Module pyxb.binding.saxer

      1  # -*- coding: utf-8 -*-
      2  # Copyright 2009-2013, Peter A. Bigot
      3  #
      4  # Licensed under the Apache License, Version 2.0 (the "License"); you may
      5  # not use this file except in compliance with the License. You may obtain a
      6  # copy of the License at:
      7  #
      8  #            http://www.apache.org/licenses/LICENSE-2.0
      9  #
     10  # Unless required by applicable law or agreed to in writing, software
     11  # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     12  # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
     13  # License for the specific language governing permissions and limitations
     14  # under the License.
     15
     16  """This module contains support for generating bindings from an XML stream
     17  using a SAX parser."""
     18
     19  import xml.dom
     20  import pyxb.namespace
     21  import pyxb.utils.saxutils
     22  import pyxb.utils.saxdom
     23  import pyxb.utils.utility
     24  from pyxb.binding import basis
     25  from pyxb.namespace.builtin import XMLSchema_instance as XSI
     26  import logging
     27
     28  _log = logging.getLogger(__name__)
     29
     30 -class _SAXElementState (pyxb.utils.saxutils.SAXElementState):
     31      """State required to generate bindings for a specific element.
     32
     33      If the document being parsed includes references to unrecognized elements,
     34      a DOM instance of the element and its content is created and treated as a
     35      wildcard element.
     36      """
     37
     38      # An expanded name corresponding to xsi:nil
     39      __XSINilTuple = XSI.nil.uriTuple()
     40
     41      # The binding instance being created for this element.  When the
     42      # element type has simple content, the binding instance cannot be
     43      # created until the end of the element has been reached and the
     44      # content of the element has been processed accumulated for use in
     45      # the instance constructor.  When the element type has complex
     46      # content, the binding instance must be created at the start of
     47      # the element, so contained elements can be properly stored.
     48      __bindingInstance = None
     49
     50      # The schema binding for the element being constructed.
     51      __elementBinding = None
     52
     53 -    def setElementBinding (self, element_binding):
     54          """Record the binding to be used for this element.
     55
     56          Generally ignored, except at the top level this is the only way to
     57          associate a binding instance created from an xsi:type description with
     58          a specific element."""
     59          self.__elementBinding = element_binding
     60
     61      # The nearest enclosing complex type definition
     62 -    def enclosingCTD (self):
     63          """The nearest enclosing complex type definition, as used for
     64          resolving local element/attribute names.
     65
     66          @return: An instance of L{basis.complexTypeDefinition}, or C{None} if
     67          the element is top-level
     68          """
     69          return self.__enclosingCTD
     70      __enclosingCTD = None
     71
     72      # The factory that is called to create a binding instance for this
     73      # element; None if the binding instance was created at the start
     74      # of the element.
     75      __delayedConstructor = None
     76
     77      # An xml.sax.xmlreader.Attributes instance providing the
     78      # attributes for the element.
     79      __attributes = None
     80
     81      # An xml.dom.Node corresponding to the (sub-)document
     82      __domDocument = None
     83
     84      __domDepth = None
     85
     86 -    def __init__ (self, **kw):
     87          super(_SAXElementState, self).__init__(**kw)
     88          self.__bindingInstance = None
     89          parent_state = self.parentState()
     90          if isinstance(parent_state, _SAXElementState):
     91              self.__enclosingCTD = parent_state.enclosingCTD()
     92              self.__domDocument = parent_state.__domDocument
     93              if self.__domDocument is not None:
     94                  self.__domDepth = parent_state.__domDepth + 1
     95
     96 -    def setEnclosingCTD (self, enclosing_ctd):
     97          """Set the enclosing complex type definition for this element.
     98
     99          @param enclosing_ctd: The scope for a local element.
    100          @type enclosing_ctd: L{basis.complexTypeDefinition}
    101          @return: C{self}
    102          """
    103          self.__enclosingCTD = enclosing_ctd
    104
    105      # Create the binding instance for this element.
    106 -    def __constructElement (self, new_object_factory, attrs, content=None):
    107          kw = { '_from_xml' : True,
    108                 '_location' : self.location() }
    109
    110          # Note whether the node is marked nil
    111          if self.__XSINilTuple in attrs:
    112              kw['_nil'] = pyxb.binding.datatypes.boolean(attrs.getValue(self.__XSINilTuple))
    113
    114          if content is None:
    115              content = []
    116          self.__bindingInstance = new_object_factory(*content, **kw)
    117          if isinstance(self.__bindingInstance, pyxb.utils.utility.Locatable_mixin):
    118              self.__bindingInstance._setLocation(self.location())
    119
    120          # Record the namespace context so users of the binding can
    121          # interpret QNames within the attributes and content.
    122          self.__bindingInstance._setNamespaceContext(self.__namespaceContext)
    123
    124          # Set instance attributes
    125          # NB: attrs implements the SAX AttributesNS interface, meaning
    126          # that names are pairs of (namespaceURI, localName), just like we
    127          # want them to be.
    128          for attr_name in self.__attributes.getNames():
    129              attr_en = pyxb.namespace.ExpandedName(attr_name)
    130              # Ignore xmlns and xsi attributes; we've already handled those
    131              if attr_en.namespace() in ( pyxb.namespace.XMLNamespaces, XSI ):
    132                  continue
    133              # The binding instance may be a simple type that does not support
    134              # attributes; the following raises an exception in that case.
    135              self.__bindingInstance._setAttribute(attr_en, attrs.getValue(attr_name))
    136
    137          return self.__bindingInstance
    138
    139 -    def inDOMMode (self):
    140          return self.__domDocument is not None
    141
    142 -    def enterDOMMode (self, attrs):
    143          """Actions upon first encountering an element for which we cannot create a binding.
    144
    145          Invoking this transitions the parser into DOM mode, creating a new DOM
    146          document that will represent this element including its content."""
    147          assert not self.__domDocument
    148          self.__domDocument = pyxb.utils.saxdom.Document(namespace_context=self.namespaceContext())
    149          self.__domDepth = 0
    150          return self.startDOMElement(attrs)
    151
    152 -    def startDOMElement (self, attrs):
    153          """Actions upon entering an element that is part of a DOM subtree."""
    154          self.__domDepth += 1
    155          self.__attributes = pyxb.utils.saxdom.NamedNodeMap()
    156          ns_ctx = self.namespaceContext()
    157          for name in attrs.getNames():
    158              attr_en = pyxb.namespace.ExpandedName(name)
    159              self.__attributes._addItem(pyxb.utils.saxdom.Attr(expanded_name=attr_en, namespace_context=ns_ctx, value=attrs.getValue(name), location=self.location()))
    160
    161 -    def endDOMElement (self):
    162          """Actions upon leaving an element that is part of a DOM subtree."""
    163          ns_ctx = self.namespaceContext()
    164          element = pyxb.utils.saxdom.Element(namespace_context=ns_ctx, expanded_name=self.expandedName(), attributes=self.__attributes, location=self.location())
    165          for info in self.content():
    166              if isinstance(info.item, xml.dom.Node):
    167                  element.appendChild(info.item)
    168              else:
    169                  element.appendChild(pyxb.utils.saxdom.Text(info.item, namespace_context=ns_ctx))
    170          self.__domDepth -= 1
    171          if 0 == self.__domDepth:
    172              self.__domDocument.appendChild(element)
    173              #pyxb.utils.saxdom._DumpDOM(self.__domDocument)
    174              self.__domDepth = None
    175              self.__domDocument = None
    176          parent_state = self.parentState()
    177          parent_state.addElementContent(self.location(), element, None)
    178          return element
    179
    180 -    def startBindingElement (self, type_class, new_object_factory, element_decl, attrs):
    181          """Actions upon entering an element that will produce a binding instance.
    182
    183          The element use is recorded.  If the type is a subclass of
    184          L{basis.simpleTypeDefinition}, a delayed constructor is recorded so
    185          the binding instance can be created upon completion of the element;
    186          otherwise, a binding instance is created and stored.  The attributes
    187          are used to initialize the binding instance (now, or upon element
    188          end).
    189
    190          @param type_class: The Python type of the binding instance
    191          @type type_class: subclass of L{basis._TypeBinding_mixin}
    192          @param new_object_factory: A callable object that creates an instance of the C{type_class}
    193          @param element_decl: The element use with which the binding instance is associated.  Will be C{None} for top-level elements
    194          @type element_decl: L{basis.element}
    195          @param attrs: The XML attributes associated with the element
    196          @type attrs: C{xml.sax.xmlreader.Attributes}
    197          @return: The generated binding instance, or C{None} if creation is delayed
    198          """
    199          self.__delayedConstructor = None
    200          self.__elementDecl = element_decl
    201          self.__attributes = attrs
    202          if type_class._IsSimpleTypeContent():
    203              self.__delayedConstructor = new_object_factory
    204          else:
    205              self.__constructElement(new_object_factory, attrs)
    206          return self.__bindingInstance
    207
    208 -    def endBindingElement (self):
    209          """Perform any end-of-element processing.
    210
    211          For simple type instances, this creates the binding instance.
    212          @return: The generated binding instance
    213          """
    214          if self.__delayedConstructor is not None:
    215              args = []
    216              for info in self.content():
    217                  if info.maybe_element or (info.element_decl is not None):
    218                      raise pyxb.NonElementValidationError(info.item, info.location)
    219                  args.append(info.item)
    220              self.__constructElement(self.__delayedConstructor, self.__attributes, args)
    221          else:
    222              for info in self.content():
    223                  self.__bindingInstance.append(info.item,
    224                                                _element_decl=info.element_decl,
    225                                                _maybe_element=info.maybe_element,
    226                                                _location=info.location)
    227          parent_state = self.parentState()
    228          if parent_state is not None:
    229              parent_state.addElementContent(self.location(), self.__bindingInstance, self.__elementDecl)
    230          # As CreateFromDOM does, validate the resulting element
    231          if self.__bindingInstance._element() is None:
    232              self.__bindingInstance._setElement(self.__elementBinding)
    233          return self.__bindingInstance._postDOMValidate()
    234
    235 -class PyXBSAXHandler (pyxb.utils.saxutils.BaseSAXHandler):
    236      """A SAX handler class which generates a binding instance for a document
    237      through a streaming parser.
    238
    239      An example of using this to parse the document held in the (unicode) text
    240      value C{xmlt} is::
    241
    242        import pyxb.binding.saxer
    243        import io
    244
    245        saxer = pyxb.binding.saxer.make_parser()
    246        handler = saxer.getContentHandler()
    247        saxer.parse(io.StringIO(xmlt))
    248        instance = handler.rootObject()
    249
    250      """
    251
    252      # Whether invocation of handler methods should be traced
    253      __trace = False
    254
    255      # An expanded name corresponding to xsi:type
    256      __XSITypeTuple = XSI.type.uriTuple()
    257
    258      __domHandler = None
    259      __domDepth = None
    260
    261 -    def rootObject (self):
    262          """Return the binding object corresponding to the top-most
    263          element in the document
    264
    265          @return: An instance of L{basis._TypeBinding_mixin} (most usually a
    266          L{basis.complexTypeDefinition}.
    267
    268          @raise pyxb.[UnrecognizedDOMRootNodeError: No binding could be found to
    269          match the top-level element in the document."""
    270          if not isinstance(self.__rootObject, basis._TypeBinding_mixin):
    271              # Happens if the top-level element got processed as a DOM instance.
    272              assert isinstance(self.__rootObject, xml.dom.Node)
    273              raise pyxb.UnrecognizedDOMRootNodeError(self.__rootObject)
    274          return self.__rootObject._postDOMValidate()
    275      __rootObject = None
    276
    277 -    def reset (self):
    278          """Reset the state of the handler in preparation for processing a new
    279          document.
    280
    281          @return: C{self}
    282          """
    283          super(PyXBSAXHandler, self).reset()
    284          self.__rootObject = None
    285          return self
    286
    287 -    def __init__ (self, **kw):
    288          """Create a parser instance for converting XML to bindings.
    289
    290          @keyword element_state_constructor: Overridden with the value
    291          L{_SAXElementState} before invoking the L{superclass
    292          constructor<pyxb.utils.saxutils.BaseSAXHandler.__init__>}.
    293          """
    294
    295          kw.setdefault('element_state_constructor', _SAXElementState)
    296          super(PyXBSAXHandler, self).__init__(**kw)
    297          self.reset()
    298
    299 -    def startElementNS (self, name, qname, attrs):
    300          (this_state, parent_state, ns_ctx, name_en) = super(PyXBSAXHandler, self).startElementNS(name, qname, attrs)
    301
    302          # Delegate processing if in DOM mode
    303          if this_state.inDOMMode():
    304              return this_state.startDOMElement(attrs)
    305
    306          # Resolve the element within the appropriate context.  Note
    307          # that global elements have no use, only the binding.
    308          if parent_state.enclosingCTD() is not None:
    309              (element_binding, element_decl) = parent_state.enclosingCTD()._ElementBindingDeclForName(name_en)
    310          else:
    311              element_decl = None
    312              element_binding = name_en.elementBinding()
    313          this_state.setElementBinding(element_binding)
    314
    315          # Non-root elements should have an element use, from which we can
    316          # extract the binding if we couldn't find one elsewhere.  (Keep any
    317          # current binding, since it may be a member of a substitution group.)
    318          if (element_decl is not None) and (element_binding is None):
    319              assert self.__rootObject is not None
    320              element_binding = element_decl.elementBinding()
    321              assert element_binding is not None
    322
    323          # Start knowing nothing
    324          type_class = None
    325          if element_binding is not None:
    326              element_binding = element_binding.elementForName(name)
    327              type_class = element_binding.typeDefinition()
    328
    329          # Process an xsi:type attribute, if present
    330          if self.__XSITypeTuple in attrs:
    331              (did_replace, type_class) = XSI._InterpretTypeAttribute(attrs.getValue(self.__XSITypeTuple), ns_ctx, self.fallbackNamespace(), type_class)
    332              if did_replace:
    333                  element_binding = None
    334
    335          if type_class is None:
    336              # Bother.  We don't know what this thing is.  But that's not an
    337              # error, if the schema accepts wildcards.  For consistency with
    338              # the DOM-based interface, we need to build a DOM node.
    339              return this_state.enterDOMMode(attrs)
    340
    341          if element_binding is not None:
    342              # Invoke binding __call__ method not Factory, so can check for
    343              # abstract elements.
    344              new_object_factory = element_binding
    345          else:
    346              new_object_factory = type_class.Factory
    347
    348          # Update the enclosing complex type definition for this
    349          # element state.
    350          assert type_class is not None
    351          if issubclass(type_class, pyxb.binding.basis.complexTypeDefinition):
    352              this_state.setEnclosingCTD(type_class)
    353          else:
    354              this_state.setEnclosingCTD(parent_state.enclosingCTD())
    355
    356          # Process the element start.  This may or may not return a
    357          # binding object.
    358          binding_object = this_state.startBindingElement(type_class, new_object_factory, element_decl, attrs)
    359
    360          # If the top-level element has complex content, this sets the
    361          # root object.  If it has simple content, see endElementNS.
    362          if self.__rootObject is None:
    363              self.__rootObject = binding_object
    364
    365 -    def endElementNS (self, name, qname):
    366          this_state = super(PyXBSAXHandler, self).endElementNS(name, qname)
    367          if this_state.inDOMMode():
    368              # Delegate processing if in DOM mode.  Note that completing this
    369              # element may take us out of DOM mode.  In any case, the returned
    370              # binding object is a DOM element instance.
    371              binding_object = this_state.endDOMElement()
    372          else:
    373              # Process the element end.  This will return a binding object,
    374              # either the one created at the start or the one created at
    375              # the end.
    376              binding_object = this_state.endBindingElement()
    377          assert binding_object is not None
    378
    379          # If we don't have a root object, save it.  No, there is not a
    380          # problem doing this on the close of the element.  If the
    381          # top-level element has complex content, the object was
    382          # created on start, and the root object has been assigned.  If
    383          # it has simple content, then there are no internal elements
    384          # that could slip in and set this before we get to it here.
    385          #
    386          # Unless we're still in DOM mode, in which case this isn't really the
    387          # root object.  Then the real root object will be the one that caused
    388          # us to enter DOM mode.
    389          if (self.__rootObject is None) and not this_state.inDOMMode():
    390              self.__rootObject = binding_object
    391
    392 -def make_parser (*args, **kw):
    393      """Extend L{pyxb.utils.saxutils.make_parser} to change the default
    394      C{content_handler_constructor} to be L{PyXBSAXHandler}.
    395      """
    396      kw.setdefault('content_handler_constructor', PyXBSAXHandler)
    397      return pyxb.utils.saxutils.make_parser(*args, **kw)
    398
    399  ## Local Variables:
    400  ## fill-column:78
    401  ## End:
    402