Skip to content

Commit

Permalink
Closing the release v1.1.4
Browse files Browse the repository at this point in the history
  - Add get_primitive_type() to XMLSchemaProxy
  - Restore boolean_value() usage with an additional token=None argument
    to preserve compatibility with xmlschema v1.0.9
  - Remove XPathToken.boolean()
  - XSD_BUILTIN_TYPES sample values completed
  • Loading branch information
brunato committed Feb 21, 2019
1 parent 7539b5b commit 808d0ef
Show file tree
Hide file tree
Showing 9 changed files with 90 additions and 87 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ CHANGELOG
* Implementation of a full Static Analysis Phase at parse() level
* Schema-based static analysis for XPath 2.0 parsers using schema contexts
* Added ``XPathSchemaContext`` class for processing schema contexts
* Added boolean(), atomization() and get_atomized_operand() helpers to XPathToken
* Added atomization() and get_atomized_operand() helpers to XPathToken
* Fix value comparison operators

`v1.1.3`_ (2019-02-06)
Expand Down
27 changes: 10 additions & 17 deletions elementpath/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1101,31 +1101,31 @@ def ncname_validator(x):
lambda x: isinstance(x, string_base_type), value='https://example.com'
),
'normalizedString': XsdBuiltin(
lambda x: isinstance(x, string_base_type) and '\t' not in x and '\r' not in x, value='alpha',
lambda x: isinstance(x, string_base_type) and '\t' not in x and '\r' not in x, value=' alpha ',
),
'token': XsdBuiltin(
lambda x: isinstance(x, string_base_type) and WHITESPACES_PATTERN.match(x) is None, value='token'
lambda x: isinstance(x, string_base_type) and WHITESPACES_PATTERN.match(x) is None, value='a token'
),
'language': XsdBuiltin(
lambda x: isinstance(x, string_base_type) and LANGUAGE_CODE_PATTERN.match(x) is not None, value='en:US'
lambda x: isinstance(x, string_base_type) and LANGUAGE_CODE_PATTERN.match(x) is not None, value='en-US'
),
'Name': XsdBuiltin(
lambda x: isinstance(x, string_base_type) and NAME_PATTERN.match(x) is not None, value='node'
lambda x: isinstance(x, string_base_type) and NAME_PATTERN.match(x) is not None, value='_a.name::'
),
'NCName': XsdBuiltin(
ncname_validator, value='alpha'
ncname_validator, value='nc-name'
),
'ID': XsdBuiltin(
ncname_validator, value='alpha'
ncname_validator, value='id1'
),
'IDREF': XsdBuiltin(
ncname_validator, value='alpha'
ncname_validator, value='id_ref1'
),
'ENTITY': XsdBuiltin(
ncname_validator, value='alpha'
ncname_validator, value='entity1'
),
'NMTOKEN': XsdBuiltin(
lambda x: isinstance(x, string_base_type) and NMTOKEN_PATTERN.match(x) is not None, value='alpha'
lambda x: isinstance(x, string_base_type) and NMTOKEN_PATTERN.match(x) is not None, value='a_token'
),
'base64Binary': XsdBuiltin(
base64_binary_validator, value=b'YWxwaGE='
Expand All @@ -1134,7 +1134,7 @@ def ncname_validator(x):
hex_binary_validator, value=b'31'
),
'dateTimeStamp': XsdBuiltin(
lambda x: isinstance(x, string_base_type), value=''
lambda x: isinstance(x, string_base_type), value='2000-01-01T12:00:00+01:00'
),
'integer': XsdBuiltin(
lambda x: isinstance(x, int), value=1
Expand Down Expand Up @@ -1179,10 +1179,3 @@ def ncname_validator(x):
lambda x: isinstance(x, bool), value=True
),
}


__all__ = [
'Date', 'Date10', 'DateTime', 'DateTime10', 'GregorianDay', 'GregorianMonth', 'GregorianMonthDay',
'GregorianYear', 'GregorianYearMonth', 'GregorianYear10', 'GregorianYearMonth10',
'Duration', 'DayTimeDuration', 'YearMonthDuration', 'Timezone', 'UntypedAtomic',
]
31 changes: 28 additions & 3 deletions elementpath/schema_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ class AbstractXsdComponent(object):
def name(self):
"""The XSD component's name. It's `None` for a local type definition."""

@property
@abstractmethod
def local_name(self):
"""The local part of the XSD component's name. It's `None` for a local type definition."""

@abstractmethod
def is_matching(self, name, default_namespace):
"""
Expand Down Expand Up @@ -101,10 +106,11 @@ def has_simple_content(self):
`False` otherwise.
"""

@property
@abstractmethod
def primitive_type(self):
"""The XSD built-in primitive type."""
def decode(self, obj, *args, **kwargs):
"""
Decodes XML data using the XSD type.
"""


####
Expand Down Expand Up @@ -207,6 +213,15 @@ def iter_atomic_types(self):
implementation must yields objects that implement the `AbstractXsdType` interface.
"""

@abstractmethod
def get_primitive_type(self, xsd_type):
"""
Returns the primitive type of an XSD type.
:param xsd_type: an XSD type instance.
:return: an XSD builtin primitive type.
"""


class XMLSchemaProxy(AbstractSchemaProxy):
"""
Expand Down Expand Up @@ -267,6 +282,16 @@ def iter_atomic_types(self):
if xsd_type.target_namespace != XSD_NAMESPACE and hasattr(xsd_type, 'primitive_type'):
yield xsd_type

def get_primitive_type(self, xsd_type):
if not xsd_type.is_simple():
return self._schema.maps.types['{%s}anyType']
elif not hasattr(xsd_type, 'primitive_type'):
return self.get_primitive_type(xsd_type.base_type)
elif xsd_type.primitive_type is not xsd_type:
return self.get_primitive_type(xsd_type.primitive_type)
else:
return xsd_type


__all__ = ['AbstractXsdComponent', 'AbstractEtreeElement', 'AbstractXsdType', 'AbstractXsdAttribute',
'AbstractXsdElement', 'AbstractSchemaProxy', 'XMLSchemaProxy']
18 changes: 10 additions & 8 deletions elementpath/xpath1_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
from .compat import PY3, string_base_type
from .exceptions import ElementPathSyntaxError, ElementPathTypeError, ElementPathNameError, \
ElementPathMissingContextError
from .datatypes import UntypedAtomic, DayTimeDuration, YearMonthDuration
from .schema_proxy import XPathSchemaContext
from .datatypes import UntypedAtomic, DayTimeDuration, YearMonthDuration, XSD_BUILTIN_TYPES
from .xpath_context import XPathSchemaContext
from .tdop_parser import Parser, MultiLabel
from .namespaces import XML_ID_QNAME, XML_LANG_QNAME, XPATH_1_DEFAULT_NAMESPACES, \
XPATH_FUNCTIONS_NAMESPACE, XSD_NAMESPACE, qname_to_prefixed
from .xpath_token import XPathToken
from .xpath_helpers import AttributeNode, NamespaceNode, is_etree_element, is_xpath_node, is_element_node, \
is_document_node, is_attribute_node, is_text_node, is_comment_node, is_processing_instruction_node, \
node_name, node_string_value, data_value, string_value, number_value
node_name, node_string_value, boolean_value, data_value, string_value, number_value

XML_NAME_CHARACTER = (u"A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF"
u"\u200C\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD")
Expand Down Expand Up @@ -233,7 +233,8 @@ def evaluate(self, context=None):
xsd_type = self.match_xsd_type(context.item, name)
if xsd_type is not None:
if isinstance(context.item, AttributeNode):
return getattr(self.xsd_type.primitive_type, 'value', None)
primitive_type = self.parser.schema.get_primitive_type(xsd_type)
return XSD_BUILTIN_TYPES[primitive_type.local_name].value
else:
return context.item

Expand Down Expand Up @@ -268,7 +269,8 @@ def select(self, context=None):
xsd_type = self.match_xsd_type(item, name)
if xsd_type is not None:
if isinstance(context.item, AttributeNode):
yield getattr(self.xsd_type.primitive_type, 'value', None)
primitive_type = self.parser.schema.get_primitive_type(xsd_type)
yield XSD_BUILTIN_TYPES[primitive_type.local_name].value
else:
yield context.item

Expand Down Expand Up @@ -717,7 +719,7 @@ def select(self, context=None):
isinstance(predicate[0], (int, float)):
if context.position == predicate[0] - 1:
yield context.item
elif self.boolean(predicate):
elif boolean_value(predicate, self):
yield context.item


Expand Down Expand Up @@ -1113,12 +1115,12 @@ def evaluate(self, context=None):
# Boolean functions
@method(function('boolean', nargs=1))
def evaluate(self, context=None):
return self.boolean(self[0].get_results(context))
return boolean_value(self[0].get_results(context), self)


@method(function('not', nargs=1))
def evaluate(self, context=None):
return not self.boolean(self[0].get_results(context))
return not boolean_value(self[0].get_results(context), self)


@method(function('true', nargs=0))
Expand Down
4 changes: 2 additions & 2 deletions elementpath/xpath2_constructors.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

from .compat import unicode_type, urlparse, URLError, string_base_type
from .exceptions import ElementPathError, xpath_error
from .xpath_helpers import is_attribute_node, string_value
from .xpath_helpers import is_attribute_node, boolean_value, string_value
from .datatypes import DateTime10, Date10, Time, GregorianDay, GregorianMonth, GregorianMonthDay, \
GregorianYear10, GregorianYearMonth10, UntypedAtomic, Duration, YearMonthDuration, DayTimeDuration, \
WHITESPACES_PATTERN, QNAME_PATTERN, NMTOKEN_PATTERN, NAME_PATTERN, NCNAME_PATTERN, HEX_BINARY_PATTERN, \
Expand Down Expand Up @@ -434,7 +434,7 @@ def nud(self):
@method('boolean')
def evaluate(self, context=None):
if self.label == 'function':
return self.boolean(self[0].get_results(context))
return boolean_value(self[0].get_results(context), self)

# xs:boolean constructor
item = self.get_argument(context)
Expand Down
2 changes: 1 addition & 1 deletion elementpath/xpath2_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from .compat import PY3, string_base_type, unicode_chr, urlparse, urljoin, urllib_quote, unicode_type
from .datatypes import QNAME_PATTERN, DateTime, Date, Time, Timezone, Duration, DayTimeDuration
from .namespaces import prefixed_to_qname, get_namespace
from .schema_proxy import XPathSchemaContext
from .xpath_context import XPathSchemaContext
from .xpath_helpers import is_document_node, is_xpath_node, is_element_node, is_attribute_node, \
node_name, node_string_value, node_nilled, node_base_uri, node_document_uri, data_value, string_value

Expand Down
11 changes: 6 additions & 5 deletions elementpath/xpath2_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@
from .namespaces import XSD_NAMESPACE, XPATH_FUNCTIONS_NAMESPACE, XPATH_2_DEFAULT_NAMESPACES, \
XSD_NOTATION, XSD_ANY_ATOMIC_TYPE, get_namespace, qname_to_prefixed, prefixed_to_qname
from .datatypes import XSD_BUILTIN_TYPES
from .xpath_helpers import is_xpath_node
from .xpath_helpers import is_xpath_node, boolean_value
from .tdop_parser import create_tokenizer
from .xpath1_parser import XML_NCNAME_PATTERN, XPath1Parser
from .schema_proxy import XPathSchemaContext, AbstractSchemaProxy
from .xpath_context import XPathSchemaContext
from .schema_proxy import AbstractSchemaProxy


class XPath2Parser(XPath1Parser):
Expand Down Expand Up @@ -408,15 +409,15 @@ def nud(self):

@method('if')
def evaluate(self, context=None):
if self.boolean(self[0].evaluate(context)):
if boolean_value(self[0].evaluate(context), self):
return self[1].evaluate(context)
else:
return self[2].evaluate(context)


@method('if')
def select(self, context=None):
if self.boolean(list(self[0].select(context))):
if boolean_value(list(self[0].select(context)), self):
for result in self[1].select(context):
yield result
else:
Expand Down Expand Up @@ -457,7 +458,7 @@ def evaluate(self, context=None):
for results in product(*selectors):
for i in range(len(results)):
context.variables[self[i * 2][0].value] = results[i]
if self.boolean(list(self[-1].select(context.copy()))):
if boolean_value(list(self[-1].select(context.copy())), self):
if some:
return True
elif not some:
Expand Down
23 changes: 11 additions & 12 deletions elementpath/xpath_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@
# @author Davide Brunato <[email protected]>
#
"""
Helper functions for nodes and for most-used XPath errorless functions.
Helper functions for XPath nodes and functions.
"""
from collections import namedtuple

from .compat import PY3
from .namespaces import XML_BASE_QNAME, XML_ID_QNAME, XSI_TYPE_QNAME, XSI_NIL_QNAME, \
XSD_UNTYPED, XSD_UNTYPED_ATOMIC, prefixed_to_qname
from .exceptions import xpath_error
from .datatypes import UntypedAtomic

###
Expand Down Expand Up @@ -232,16 +233,10 @@ def node_type_name(obj, schema=None):

###
# XPath base functions
def boolean_value(obj):
def boolean_value(obj, token=None):
"""
The effective boolean value, as computed by fn:boolean().
Ref: https://www.w3.org/TR/xpath20/#dt-ebv
TODO: replaced by XPathToken.boolean() but used by xmlschema-1.0.9, so to be removed asap.
"""
from .exceptions import ElementPathTypeError

if isinstance(obj, list):
if not obj:
return False
Expand All @@ -250,12 +245,16 @@ def boolean_value(obj):
elif len(obj) == 1:
return bool(obj[0])
else:
raise ElementPathTypeError(
"Effective boolean value is not defined for a sequence of two or "
"more items not starting with an XPath node: %r" % obj
raise xpath_error(
code='FORG0006', token=token, prefix=getattr(token, 'error_prefix', 'err'),
message="Effective boolean value is not defined for a sequence of two or "
"more items not starting with an XPath node.",
)
elif isinstance(obj, tuple) or is_element_node(obj):
raise ElementPathTypeError("Effective boolean value is not defined for %r." % obj)
raise xpath_error(
code='FORG0006', token=token, prefix=getattr(token, 'error_prefix', 'err'),
message="Effective boolean value is not defined for {}.".format(obj)
)
return bool(obj)


Expand Down
Loading

0 comments on commit 808d0ef

Please sign in to comment.