# -*- coding: utf-8 -*- # # Copyright (c), 2016-2019, SISSA (International School for Advanced Studies). # All rights reserved. # This file is distributed under the terms of the MIT License. # See the file 'LICENSE' in the root directory of the present # distribution, or http://opensource.org/licenses/MIT. # # @author Davide Brunato # """ This module contains namespace definitions for W3C core standards and namespace related classes. """ from __future__ import unicode_literals import os import re from .compat import MutableMapping, Mapping ### # Namespace URIs XSD_NAMESPACE = 'http://www.w3.org/2001/XMLSchema' "URI of the XML Schema Definition namespace (xs|xsd)" XSI_NAMESPACE = 'http://www.w3.org/2001/XMLSchema-instance' "URI of the XML Schema Instance namespace (xsi)" XML_NAMESPACE = 'http://www.w3.org/XML/1998/namespace' "URI of the XML namespace (xml)" XHTML_NAMESPACE = 'http://www.w3.org/1999/xhtml' XHTML_DATATYPES_NAMESPACE = 'http://www.w3.org/1999/xhtml/datatypes/' "URIs of the Extensible Hypertext Markup Language namespace (html)" XLINK_NAMESPACE = 'http://www.w3.org/1999/xlink' "URI of the XML Linking Language (XLink)" XSLT_NAMESPACE = "http://www.w3.org/1999/XSL/Transform" "URI of the XSL Transformations namespace (xslt)" HFP_NAMESPACE = 'http://www.w3.org/2001/XMLSchema-hasFacetAndProperty' "URI of the XML Schema has Facet and Property namespace (hfp)" VC_NAMESPACE = 'http://www.w3.org/2007/XMLSchema-versioning' "URI of the XML Schema Versioning namespace (vc)" ### # Schema location hints SCHEMAS_DIR = os.path.join(os.path.dirname(__file__), 'validators/schemas/') LOCATION_HINTS = { # Locally saved schemas HFP_NAMESPACE: os.path.join(SCHEMAS_DIR, 'XMLSchema-hasFacetAndProperty_minimal.xsd'), VC_NAMESPACE: os.path.join(SCHEMAS_DIR, 'XMLSchema-versioning_minimal.xsd'), XLINK_NAMESPACE: os.path.join(SCHEMAS_DIR, 'xlink.xsd'), XHTML_NAMESPACE: os.path.join(SCHEMAS_DIR, 'xhtml1-strict.xsd'), # Remote locations: contributors can propose additional official locations # for other namespaces for extending this list. XSLT_NAMESPACE: os.path.join(SCHEMAS_DIR, 'http://www.w3.org/2007/schema-for-xslt20.xsd'), } ### # Helper functions and classes 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): return '' class NamespaceResourcesMap(MutableMapping): """ Dictionary for storing information about namespace resources. The values are lists of strings. Setting an existing value appends the string to the value. Setting a value with a list sets/replaces the value. """ def __init__(self, *args, **kwargs): self._store = dict() self.update(*args, **kwargs) def __getitem__(self, uri): return self._store[uri] def __setitem__(self, uri, value): if isinstance(value, list): self._store[uri] = value[:] else: try: self._store[uri].append(value) except KeyError: self._store[uri] = [value] def __delitem__(self, uri): del self._store[uri] def __iter__(self): return iter(self._store) def __len__(self): return len(self._store) def __repr__(self): return repr(self._store) def clear(self): self._store.clear() class NamespaceMapper(MutableMapping): """ A class to map/unmap namespace prefixes to URIs. The :param namespaces: Initial data with namespace prefixes and URIs. """ def __init__(self, namespaces=None, register_namespace=None): self._namespaces = {} self.register_namespace = register_namespace if namespaces is not None: self.update(namespaces) def __getitem__(self, key): return self._namespaces[key] def __setitem__(self, key, value): self._namespaces[key] = value try: self.register_namespace(key, value) except (TypeError, ValueError): pass def __delitem__(self, key): del self._namespaces[key] def __iter__(self): return iter(self._namespaces) def __len__(self): return len(self._namespaces) @property def default_namespace(self): return self._namespaces.get('') def clear(self): self._namespaces.clear() def map_qname(self, qname): """ Converts an extended QName to the prefixed format. Only registered namespaces are mapped. :param qname: a QName in extended format or a local name. :return: a QName in prefixed format or a local name. """ try: if qname[0] != '{' or not self._namespaces: return qname except IndexError: return qname qname_uri = get_namespace(qname) for prefix, uri in self.items(): if uri != qname_uri: continue if prefix: self._namespaces[prefix] = uri return qname.replace(u'{%s}' % uri, u'%s:' % prefix) else: if uri: self._namespaces[prefix] = uri return qname.replace(u'{%s}' % uri, '') else: return qname def unmap_qname(self, qname, name_table=None): """ Converts a QName in prefixed format or a local name to the extended QName format. Local names are converted only if a default namespace is included in the instance. If a *name_table* is provided a local name is mapped to the default namespace only if not found in the name table. :param qname: a QName in prefixed format or a local name :param name_table: an optional lookup table for checking local names. :return: a QName in extended format or a local name. """ try: if qname[0] == '{' or not self: return qname except IndexError: return qname try: prefix, name = qname.split(':', 1) except ValueError: if not self._namespaces.get(''): return qname elif name_table is None or qname not in name_table: return '{%s}%s' % (self._namespaces.get(''), qname) else: return qname else: try: uri = self._namespaces[prefix] except KeyError: return qname else: return u'{%s}%s' % (uri, name) if uri else name def transfer(self, other): transferred = [] for k, v in other.items(): if k in self: if v != self[k]: continue else: self[k] = v transferred.append(k) for k in transferred: del other[k] class NamespaceView(Mapping): """ A read-only map for filtered access to a dictionary that stores objects mapped from QNames. """ def __init__(self, qname_dict, namespace_uri): self.target_dict = qname_dict self.namespace = namespace_uri if namespace_uri: self._key_fmt = '{' + namespace_uri + '}%s' else: self._key_fmt = '%s' def __getitem__(self, key): return self.target_dict[self._key_fmt % key] def __len__(self): return len(self.as_dict()) def __iter__(self): return iter(self.as_dict()) def __repr__(self): return '%s(%s)' % (self.__class__.__name__, str(self.as_dict())) def __contains__(self, key): return self._key_fmt % key in self.target_dict def __eq__(self, other): return self.as_dict() == dict(other.items()) def copy(self, **kwargs): return self.__class__(self, **kwargs) def as_dict(self, fqn_keys=False): if fqn_keys: return { k: v for k, v in self.target_dict.items() if self.namespace == get_namespace(k) } else: return { k if k[0] != '{' else k[k.rindex('}') + 1:]: v for k, v in self.target_dict.items() if self.namespace == get_namespace(k) }