elementpath 5.0.1__tar.gz → 5.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {elementpath-5.0.1 → elementpath-5.0.2}/CHANGELOG.rst +6 -0
- {elementpath-5.0.1/elementpath.egg-info → elementpath-5.0.2}/PKG-INFO +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/conf.py +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/__init__.py +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/compare.py +15 -6
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/datetime.py +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/etree.py +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/helpers.py +11 -10
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/protocols.py +3 -2
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/serialization.py +11 -6
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/tree_builders.py +8 -13
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath1/_xpath1_operators.py +6 -4
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath1/xpath1_parser.py +4 -2
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath2/_xpath2_functions.py +5 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath2/xpath2_parser.py +10 -3
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath30/_xpath30_functions.py +10 -6
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath31/_xpath31_functions.py +6 -4
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath_context.py +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath_nodes.py +8 -7
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath_selectors.py +22 -5
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath_tokens.py +2 -2
- {elementpath-5.0.1 → elementpath-5.0.2/elementpath.egg-info}/PKG-INFO +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/pyproject.toml +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/mypy_tests/protocols.py +1 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_helpers.py +12 -7
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_selectors.py +11 -1
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath1_parser.py +37 -13
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath2_functions.py +6 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath2_parser.py +25 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath30.py +31 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath_nodes.py +3 -3
- {elementpath-5.0.1 → elementpath-5.0.2}/tox.ini +26 -13
- {elementpath-5.0.1 → elementpath-5.0.2}/LICENSE +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/MANIFEST.in +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/README.rst +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/Makefile +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/advanced.rst +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/index.rst +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/introduction.rst +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/make.bat +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/pratt_api.rst +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/requirements.txt +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/doc/xpath_api.rst +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/aliases.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/collations.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/atomic_types.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/binary.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/numeric.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/proxies.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/qname.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/string.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/untyped.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/datatypes/uri.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/decoder.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/exceptions.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/extras/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/extras/pathnodes.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/namespaces.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/py.typed +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/regex/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/regex/categories_fallback.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/regex/character_classes.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/regex/codepoints.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/regex/patterns.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/regex/unicode_blocks.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/regex/unicode_categories.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/regex/unicode_subsets.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/schema_proxy.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/sequence_types.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/tdop.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/validators/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/validators/analyze-string.xsd +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/validators/schema-for-json.xsd +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath1/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath1/_xpath1_axes.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath1/_xpath1_functions.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath2/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath2/_xpath2_constructors.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath2/_xpath2_operators.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath3.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath30/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath30/_translation_maps.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath30/_xpath30_operators.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath30/xpath30_helpers.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath30/xpath30_parser.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath31/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath31/_xpath31_operators.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath/xpath31/xpath31_parser.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath.egg-info/SOURCES.txt +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath.egg-info/dependency_links.txt +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath.egg-info/requires.txt +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/elementpath.egg-info/top_level.txt +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/requirements-dev.txt +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/scripts/generate_codepoints.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/setup.cfg +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/__init__.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/memory_profiling.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/mypy_tests/advanced.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/mypy_tests/selectors.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/resources/analyze-string.xsd +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/resources/external_entity.xml +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/resources/sample.xml +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/resources/schema-for-json.xsd +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/resources/unparsed_entity.xml +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/resources/unused_external_entity.xml +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/resources/unused_unparsed_entity.xml +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/resources/with_entity.xml +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/run_all_tests.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/run_typing_tests.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/run_w3c_tests.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_collations.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_compare.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_datatypes.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_decoder.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_etree.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_exceptions.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_namespaces.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_package.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_pathnodes.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_regex.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_schema_context.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_schema_proxy.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_sequence_types.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_serialization.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_tdop_parser.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_tree_builders.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_validators.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath2_constructors.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath31.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath_context.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/test_xpath_tokens.py +0 -0
- {elementpath-5.0.1 → elementpath-5.0.2}/tests/xpath_test_class.py +0 -0
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
CHANGELOG
|
|
3
3
|
*********
|
|
4
4
|
|
|
5
|
+
`v5.0.2`_ (2025-06-18)
|
|
6
|
+
======================
|
|
7
|
+
* Fix for XPath 2.0 *fn:node-name* (issue #91)
|
|
8
|
+
* Workaround for processing arguments with multiple occurrences for external functions (issue #92)
|
|
9
|
+
|
|
5
10
|
`v5.0.1`_ (2025-05-11)
|
|
6
11
|
======================
|
|
7
12
|
* Fix XDM type labeling with element and xsi:type substitutions (issue #89)
|
|
@@ -507,3 +512,4 @@ CHANGELOG
|
|
|
507
512
|
.. _v4.8.0: https://github.com/sissaschool/elementpath/compare/v4.7.0...v4.8.0
|
|
508
513
|
.. _v5.0.0: https://github.com/sissaschool/elementpath/compare/v4.8.0...v5.0.0
|
|
509
514
|
.. _v5.0.1: https://github.com/sissaschool/elementpath/compare/v5.0.0...v5.0.1
|
|
515
|
+
.. _v5.0.2: https://github.com/sissaschool/elementpath/compare/v5.0.1...v5.0.2
|
|
@@ -66,10 +66,17 @@ def deep_equal(seq1: Iterable[Any],
|
|
|
66
66
|
|
|
67
67
|
if (value1 is None) ^ (value2 is None):
|
|
68
68
|
return False
|
|
69
|
+
elif isinstance(value1, (XPathArray, XPathMap, XPathNode)) ^ \
|
|
70
|
+
isinstance(value2, (XPathArray, XPathMap, XPathNode)):
|
|
71
|
+
return False
|
|
69
72
|
elif value1 is None:
|
|
70
73
|
return True
|
|
71
|
-
elif isinstance(value1,
|
|
72
|
-
|
|
74
|
+
elif isinstance(value1, XPathMap):
|
|
75
|
+
assert isinstance(value2, XPathMap)
|
|
76
|
+
return value1 == value2
|
|
77
|
+
elif isinstance(value1, XPathArray):
|
|
78
|
+
assert isinstance(value2, XPathArray)
|
|
79
|
+
return value1 == value2
|
|
73
80
|
elif isinstance(value1, XPathNode):
|
|
74
81
|
assert isinstance(value2, XPathNode)
|
|
75
82
|
if value1.__class__ != value2.__class__:
|
|
@@ -187,9 +194,10 @@ def deep_compare(obj1: Any,
|
|
|
187
194
|
|
|
188
195
|
def etree_deep_compare(e1: ElementProtocol, e2: ElementProtocol) -> int:
|
|
189
196
|
nonlocal result
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
197
|
+
if not callable(e1.tag) and not callable(e2.tag):
|
|
198
|
+
result = cm.strcoll(e1.tag, e2.tag)
|
|
199
|
+
if result:
|
|
200
|
+
return result
|
|
193
201
|
|
|
194
202
|
result = cm.strcoll((e1.text or '').strip(), (e2.text or '').strip())
|
|
195
203
|
if result:
|
|
@@ -315,7 +323,8 @@ def deep_compare(obj1: Any,
|
|
|
315
323
|
|
|
316
324
|
elif isinstance(value1, float):
|
|
317
325
|
if math.isnan(value1):
|
|
318
|
-
if not
|
|
326
|
+
if not isinstance(value2, (float, Decimal)) \
|
|
327
|
+
or not math.isnan(value2):
|
|
319
328
|
return -1
|
|
320
329
|
elif math.isinf(value1):
|
|
321
330
|
if value1 != value2:
|
|
@@ -258,7 +258,7 @@ class AbstractDateTime(AnyAtomicType):
|
|
|
258
258
|
return cast(Timezone, self._dt.tzinfo)
|
|
259
259
|
|
|
260
260
|
@tzinfo.setter
|
|
261
|
-
def tzinfo(self, tz: Timezone) -> None:
|
|
261
|
+
def tzinfo(self, tz: Optional[Timezone]) -> None:
|
|
262
262
|
self._dt = self._dt.replace(tzinfo=tz)
|
|
263
263
|
|
|
264
264
|
def tzname(self) -> Optional[str]:
|
|
@@ -156,7 +156,7 @@ def etree_iter_paths(elem: ElementProtocol, path: str = '.') \
|
|
|
156
156
|
|
|
157
157
|
for child in elem:
|
|
158
158
|
if callable(child.tag):
|
|
159
|
-
if child.tag.__name__ == 'Comment':
|
|
159
|
+
if child.tag.__name__ == 'Comment':
|
|
160
160
|
comment_nodes += 1
|
|
161
161
|
yield child, f'{path}/comment()[{comment_nodes}]'
|
|
162
162
|
continue
|
|
@@ -84,7 +84,7 @@ class Patterns:
|
|
|
84
84
|
# Regex patterns related to names and namespaces
|
|
85
85
|
namespace_uri = LazyPattern(r'{([^}]+)}')
|
|
86
86
|
expanded_name = LazyPattern(
|
|
87
|
-
r'^(?:{(?P<namespace>[^}]
|
|
87
|
+
r'^(?:(?:Q{|{)(?P<namespace>[^}]*)})?'
|
|
88
88
|
r'(?P<local>[^\d\W][\w\-.\u00B7\u0300-\u036F\u0387\u06DD\u06DE\u203F\u2040]*)$',
|
|
89
89
|
)
|
|
90
90
|
unbound_expanded_name = LazyPattern(
|
|
@@ -259,19 +259,20 @@ def not_equal(op1: Any, op2: Any) -> bool:
|
|
|
259
259
|
|
|
260
260
|
|
|
261
261
|
def match_wildcard(name: Optional[str], wildcard: str) -> bool:
|
|
262
|
-
if name
|
|
262
|
+
if not name:
|
|
263
263
|
return False
|
|
264
|
-
elif wildcard
|
|
264
|
+
elif wildcard in ('*', '{*}*'):
|
|
265
265
|
return True
|
|
266
|
-
elif wildcard
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
else:
|
|
270
|
-
return name == wildcard[2:]
|
|
271
|
-
elif wildcard.startswith('{') and wildcard.endswith('}*') or wildcard.endswith(':*'):
|
|
266
|
+
elif wildcard == '{}*':
|
|
267
|
+
return name[0] != '{' or name[:2] == '{}'
|
|
268
|
+
elif wildcard[-1] == '*':
|
|
272
269
|
return name.startswith(wildcard[:-1])
|
|
273
|
-
|
|
270
|
+
elif not wildcard.startswith('{*}'):
|
|
274
271
|
return False
|
|
272
|
+
elif name[0] == '{':
|
|
273
|
+
return name.endswith(wildcard[2:])
|
|
274
|
+
else:
|
|
275
|
+
return name == wildcard[3:]
|
|
275
276
|
|
|
276
277
|
|
|
277
278
|
def escape_json_string(s: str, escaped: bool = False) -> str:
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"""
|
|
11
11
|
Define type hints protocols for XPath related objects.
|
|
12
12
|
"""
|
|
13
|
-
from collections.abc import Hashable, Iterator, Iterable,
|
|
13
|
+
from collections.abc import Callable, Hashable, Iterator, Iterable, \
|
|
14
|
+
ItemsView, Mapping, Sequence, Sized
|
|
14
15
|
from typing import overload, Any, Optional, Protocol, Union, TypeVar
|
|
15
16
|
from xml.etree.ElementTree import Element, ElementTree
|
|
16
17
|
|
|
@@ -64,7 +65,7 @@ class ElementProtocol(Sized, Hashable, Protocol):
|
|
|
64
65
|
def get(self, key: str, default: Optional[_T] = None) -> Union[str, _T, None]: ...
|
|
65
66
|
|
|
66
67
|
@property
|
|
67
|
-
def tag(self) -> str: ...
|
|
68
|
+
def tag(self) -> Union[str, Callable[[], 'ElementProtocol']]: ...
|
|
68
69
|
|
|
69
70
|
@property
|
|
70
71
|
def text(self) -> Optional[str]: ...
|
|
@@ -20,6 +20,7 @@ from elementpath.datatypes import AnyAtomicType, AnyURI, AbstractDateTime, \
|
|
|
20
20
|
AbstractBinary, UntypedAtomic, QName
|
|
21
21
|
from elementpath.xpath_nodes import XPathNode, ElementNode, AttributeNode, DocumentNode, \
|
|
22
22
|
NamespaceNode, TextNode, CommentNode
|
|
23
|
+
from elementpath.xpath_nodes import EtreeElementNode
|
|
23
24
|
from elementpath.xpath_tokens import XPathToken, XPathMap, XPathArray
|
|
24
25
|
from elementpath.protocols import EtreeElementProtocol, LxmlElementProtocol
|
|
25
26
|
|
|
@@ -147,7 +148,9 @@ def get_serialization_params(params: Union[None, ElementNode, XPathMap] = None,
|
|
|
147
148
|
raise xpath_error('SEPM0019', token=token)
|
|
148
149
|
|
|
149
150
|
for child in root:
|
|
150
|
-
if child.tag
|
|
151
|
+
if callable(child.tag):
|
|
152
|
+
continue
|
|
153
|
+
elif child.tag == SER_PARAM_OMIT_XML_DECLARATION:
|
|
151
154
|
value = child.get('value')
|
|
152
155
|
if value not in ('yes', 'no') or len(child.attrib) > 1:
|
|
153
156
|
raise xpath_error('SEPM0017', token=token)
|
|
@@ -289,7 +292,8 @@ def serialize_to_xml(elements: Iterable[Any],
|
|
|
289
292
|
chunks = []
|
|
290
293
|
for item in iter_normalized(elements, item_separator):
|
|
291
294
|
if isinstance(item, ElementNode):
|
|
292
|
-
item
|
|
295
|
+
assert isinstance(item, EtreeElementNode)
|
|
296
|
+
elem = item.obj
|
|
293
297
|
elif isinstance(item, (AttributeNode, NamespaceNode)):
|
|
294
298
|
raise xpath_error('SENR0001', token=token)
|
|
295
299
|
elif isinstance(item, TextNode):
|
|
@@ -306,15 +310,15 @@ def serialize_to_xml(elements: Iterable[Any],
|
|
|
306
310
|
|
|
307
311
|
try:
|
|
308
312
|
cks = etree_module.tostringlist(
|
|
309
|
-
|
|
313
|
+
elem, encoding='utf-8', method=method, **kwargs
|
|
310
314
|
)
|
|
311
315
|
except TypeError:
|
|
312
|
-
ck = etree_module.tostring(
|
|
313
|
-
chunks.append(ck.decode('utf-8').rstrip(
|
|
316
|
+
ck = etree_module.tostring(elem, encoding='utf-8', method=method)
|
|
317
|
+
chunks.append(ck.decode('utf-8').rstrip(elem.tail))
|
|
314
318
|
else:
|
|
315
319
|
if cks and cks[0].startswith(b'<?'):
|
|
316
320
|
cks[0] = cks[0].replace(b'\'', b'"')
|
|
317
|
-
chunks.append(b'\n'.join(cks).decode('utf-8').rstrip(
|
|
321
|
+
chunks.append(b'\n'.join(cks).decode('utf-8').rstrip(elem.tail))
|
|
318
322
|
|
|
319
323
|
if not character_map:
|
|
320
324
|
return (item_separator or '').join(chunks)
|
|
@@ -347,6 +351,7 @@ def serialize_to_json(elements: Iterable[Any],
|
|
|
347
351
|
if isinstance(obj, DocumentNode):
|
|
348
352
|
return ''.join(self.default(child) for child in obj)
|
|
349
353
|
elif isinstance(obj, ElementNode):
|
|
354
|
+
assert isinstance(obj, EtreeElementNode)
|
|
350
355
|
elem = obj.obj
|
|
351
356
|
assert etree_module is not None
|
|
352
357
|
|
|
@@ -46,18 +46,13 @@ def get_node_tree(root: RootArgType,
|
|
|
46
46
|
`False` is provided creates a dummy document when the root is an Element instance. \
|
|
47
47
|
For default the root node kind is preserved.
|
|
48
48
|
"""
|
|
49
|
-
root_node: RootNodeType
|
|
50
|
-
|
|
51
49
|
if isinstance(root, (DocumentNode, ElementNode)):
|
|
52
50
|
if uri is not None and root.uri is None:
|
|
53
51
|
root.tree.uri = uri
|
|
54
52
|
|
|
55
53
|
if fragment:
|
|
56
54
|
if isinstance(root, DocumentNode):
|
|
57
|
-
|
|
58
|
-
if root_node.uri is None:
|
|
59
|
-
root_node.uri = root.uri
|
|
60
|
-
return root_node
|
|
55
|
+
return root.getroot()
|
|
61
56
|
elif fragment is False and \
|
|
62
57
|
isinstance(root, ElementNode) and \
|
|
63
58
|
is_etree_element_instance(root.obj):
|
|
@@ -80,9 +75,7 @@ def get_node_tree(root: RootArgType,
|
|
|
80
75
|
cast(SchemaElemType, root), uri
|
|
81
76
|
)
|
|
82
77
|
else:
|
|
83
|
-
return build_node_tree(
|
|
84
|
-
cast(ElementTreeRootType, root), namespaces, uri, fragment
|
|
85
|
-
)
|
|
78
|
+
return build_node_tree(root, namespaces, uri, fragment)
|
|
86
79
|
|
|
87
80
|
|
|
88
81
|
def build_node_tree(root: ElementTreeRootType,
|
|
@@ -160,7 +153,7 @@ def build_node_tree(root: ElementTreeRootType,
|
|
|
160
153
|
TextNode(elem.text, child, position)
|
|
161
154
|
position += 1
|
|
162
155
|
|
|
163
|
-
elif elem.tag.__name__ == 'Comment':
|
|
156
|
+
elif elem.tag.__name__ == 'Comment':
|
|
164
157
|
child = CommentNode(elem, parent, position)
|
|
165
158
|
position += 1
|
|
166
159
|
else:
|
|
@@ -239,7 +232,8 @@ def build_lxml_node_tree(root: LxmlRootType,
|
|
|
239
232
|
|
|
240
233
|
# Add root siblings (comments and processing instructions)
|
|
241
234
|
for elem in reversed([x for x in root_elem.itersiblings(preceding=True)]):
|
|
242
|
-
|
|
235
|
+
assert callable(elem.tag)
|
|
236
|
+
if elem.tag.__name__ == 'Comment':
|
|
243
237
|
CommentNode(elem, document_node, position)
|
|
244
238
|
position += 1
|
|
245
239
|
else:
|
|
@@ -293,7 +287,7 @@ def build_lxml_node_tree(root: LxmlRootType,
|
|
|
293
287
|
TextNode(elem.text, child, position)
|
|
294
288
|
position += 1
|
|
295
289
|
|
|
296
|
-
elif elem.tag.__name__ == 'Comment':
|
|
290
|
+
elif elem.tag.__name__ == 'Comment':
|
|
297
291
|
child = CommentNode(elem, parent, position)
|
|
298
292
|
position += 1
|
|
299
293
|
else:
|
|
@@ -319,7 +313,8 @@ def build_lxml_node_tree(root: LxmlRootType,
|
|
|
319
313
|
|
|
320
314
|
# Add root following siblings (comments and processing instructions)
|
|
321
315
|
for elem in root_elem.itersiblings():
|
|
322
|
-
|
|
316
|
+
assert callable(elem.tag)
|
|
317
|
+
if elem.tag.__name__ == 'Comment':
|
|
323
318
|
CommentNode(elem, document_node, position)
|
|
324
319
|
position += 1
|
|
325
320
|
else:
|
|
@@ -124,7 +124,7 @@ class _PrefixedReferenceToken(XPathToken):
|
|
|
124
124
|
prefix = self[0].value
|
|
125
125
|
assert isinstance(prefix, str)
|
|
126
126
|
if prefix == '*':
|
|
127
|
-
return '
|
|
127
|
+
return '{*}%s' % self[1].value
|
|
128
128
|
else:
|
|
129
129
|
return f'{{{self.get_namespace(prefix)}}}{self[1].value}'
|
|
130
130
|
|
|
@@ -224,10 +224,12 @@ def nud_namespace_uri(self: XPathToken) -> XPathToken:
|
|
|
224
224
|
cls: type[XPathToken] = self.parser.symbol_table['(string)']
|
|
225
225
|
self[:] = cls(self.parser, namespace), self.parser.expression(90)
|
|
226
226
|
|
|
227
|
-
if
|
|
228
|
-
self.value = self[1].value
|
|
229
|
-
else:
|
|
227
|
+
if self[0].value:
|
|
230
228
|
self.value = f'{{{self[0].value}}}{self[1].value}'
|
|
229
|
+
elif self[1].value == '*':
|
|
230
|
+
self.value = '{}*'
|
|
231
|
+
else:
|
|
232
|
+
self.value = self[1].value
|
|
231
233
|
return self
|
|
232
234
|
|
|
233
235
|
|
|
@@ -277,8 +277,10 @@ class XPath1Parser(Parser[XPathTokenType]):
|
|
|
277
277
|
not isinstance(self.next_token, (XPathFunction, XPathAxis)) and \
|
|
278
278
|
self.name_pattern.match(self.next_token.symbol) is not None:
|
|
279
279
|
# Disambiguation replacing the next token with a '(name)' token
|
|
280
|
-
cls =
|
|
281
|
-
|
|
280
|
+
cls = self.symbol_table['(name)']
|
|
281
|
+
assert not issubclass(cls, XPathFunction)
|
|
282
|
+
value = self.next_token.symbol
|
|
283
|
+
self.next_token = cls(self, value)
|
|
282
284
|
else:
|
|
283
285
|
raise self.next_token.wrong_syntax(message)
|
|
284
286
|
|
|
@@ -272,8 +272,13 @@ def evaluate_node_name_function(self: XPathFunction, context: ContextType = None
|
|
|
272
272
|
elif name.startswith('{'):
|
|
273
273
|
# name is a QName in extended format
|
|
274
274
|
namespace, local_name = split_expanded_name(name)
|
|
275
|
+
if not namespace:
|
|
276
|
+
return QName('', local_name)
|
|
277
|
+
|
|
275
278
|
for pfx, uri in self.parser.namespaces.items():
|
|
276
279
|
if uri == namespace:
|
|
280
|
+
if not pfx:
|
|
281
|
+
return QName(uri, local_name)
|
|
277
282
|
return QName(uri, '{}:{}'.format(pfx, local_name))
|
|
278
283
|
raise self.error('FONS0004', 'no prefix found for namespace {}'.format(namespace))
|
|
279
284
|
else:
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"""
|
|
11
11
|
XPath 2.0 implementation - part 1 (parser class and symbols)
|
|
12
12
|
"""
|
|
13
|
+
import copy
|
|
13
14
|
from abc import ABCMeta
|
|
14
15
|
import locale
|
|
15
16
|
from collections.abc import Callable, MutableMapping
|
|
@@ -355,7 +356,7 @@ class XPath2Parser(XPath1Parser):
|
|
|
355
356
|
)
|
|
356
357
|
|
|
357
358
|
if self.symbol_table is self.__class__.symbol_table:
|
|
358
|
-
self.symbol_table =
|
|
359
|
+
self.symbol_table = copy.copy(self.symbol_table)
|
|
359
360
|
self.symbol_table[symbol] = token_class
|
|
360
361
|
self.tokenizer = None
|
|
361
362
|
|
|
@@ -399,7 +400,7 @@ class XPath2Parser(XPath1Parser):
|
|
|
399
400
|
lookup_name = qname.expanded_name
|
|
400
401
|
|
|
401
402
|
if self.symbol_table is self.__class__.symbol_table:
|
|
402
|
-
self.symbol_table =
|
|
403
|
+
self.symbol_table = copy.copy(self.symbol_table)
|
|
403
404
|
|
|
404
405
|
if lookup_name in self.symbol_table:
|
|
405
406
|
msg = f'function {qname.qname!r} is already registered'
|
|
@@ -439,7 +440,13 @@ class XPath2Parser(XPath1Parser):
|
|
|
439
440
|
context: Optional[XPathContext] = None) -> Any:
|
|
440
441
|
args = []
|
|
441
442
|
for k in range(len(self_)):
|
|
442
|
-
|
|
443
|
+
try:
|
|
444
|
+
if sequence_types[k][-1] in '+*':
|
|
445
|
+
arg = self_[k].evaluate(context)
|
|
446
|
+
else:
|
|
447
|
+
arg = self_.get_argument(context, index=k)
|
|
448
|
+
except IndexError:
|
|
449
|
+
arg = self_.get_argument(context, index=k)
|
|
443
450
|
args.append(arg)
|
|
444
451
|
|
|
445
452
|
if sequence_types:
|
|
@@ -25,7 +25,6 @@ from urllib.request import urlopen
|
|
|
25
25
|
from urllib.error import URLError
|
|
26
26
|
|
|
27
27
|
from elementpath.aliases import Emptiable
|
|
28
|
-
from elementpath.protocols import ElementProtocol
|
|
29
28
|
from elementpath.exceptions import ElementPathError
|
|
30
29
|
from elementpath.tdop import MultiLabel
|
|
31
30
|
from elementpath.helpers import OCCURRENCE_INDICATORS, Patterns, \
|
|
@@ -660,7 +659,7 @@ def evaluate_format_number_function(self: XPathFunction, context: ContextType =
|
|
|
660
659
|
|
|
661
660
|
minus_sign = decimal_format['minus-sign']
|
|
662
661
|
|
|
663
|
-
prefix = ''
|
|
662
|
+
prefix: str = ''
|
|
664
663
|
if value >= 0:
|
|
665
664
|
subpic = sub_pictures[0]
|
|
666
665
|
else:
|
|
@@ -683,7 +682,8 @@ def evaluate_format_number_function(self: XPathFunction, context: ContextType =
|
|
|
683
682
|
suffix = percent_sign
|
|
684
683
|
subpic = subpic[:-1]
|
|
685
684
|
|
|
686
|
-
|
|
685
|
+
exponent = value.as_tuple().exponent
|
|
686
|
+
if isinstance(exponent, int) and exponent < 0:
|
|
687
687
|
value *= 100
|
|
688
688
|
else:
|
|
689
689
|
value = decimal.Decimal(int(value) * 100)
|
|
@@ -692,7 +692,8 @@ def evaluate_format_number_function(self: XPathFunction, context: ContextType =
|
|
|
692
692
|
suffix = per_mille_sign
|
|
693
693
|
subpic = subpic[:-1]
|
|
694
694
|
|
|
695
|
-
|
|
695
|
+
exponent = value.as_tuple().exponent
|
|
696
|
+
if isinstance(exponent, int) and exponent < 0:
|
|
696
697
|
value *= 1000
|
|
697
698
|
else:
|
|
698
699
|
value = decimal.Decimal(int(value) * 1000)
|
|
@@ -931,7 +932,7 @@ def evaluate_format_date_time_functions(self: XPathFunction, context: ContextTyp
|
|
|
931
932
|
sequence_types=('xs:string?', 'xs:string', 'xs:string',
|
|
932
933
|
'element(fn:analyze-string-result)')))
|
|
933
934
|
def evaluate_analyze_string_function(self: XPathFunction, context: ContextType = None) \
|
|
934
|
-
->
|
|
935
|
+
-> ElementNode:
|
|
935
936
|
if self.context is not None:
|
|
936
937
|
context = self.context
|
|
937
938
|
|
|
@@ -1055,7 +1056,7 @@ def evaluate_analyze_string_function(self: XPathFunction, context: ContextType =
|
|
|
1055
1056
|
else:
|
|
1056
1057
|
root = context.etree.XML(''.join(lines))
|
|
1057
1058
|
|
|
1058
|
-
return cast(
|
|
1059
|
+
return cast(ElementNode, get_node_tree(root=root, namespaces=self.parser.namespaces))
|
|
1059
1060
|
|
|
1060
1061
|
|
|
1061
1062
|
###
|
|
@@ -1760,6 +1761,9 @@ def evaluate_node_name_function(self: XPathFunction, context: ContextType = None
|
|
|
1760
1761
|
elif name.startswith('{'):
|
|
1761
1762
|
# name is a QName in extended format
|
|
1762
1763
|
namespace, local_name = split_expanded_name(name)
|
|
1764
|
+
if not namespace:
|
|
1765
|
+
return QName('', local_name)
|
|
1766
|
+
|
|
1763
1767
|
for pfx, uri in self.parser.namespaces.items():
|
|
1764
1768
|
if uri == namespace:
|
|
1765
1769
|
if not pfx:
|
|
@@ -709,7 +709,7 @@ def evaluate_parse_json_functions(self: XPathFunction, context: ContextType = No
|
|
|
709
709
|
if json_text is None:
|
|
710
710
|
return []
|
|
711
711
|
|
|
712
|
-
def _fallback(*
|
|
712
|
+
def _fallback(*a: Any, **kw: Any) -> str:
|
|
713
713
|
return '\uFFFD'
|
|
714
714
|
|
|
715
715
|
liberal = False
|
|
@@ -748,7 +748,7 @@ def evaluate_parse_json_functions(self: XPathFunction, context: ContextType = No
|
|
|
748
748
|
fallback = cast(Callable[..., str], v)
|
|
749
749
|
escape = False
|
|
750
750
|
|
|
751
|
-
def decode_value(value: SequenceType[ItemType]) -> ItemType:
|
|
751
|
+
def decode_value(value: SequenceType[ItemType]) -> Emptiable[ItemType]:
|
|
752
752
|
if value is None:
|
|
753
753
|
return []
|
|
754
754
|
elif isinstance(value, list):
|
|
@@ -765,6 +765,8 @@ def evaluate_parse_json_functions(self: XPathFunction, context: ContextType = No
|
|
|
765
765
|
|
|
766
766
|
def json_object_pairs_to_map(obj: Iterable[tuple[str, SequenceType[ItemType]]]) -> XPathMap:
|
|
767
767
|
items: dict[ItemType, SequenceType[ItemType]] = {}
|
|
768
|
+
key: Any
|
|
769
|
+
value: Any
|
|
768
770
|
|
|
769
771
|
for item in obj:
|
|
770
772
|
key, value = decode_value(item[0]), decode_value(item[1])
|
|
@@ -775,7 +777,7 @@ def evaluate_parse_json_functions(self: XPathFunction, context: ContextType = No
|
|
|
775
777
|
raise self.error('FOJS0003')
|
|
776
778
|
|
|
777
779
|
if isinstance(value, list):
|
|
778
|
-
values = [decode_value(x) for x in value]
|
|
780
|
+
values: Any = [decode_value(x) for x in value]
|
|
779
781
|
items[key] = XPathArray(self.parser, values) if values else values
|
|
780
782
|
else:
|
|
781
783
|
items[key] = value
|
|
@@ -1262,7 +1264,7 @@ def evaluate_json_to_xml_function(self: XPathFunction, context: ContextType = No
|
|
|
1262
1264
|
else:
|
|
1263
1265
|
raise self.missing_context()
|
|
1264
1266
|
|
|
1265
|
-
def _fallback(*
|
|
1267
|
+
def _fallback(*a: Any, **kw: Any) -> str:
|
|
1266
1268
|
return '�'
|
|
1267
1269
|
|
|
1268
1270
|
liberal = False
|
|
@@ -617,7 +617,7 @@ class XPathContext:
|
|
|
617
617
|
for item in root.iter_descendants(with_self=False):
|
|
618
618
|
if position < item.position and item not in descendants:
|
|
619
619
|
self.item = item
|
|
620
|
-
yield
|
|
620
|
+
yield item
|
|
621
621
|
|
|
622
622
|
self.item, self.axis = status
|
|
623
623
|
|
|
@@ -1099,6 +1099,7 @@ class EtreeElementNode(ElementNode):
|
|
|
1099
1099
|
position: int = 1,
|
|
1100
1100
|
nsmap: Union[NsmapType, NamespacesType, None] = None) -> None:
|
|
1101
1101
|
|
|
1102
|
+
assert not callable(elem.tag)
|
|
1102
1103
|
self.name = elem.tag
|
|
1103
1104
|
self.obj = elem
|
|
1104
1105
|
self.parent = parent
|
|
@@ -1266,15 +1267,15 @@ class EtreeElementNode(ElementNode):
|
|
|
1266
1267
|
|
|
1267
1268
|
def match_name(self, name: str, default_namespace: Optional[str] = None) -> bool:
|
|
1268
1269
|
if '*' in name:
|
|
1269
|
-
return match_wildcard(self.
|
|
1270
|
+
return match_wildcard(self.name, name)
|
|
1270
1271
|
elif not name:
|
|
1271
|
-
return not self.
|
|
1272
|
+
return not self.name
|
|
1272
1273
|
elif hasattr(self.obj, 'type') and hasattr(self.obj, 'is_matching'):
|
|
1273
1274
|
return cast(XsdElementProtocol, self.obj).is_matching(name, default_namespace)
|
|
1274
1275
|
elif name[0] == '{' or not default_namespace:
|
|
1275
|
-
return self.
|
|
1276
|
+
return self.name == name
|
|
1276
1277
|
else:
|
|
1277
|
-
return self.
|
|
1278
|
+
return self.name == f'{{{default_namespace}}}{name}'
|
|
1278
1279
|
|
|
1279
1280
|
def get_document_node(self, replace: bool = False, as_parent: bool = True) -> 'DocumentNode':
|
|
1280
1281
|
"""
|
|
@@ -1353,7 +1354,7 @@ class LazyElementNode(EtreeElementNode):
|
|
|
1353
1354
|
for elem in self.obj:
|
|
1354
1355
|
if not callable(elem.tag):
|
|
1355
1356
|
LazyElementNode(elem, self)
|
|
1356
|
-
elif elem.tag.__name__ == 'Comment':
|
|
1357
|
+
elif elem.tag.__name__ == 'Comment':
|
|
1357
1358
|
CommentNode(elem, self)
|
|
1358
1359
|
else:
|
|
1359
1360
|
ProcessingInstructionNode(elem, parent=self)
|
|
@@ -1439,9 +1440,9 @@ class SchemaElementNode(ElementNode):
|
|
|
1439
1440
|
|
|
1440
1441
|
def match_name(self, name: str, default_namespace: Optional[str] = None) -> bool:
|
|
1441
1442
|
if '*' in name:
|
|
1442
|
-
return match_wildcard(self.
|
|
1443
|
+
return match_wildcard(self.name, name)
|
|
1443
1444
|
elif not name:
|
|
1444
|
-
return not self.
|
|
1445
|
+
return not self.name
|
|
1445
1446
|
elif hasattr(self.obj, 'type'):
|
|
1446
1447
|
return self.obj.is_matching(name, default_namespace)
|
|
1447
1448
|
else:
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
# @author Davide Brunato <brunato@sissa.it>
|
|
9
9
|
#
|
|
10
10
|
import datetime
|
|
11
|
+
import warnings
|
|
11
12
|
from collections.abc import Iterator
|
|
12
13
|
from typing import Any, Optional, Union
|
|
13
14
|
|
|
@@ -66,8 +67,12 @@ def select(root: Optional[RootArgType],
|
|
|
66
67
|
:return: a list with XPath nodes or a basic type for expressions based \
|
|
67
68
|
on a function or literal.
|
|
68
69
|
"""
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
if parser is None:
|
|
71
|
+
parser = XPath2Parser
|
|
72
|
+
if schema is not None and parser.version > '1.0':
|
|
73
|
+
kwargs['schema'] = schema
|
|
74
|
+
|
|
75
|
+
root_token = parser(namespaces, **kwargs).parse(path)
|
|
71
76
|
context = XPathContext(root, namespaces, uri, fragment, item, position, size,
|
|
72
77
|
axis, schema, variables, current_dt, timezone)
|
|
73
78
|
return root_token.get_results(context)
|
|
@@ -119,8 +124,12 @@ def iter_select(root: Optional[RootArgType],
|
|
|
119
124
|
:param kwargs: other optional parameters for the parser instance.
|
|
120
125
|
:return: a generator of the XPath expression results.
|
|
121
126
|
"""
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
if parser is None:
|
|
128
|
+
parser = XPath2Parser
|
|
129
|
+
if schema is not None and parser.version > '1.0':
|
|
130
|
+
kwargs['schema'] = schema
|
|
131
|
+
|
|
132
|
+
root_token = parser(namespaces, **kwargs).parse(path)
|
|
124
133
|
context = XPathContext(root, namespaces, uri, fragment, item, position, size,
|
|
125
134
|
axis, schema, variables, current_dt, timezone)
|
|
126
135
|
return root_token.select_results(context)
|
|
@@ -148,7 +157,13 @@ class Selector(object):
|
|
|
148
157
|
parser: Optional['ParserClassType'] = None,
|
|
149
158
|
**kwargs: Any) -> None:
|
|
150
159
|
|
|
151
|
-
|
|
160
|
+
if 'variables' in kwargs:
|
|
161
|
+
msg = ("Argument 'variables' here is deprecated and will be"
|
|
162
|
+
"be removed in the next major release. Provide this "
|
|
163
|
+
"argument later to Selector.select/iter_select.")
|
|
164
|
+
warnings.warn(DeprecationWarning(msg))
|
|
165
|
+
|
|
166
|
+
self._variables = kwargs.pop('variables', None)
|
|
152
167
|
self.parser = (parser or XPath2Parser)(namespaces, **kwargs)
|
|
153
168
|
self.path = path
|
|
154
169
|
self.root_token = self.parser.parse(path)
|
|
@@ -191,6 +206,8 @@ class Selector(object):
|
|
|
191
206
|
:param kwargs: other optional parameters for the XPath dynamic context.
|
|
192
207
|
:return: a generator of the XPath expression results.
|
|
193
208
|
"""
|
|
209
|
+
if 'schema' not in kwargs:
|
|
210
|
+
kwargs['schema'] = self.parser.schema
|
|
194
211
|
if 'variables' not in kwargs and self._variables:
|
|
195
212
|
kwargs['variables'] = self._variables
|
|
196
213
|
|
|
@@ -567,7 +567,7 @@ class XPathToken(Token[XPathTokenType]):
|
|
|
567
567
|
|
|
568
568
|
for result in self.select(context):
|
|
569
569
|
if not isinstance(result, XPathNode):
|
|
570
|
-
yield
|
|
570
|
+
yield result
|
|
571
571
|
elif isinstance(result, NamespaceNode):
|
|
572
572
|
if self.parser.compatibility_mode:
|
|
573
573
|
yield result.prefix, result.uri
|
|
@@ -797,7 +797,7 @@ class XPathToken(Token[XPathTokenType]):
|
|
|
797
797
|
return _item
|
|
798
798
|
raise self.error('FODT0001', str(err)) from None
|
|
799
799
|
|
|
800
|
-
if
|
|
800
|
+
if isinstance(_item, AbstractDateTime):
|
|
801
801
|
_item.tzinfo = timezone
|
|
802
802
|
return _item
|
|
803
803
|
|