Skip to content
This repository has been archived by the owner on Apr 29, 2023. It is now read-only.

Commit

Permalink
generate: replace incorrect use of default with fallback
Browse files Browse the repository at this point in the history
Commit 68ca552 introduced an error in
nomenclature by confusing the concept of a default namespace with the
concept of a fallback namespace.  The two are not at all the same thing.

A fallback namespace is used to associate an element or attribute
identified by an expanded name that has no namespace name with the
information set from a schema that had no target namespace.  The feature
exists because there is no other way to perform that association.

A default namespace is used to provide a default namespace name if there
is no namespace prefix on the tag.  In valid XML this is specified
through an `xmlns=uri` attribute in a start tag.

To this point PyXB has not accepted as valid any XML where the root
element was in a non-absent namespace that was not explicitly
identified.  PyXB depends on the XML parser to determine the namespace
name of an expanded name.  It is only when that name is absent (and so
could not have been provided by valid XML) that PyXB will associate the
corresponding information set from a contextually-specified absent
Namespace.  It is outside PyXB's scope to override namespaces in a
situation where valid XML requires a namespace name and the parser fails
to provide one.  Since xml.sax does not appear to provide a mechanism to
assign an initial default namespace that is not present in the XML text
there is no way to support the use case in issue #94 within PyXB.

This commit makes no intentional behavioral change, but to reduce future
confusion it changes the canonical keyword and positional argument to be
`fallback_namespace`.  It adds a temporary workaround to accept
`default_namespace` as a deprecated alias in case somebody's depending
on the existing behavior.

Closes #94.
  • Loading branch information
pabigot committed Feb 11, 2018
1 parent 817e4c1 commit 14737c2
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 18 deletions.
35 changes: 22 additions & 13 deletions pyxb/binding/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -1675,31 +1675,38 @@ def _finalizeModuleContents_vx (self, template_map):
''')
self.bindingIO().appendPrologBoilerplate(template_map)
self.bindingIO().prolog().append(self.bindingIO().expand('''
def CreateFromDocument (xml_text, default_namespace=None, location_base=None):
def CreateFromDocument (xml_text, fallback_namespace=None, location_base=None, default_namespace=None):
"""Parse the given XML and use the document element to create a
Python instance.
@param xml_text An XML document. This should be data (Python 2
str or Python 3 bytes), or a text (Python 2 unicode or Python 3
str) in the L{pyxb._InputEncoding} encoding.
@keyword default_namespace The L{pyxb.Namespace} instance to use as the
default namespace where there is no default namespace in scope.
If unspecified or C{None}, the namespace of the module containing
this function will be used.
@keyword fallback_namespace An absent L{pyxb.Namespace} instance
to use for unqualified names when there is no default namespace in
scope. If unspecified or C{None}, the namespace of the module
containing this function will be used, if it is an absent
namespace.
@keyword location_base: An object to be recorded as the base of all
L{pyxb.utils.utility.Location} instances associated with events and
objects handled by the parser. You might pass the URI from which
the document was obtained.
@keyword default_namespace An alias for @c fallback_namespace used
in PyXB 1.1.4 through 1.2.6. It behaved like a default namespace
only for absent namespaces.
"""
if pyxb.XMLStyle_saxer != pyxb._XMLStyle:
dom = pyxb.utils.domutils.StringToDOM(xml_text)
return CreateFromDOM(dom.documentElement, default_namespace=default_namespace)
if default_namespace is None:
default_namespace = Namespace.fallbackNamespace()
saxer = pyxb.binding.saxer.make_parser(fallback_namespace=default_namespace, location_base=location_base)
return CreateFromDOM(dom.documentElement)
if fallback_namespace is None:
fallback_namespace = default_namespace
if fallback_namespace is None:
fallback_namespace = Namespace.fallbackNamespace()
saxer = pyxb.binding.saxer.make_parser(fallback_namespace=fallback_namespace, location_base=location_base)
handler = saxer.getContentHandler()
xmld = xml_text
if isinstance(xmld, %{_TextType}):
Expand All @@ -1708,14 +1715,16 @@ def CreateFromDocument (xml_text, default_namespace=None, location_base=None):
instance = handler.rootObject()
return instance
def CreateFromDOM (node, default_namespace=None):
def CreateFromDOM (node, fallback_namespace=None, default_namespace=None):
"""Create a Python instance from the given DOM node.
The node tag must correspond to an element declaration in this module.
@deprecated: Forcing use of DOM interface is unnecessary; use L{CreateFromDocument}."""
if default_namespace is None:
default_namespace = Namespace.fallbackNamespace()
return pyxb.binding.basis.element.AnyCreateFromDOM(node, default_namespace)
if fallback_namespace is None:
fallback_namespace = default_namespace
if fallback_namespace is None:
fallback_namespace = Namespace.fallbackNamespace()
return pyxb.binding.basis.element.AnyCreateFromDOM(node, fallback_namespace)
''', **template_map))

Expand Down
85 changes: 85 additions & 0 deletions tests/trac/test-issue-0094.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging
import pyxb.binding.generate
import pyxb.utils.domutils
import xml.dom.minidom as dom

if __name__ == '__main__':
logging.basicConfig()
_log = logging.getLogger(__name__)

xsd = '''<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified"
targetNamespace="urn:test-issue-0094"
xmlns:tns="urn:test-issue-0094">
<xs:element name="MARKETPLACE">
<xs:complexType>
<xs:sequence>
<xs:element name="NAME" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="MARKETPLACE_POOL">
<xs:complexType>
<xs:sequence maxOccurs="1" minOccurs="1">
<xs:element ref="tns:MARKETPLACE" maxOccurs="unbounded" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
'''

# Fully qualified XML
fqXmlSample = '''<?xml version="1.0" encoding="UTF-8"?>
<ns:MARKETPLACE_POOL xmlns:ns="urn:test-issue-0094">
<ns:MARKETPLACE>
<ns:NAME>OpenNebula Public</ns:NAME>
</ns:MARKETPLACE>
</ns:MARKETPLACE_POOL>
'''

# Default-qualified XML
dqXmlSample = '''<?xml version="1.0" encoding="UTF-8"?>
<MARKETPLACE_POOL xmlns="urn:test-issue-0094">
<MARKETPLACE>
<NAME>OpenNebula Public</NAME>
</MARKETPLACE>
</MARKETPLACE_POOL>
'''

# XML that lacks any namespace association
nqXmlSample = '''<?xml version="1.0" encoding="UTF-8"?>
<MARKETPLACE_POOL>
<MARKETPLACE>
<NAME>OpenNebula Public</NAME>
</MARKETPLACE>
</MARKETPLACE_POOL>
'''

code = pyxb.binding.generate.GeneratePython(schema_text=xsd)
#open('code.py', 'w').write(code)
rv = compile(code, 'test', 'exec')
eval(rv)

import unittest

class TestIssue0094 (unittest.TestCase):
def testFullyQualified (self):
doc = CreateFromDocument(fqXmlSample);
self.assertEqual(doc.MARKETPLACE[0].NAME, "OpenNebula Public")

def testDefaultQualified (self):
doc = CreateFromDocument(dqXmlSample);
self.assertEqual(doc.MARKETPLACE[0].NAME, "OpenNebula Public")

def testNoQualifier (self):
with self.assertRaises(pyxb.UnrecognizedDOMRootNodeError) as cm:
doc = CreateFromDocument(nqXmlSample)

def testNoQualifierDefaulted (self):
with self.assertRaises(pyxb.UnrecognizedDOMRootNodeError) as cm:
doc = CreateFromDocument(nqXmlSample, default_namespace=Namespace)

if __name__ == '__main__':
unittest.main()
16 changes: 11 additions & 5 deletions tests/trac/trac-0119/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def testRoundTrip (self):
instance = absent.CreateFromDocument(xmld)
self.assertEqual(xmld, instance.toxml("utf-8"))

def testNoDefault (self):
def testNoFallback (self):
xmlt='''<?xml version="1.0"?>
<base:Message xmlns:base="urn:trac0119" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<command xsi:type="doit">
Expand All @@ -37,10 +37,16 @@ def testNoDefault (self):
instance = absent.CreateFromDocument(xmlt)
self.assertEqual('hi', instance.command.payload)
# Can resolve in base module if fallback namespace overridden
instance = base.CreateFromDocument(xmlt, fallback_namespace=absent.Namespace)
self.assertEqual('hi', instance.command.payload)
# Pre-1.2.7 positional argument works
instance = base.CreateFromDocument(xmlt, absent.Namespace)
self.assertEqual('hi', instance.command.payload)
# Deprecated pre-1.2.7 keyword argument works
instance = base.CreateFromDocument(xmlt, default_namespace=absent.Namespace)
self.assertEqual('hi', instance.command.payload)

def testDefault (self):
def testFallback (self):
xmlt='''<?xml version="1.0"?>
<Message xmlns="urn:trac0119" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<command xmlns="" xsi:type="doit"> <!-- undefine the default namespace -->
Expand All @@ -54,10 +60,10 @@ def testDefault (self):
instance = absent.CreateFromDocument(xmlt)
self.assertEqual('hi', instance.command.payload)
# Can resolve in base module if fallback namespace overridden
instance = base.CreateFromDocument(xmlt, default_namespace=absent.Namespace)
instance = base.CreateFromDocument(xmlt, fallback_namespace=absent.Namespace)
self.assertEqual('hi', instance.command.payload)

def testUndefineNondefault (self):
def testUndefineNonfallback (self):
xmlt='''<?xml version="1.0"?>
<base:Message xmlns:base="urn:trac0119" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<command xsi:type="doit" xmlns:base=""> <!-- undefine the base namespace -->
Expand All @@ -69,7 +75,7 @@ def testUndefineNondefault (self):
import xml.sax
self.assertRaises(xml.sax.SAXParseException, base.CreateFromDocument, xmlt)
self.assertRaises(xml.sax.SAXParseException, absent.CreateFromDocument, xmlt)
self.assertRaises(xml.sax.SAXParseException, base.CreateFromDocument, xmlt, default_namespace=absent.Namespace)
self.assertRaises(xml.sax.SAXParseException, base.CreateFromDocument, xmlt, fallback_namespace=absent.Namespace)

if __name__ == '__main__':
unittest.main()

0 comments on commit 14737c2

Please sign in to comment.