From e74f3711aad3dfe5f9804c2c9e869c881cbcb547 Mon Sep 17 00:00:00 2001 From: Davide Brunato Date: Sun, 13 Sep 2020 11:04:26 +0200 Subject: [PATCH] Split context in cycle statements and set context with typed nodes - 'for', 'some' and 'every' statements split context another time on calling iter_product() to preserve the original context item - set context.item with typed node before yield statement --- CHANGELOG.rst | 7 ++++++- elementpath/schema_proxy.py | 6 ++++-- elementpath/xpath1/xpath1_parser.py | 19 ++++++++++++------ elementpath/xpath2/xpath2_parser.py | 4 ++-- elementpath/xpath_token.py | 30 ++++++++++++++++++++++------- publiccode.yml | 4 ++-- 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 4c806943..8ecc8089 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,11 @@ CHANGELOG ********* +`v2.0.3`_ (2020-09-13) +====================== +* Fix context handling in cycle statements +* Change constructor's label to 'constructor function' + `v2.0.2`_ (2020-09-03) ====================== * Add regex translator to package API @@ -244,4 +249,4 @@ CHANGELOG .. _v2.0.0: https://github.com/sissaschool/elementpath/compare/v1.4.6...v2.0.0 .. _v2.0.1: https://github.com/sissaschool/elementpath/compare/v2.0.0...v2.0.1 .. _v2.0.2: https://github.com/sissaschool/elementpath/compare/v2.0.1...v2.0.2 - +.. _v2.0.3: https://github.com/sissaschool/elementpath/compare/v2.0.2...v2.0.3 diff --git a/elementpath/schema_proxy.py b/elementpath/schema_proxy.py index 8e698d01..b4ef2164 100644 --- a/elementpath/schema_proxy.py +++ b/elementpath/schema_proxy.py @@ -308,10 +308,12 @@ def iter_atomic_types(self): @abstractmethod def get_primitive_type(self, xsd_type): """ - Returns the primitive type of an XSD type. + Returns the type at base of the definition of an XSD type. For an atomic type + is effectively the primitive type. For a list is the primitive type of the item. + For a union is the base union type. For a complex type is xs:anyType. :param xsd_type: an XSD type instance. - :return: an XSD builtin primitive type. + :return: an XSD type instance. """ diff --git a/elementpath/xpath1/xpath1_parser.py b/elementpath/xpath1/xpath1_parser.py index 61923b40..4961c679 100644 --- a/elementpath/xpath1/xpath1_parser.py +++ b/elementpath/xpath1/xpath1_parser.py @@ -468,12 +468,14 @@ def select(self, context=None): else: self.xsd_types = self.parser.schema - yield self.get_typed_node(item) + context.item = self.get_typed_node(item) + yield context.item else: # XSD typed selection for item in context.iter_children_or_self(): if match_attribute_node(item, name) or match_element_node(item, tag): - yield self.get_typed_node(item) + context.item = self.get_typed_node(item) + yield context.item ### @@ -556,13 +558,16 @@ def select(self, context=None): self.add_xsd_type(xsd_node) else: self.xsd_types = self.parser.schema - yield self.get_typed_node(item) + + context.item = self.get_typed_node(item) + yield context.item else: # XSD typed selection for item in context.iter_children_or_self(): if match_attribute_node(item, name) or match_element_node(item, name): - yield self.get_typed_node(item) + context.item = self.get_typed_node(item) + yield context.item ### @@ -613,7 +618,8 @@ def select(self, context=None): # XSD typed selection for item in context.iter_children_or_self(): if match_attribute_node(item, name) or match_element_node(item, name): - yield self.get_typed_node(item) + context.item = self.get_typed_node(item) + yield context.item ### @@ -675,7 +681,8 @@ def select(self, context=None): if context.item is None: pass elif context.is_principal_node_kind(): - yield self.get_typed_node(item) + context.item = self.get_typed_node(item) + yield context.item @method(nullary('.')) diff --git a/elementpath/xpath2/xpath2_parser.py b/elementpath/xpath2/xpath2_parser.py index 54d9d74f..e005b39c 100644 --- a/elementpath/xpath2/xpath2_parser.py +++ b/elementpath/xpath2/xpath2_parser.py @@ -597,7 +597,7 @@ def evaluate(self, context=None): varnames = [self[k][0].value for k in range(0, len(self) - 1, 2)] selectors = [self[k].select for k in range(1, len(self) - 1, 2)] - for results in context.iter_product(selectors, varnames): + for results in copy(context).iter_product(selectors, varnames): context.variables.update(x for x in zip(varnames, results)) if self.boolean_value([x for x in self[-1].select(copy(context))]): if some: @@ -643,7 +643,7 @@ def select(self, context=None): varnames = [self[k][0].value for k in range(0, len(self) - 1, 2)] selectors = [self[k].select for k in range(1, len(self) - 1, 2)] - for results in context.iter_product(selectors, varnames): + for results in copy(context).iter_product(selectors, varnames): context.variables.update(x for x in zip(varnames, results)) yield from self[-1].select(copy(context)) diff --git a/elementpath/xpath_token.py b/elementpath/xpath_token.py index 17fb0c49..65c80509 100644 --- a/elementpath/xpath_token.py +++ b/elementpath/xpath_token.py @@ -200,12 +200,17 @@ def get_argument(self, context, index=0, required=False, default_to_context=Fals for k, result in enumerate(selector(copy(context))): if k == 0: item = result - elif not self.parser.compatibility_mode: + elif self.parser.compatibility_mode: + break + elif isinstance(context, XPathSchemaContext): + # Multiple schema nodes are ignored but do not raise. The target + # of schema context selection is XSD type association and multiple + # nodes coherency is already checked at schema level. + break + else: raise self.wrong_context_type( "a sequence of more than one item is not allowed as argument" ) - else: - break else: if item is None: if not required: @@ -285,15 +290,20 @@ def get_atomized_operand(self, context=None): except StopIteration: return else: + item = getattr(context, 'item', None) + try: next(selector) except StopIteration: if isinstance(value, UntypedAtomic): value = str(value) - if isinstance(context, XPathSchemaContext): - return value - if self.xsd_types and isinstance(value, str): - xsd_type = self.get_xsd_type(context.item) + + if not isinstance(context, XPathSchemaContext) and \ + item is not None and \ + self.xsd_types and \ + isinstance(value, str): + + xsd_type = self.get_xsd_type(item) if xsd_type is None or xsd_type.name in XSD_SPECIAL_TYPES: pass else: @@ -302,6 +312,7 @@ def get_atomized_operand(self, context=None): except (TypeError, ValueError): msg = "Type {!r} is not appropriate for the context" raise self.wrong_context_type(msg.format(type(value))) + return value else: msg = "atomized operand is a sequence of length greater than one" @@ -668,6 +679,8 @@ def get_xsd_type(self, item): xsd_type = self.xsd_types.get(item) elif isinstance(item, AttributeNode): xsd_type = self.xsd_types.get(item[0]) + elif isinstance(item, (TypedAttribute, TypedElement)): + return item.type else: xsd_type = self.xsd_types.get(item.tag) @@ -702,6 +715,9 @@ def get_typed_node(self, item): :return: a TypedAttribute or a TypedElement, or the argument \ if it's not matching any associated XSD type. """ + if isinstance(item, (TypedAttribute, TypedElement)): + return item + xsd_type = self.get_xsd_type(item) if not xsd_type: return item diff --git a/publiccode.yml b/publiccode.yml index 9246921e..d1d31076 100644 --- a/publiccode.yml +++ b/publiccode.yml @@ -6,8 +6,8 @@ publiccodeYmlVersion: '0.2' name: elementpath url: 'https://github.com/sissaschool/elementpath' landingURL: 'https://github.com/sissaschool/elementpath' -releaseDate: '2020-09-03' -softwareVersion: v2.0.2 +releaseDate: '2020-09-13' +softwareVersion: v2.0.3 developmentStatus: stable platforms: - linux