Optimize qname_to_prefixed() and get_namespace() helpers

- use_empty optional argument added to qname_to_prefixed()
This commit is contained in:
Davide Brunato 2019-10-23 09:47:49 +02:00
parent c075ff22e5
commit 6942be8ac9
4 changed files with 41 additions and 17 deletions

View File

@ -70,6 +70,9 @@ NAMESPACE_PATTERN = re.compile(r'{([^}]*)}')
def get_namespace(name):
if not name or name[0] != '{':
return ''
try:
return NAMESPACE_PATTERN.match(name).group(1)
except (AttributeError, TypeError):

View File

@ -224,34 +224,38 @@ def local_name(qname):
return qname
def qname_to_prefixed(qname, namespaces):
def qname_to_prefixed(qname, namespaces, use_empty=True):
"""
Transforms a fully qualified name into a prefixed name using a namespace map.
Returns the *qname* argument if it's not a fully qualified name or if it has
boolean value `False`.
Maps a QName in extended format to a QName in prefixed format.
Do not change local names and QNames in prefixed format.
:param qname: an extended QName or a local name.
:param qname: a QName or a local name.
:param namespaces: a map from prefixes to namespace URIs.
:param use_empty: if `True` use the empty prefix for mapping.
:return: a QName in prefixed format or a local name.
"""
if not qname:
if not qname or qname[0] != '{':
return qname
namespace = get_namespace(qname)
for prefix, uri in sorted(filter(lambda x: x[1] == namespace, namespaces.items()), reverse=True):
if not uri:
return '%s:%s' % (prefix, qname) if prefix else qname
elif prefix:
return qname.replace('{%s}' % uri, '%s:' % prefix)
else:
return qname.replace('{%s}' % uri, '')
prefixes = [x for x in namespaces if namespaces[x] == namespace]
if not prefixes:
return qname
elif prefixes[0]:
return '%s:%s' % (prefixes[0], qname.split('}', 1)[1])
elif len(prefixes) > 1:
return '%s:%s' % (prefixes[1], qname.split('}', 1)[1])
elif use_empty:
return qname.split('}', 1)[1]
else:
return qname
def qname_to_extended(qname, namespaces):
"""
Converts a QName in prefixed format or a local name to the extended QName format.
Maps a QName in prefixed format or a local name to the extended QName format.
Local names are mapped if *namespaces* has a not empty default namespace.
:param qname: a QName in prefixed format or a local name.
:param namespaces: a map from prefixes to namespace URIs.

View File

@ -40,6 +40,9 @@ class TestHelpers(unittest.TestCase):
self.assertEqual(get_namespace(XSD_SIMPLE_TYPE), XSD_NAMESPACE)
self.assertEqual(get_namespace(''), '')
self.assertEqual(get_namespace(None), '')
self.assertEqual(get_namespace('{}name'), '')
self.assertEqual(get_namespace('{ }name'), ' ')
self.assertEqual(get_namespace('{ ns }name'), ' ns ')
def test_get_qname_functions(self):
self.assertEqual(get_qname(XSD_NAMESPACE, 'element'), XSD_ELEMENT)
@ -81,8 +84,21 @@ class TestHelpers(unittest.TestCase):
self.assertEqual(qname_to_prefixed('', {}), '')
self.assertEqual(qname_to_prefixed('type', {'': XSI_NAMESPACE}), 'type')
self.assertEqual(qname_to_prefixed('type', {'ns': ''}), 'ns:type')
self.assertEqual(qname_to_prefixed('type', {'': ''}), 'type')
self.assertEqual(qname_to_prefixed('{}type', {'': ''}), 'type')
self.assertEqual(qname_to_prefixed('{}type', {'': ''}, use_empty=False), '{}type')
# Attention! in XML the empty namespace (that means no namespace) can be
# associated only with empty prefix, so these cases should never happen.
self.assertEqual(qname_to_prefixed('{}type', {'p': ''}), 'p:type')
self.assertEqual(qname_to_prefixed('type', {'p': ''}), 'type')
self.assertEqual(qname_to_prefixed('{ns}type', {'': 'ns'}, use_empty=True), 'type')
self.assertEqual(qname_to_prefixed('{ns}type', {'': 'ns'}, use_empty=False), '{ns}type')
self.assertEqual(qname_to_prefixed('{ns}type', {'': 'ns', 'p': 'ns'}, use_empty=True), 'p:type')
self.assertEqual(qname_to_prefixed('{ns}type', {'': 'ns', 'p': 'ns'}, use_empty=False), 'p:type')
self.assertEqual(qname_to_prefixed('{ns}type', {'': 'ns', 'p': 'ns0'}, use_empty=True), 'type')
self.assertEqual(qname_to_prefixed('{ns}type', {'': 'ns', 'p': 'ns0'}, use_empty=False), '{ns}type')
def test_get_xsd_annotation(self):
elem = etree_element(XSD_SCHEMA)

View File

@ -15,6 +15,7 @@ from __future__ import unicode_literals
from ..compat import PY3, string_base_type
from ..exceptions import XMLSchemaException, XMLSchemaWarning, XMLSchemaValueError
from ..namespaces import get_namespace
from ..qnames import qname_to_prefixed
from ..etree import etree_tostring, etree_getpath
from ..helpers import is_etree_element
@ -317,11 +318,11 @@ class XMLSchemaChildrenValidationError(XMLSchemaValidationError):
self.occurs = occurs
self.expected = expected
tag = qname_to_prefixed(elem.tag, validator.namespaces)
tag = qname_to_prefixed(elem.tag, validator.namespaces, use_empty=False)
if index >= len(elem):
reason = "The content of element %r is not complete." % tag
else:
child_tag = qname_to_prefixed(elem[index].tag, validator.namespaces)
child_tag = qname_to_prefixed(elem[index].tag, validator.namespaces, use_empty=False)
reason = "Unexpected child with tag %r at position %d." % (child_tag, index + 1)
if occurs and particle.is_missing(occurs):