From 4347e17f8cb12ea07659ddcbf1be9f52e3e3dac3 Mon Sep 17 00:00:00 2001 From: Lorenzo Curcio Date: Wed, 24 Dec 2025 23:57:28 +0100 Subject: [PATCH 1/4] added cache for apply_schema() element matching --- elementpath/xpath_nodes.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/elementpath/xpath_nodes.py b/elementpath/xpath_nodes.py index 0b91bc9..528bebc 100644 --- a/elementpath/xpath_nodes.py +++ b/elementpath/xpath_nodes.py @@ -1250,6 +1250,7 @@ def apply_schema(self, schema: 'AbstractSchemaProxy') -> None: delattr(root_node, '_attributes') iterators: deque[Any] = deque() + element_match_cache: dict[str|None, XsdElementProtocol|None] = {} while True: for node in children: if not isinstance(node, EtreeElementNode): @@ -1268,12 +1269,17 @@ def apply_schema(self, schema: 'AbstractSchemaProxy') -> None: xsd_element = schema.get_element(node.name) elif (content := xsd_types[-1].model_group) is None: xsd_element = None + elif node.name in element_match_cache: + xsd_element = element_match_cache[node.name] else: for xsd_element in content.iter_elements(): if xsd_element.is_matching(node.name): if xsd_element.name != node.name: # a wildcard or a substitute xsd_element = schema.get_element(node.name) + element_match_cache[node.name] = xsd_element + else: + element_match_cache[node.name] = None break else: xsd_element = None From 7847f67e5d077fc4ccedf46f2b0b23be9d8dc8c1 Mon Sep 17 00:00:00 2001 From: Lorenzo Curcio Date: Thu, 25 Dec 2025 00:03:04 +0100 Subject: [PATCH 2/4] fixed flake8 formatting --- elementpath/xpath_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elementpath/xpath_nodes.py b/elementpath/xpath_nodes.py index 528bebc..d2650a2 100644 --- a/elementpath/xpath_nodes.py +++ b/elementpath/xpath_nodes.py @@ -1250,7 +1250,7 @@ def apply_schema(self, schema: 'AbstractSchemaProxy') -> None: delattr(root_node, '_attributes') iterators: deque[Any] = deque() - element_match_cache: dict[str|None, XsdElementProtocol|None] = {} + element_match_cache: dict[str | None, XsdElementProtocol | None] = {} while True: for node in children: if not isinstance(node, EtreeElementNode): From f5f92bfab64b0218e32929d870432cbc8b16e2e9 Mon Sep 17 00:00:00 2001 From: Lorenzo Curcio Date: Thu, 25 Dec 2025 00:07:38 +0100 Subject: [PATCH 3/4] changed to Union[] syntax for python 3.9 support --- elementpath/xpath_nodes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/elementpath/xpath_nodes.py b/elementpath/xpath_nodes.py index d2650a2..88e993b 100644 --- a/elementpath/xpath_nodes.py +++ b/elementpath/xpath_nodes.py @@ -1250,7 +1250,7 @@ def apply_schema(self, schema: 'AbstractSchemaProxy') -> None: delattr(root_node, '_attributes') iterators: deque[Any] = deque() - element_match_cache: dict[str | None, XsdElementProtocol | None] = {} + element_match_cache: dict[Union[str, None], Union[XsdElementProtocol, None]] = {} while True: for node in children: if not isinstance(node, EtreeElementNode): From bae08d9e20912e71858ffd42f9aa180145cadd68 Mon Sep 17 00:00:00 2001 From: Lorenzo Curcio Date: Thu, 25 Dec 2025 00:43:34 +0100 Subject: [PATCH 4/4] final w3c fix for element match cache --- elementpath/xpath_nodes.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/elementpath/xpath_nodes.py b/elementpath/xpath_nodes.py index 88e993b..3222f4d 100644 --- a/elementpath/xpath_nodes.py +++ b/elementpath/xpath_nodes.py @@ -8,7 +8,7 @@ # @author Davide Brunato # import importlib -from collections import deque +from collections import defaultdict, deque from collections.abc import Iterator from urllib.parse import urljoin from typing import cast, Any, Optional, TYPE_CHECKING, Union @@ -1250,7 +1250,9 @@ def apply_schema(self, schema: 'AbstractSchemaProxy') -> None: delattr(root_node, '_attributes') iterators: deque[Any] = deque() - element_match_cache: dict[Union[str, None], Union[XsdElementProtocol, None]] = {} + element_match_cache: defaultdict[ + int, dict[Union[str, None], Union[XsdElementProtocol, None]] + ] = defaultdict(dict) while True: for node in children: if not isinstance(node, EtreeElementNode): @@ -1269,17 +1271,15 @@ def apply_schema(self, schema: 'AbstractSchemaProxy') -> None: xsd_element = schema.get_element(node.name) elif (content := xsd_types[-1].model_group) is None: xsd_element = None - elif node.name in element_match_cache: - xsd_element = element_match_cache[node.name] + elif node.name in (sub_cache := element_match_cache[id(content)]): + xsd_element = sub_cache[node.name] else: for xsd_element in content.iter_elements(): if xsd_element.is_matching(node.name): if xsd_element.name != node.name: # a wildcard or a substitute xsd_element = schema.get_element(node.name) - element_match_cache[node.name] = xsd_element - else: - element_match_cache[node.name] = None + sub_cache[node.name] = xsd_element break else: xsd_element = None