elementpath 5.0.0__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.
Files changed (133) hide show
  1. {elementpath-5.0.0 → elementpath-5.0.2}/CHANGELOG.rst +11 -0
  2. {elementpath-5.0.0/elementpath.egg-info → elementpath-5.0.2}/PKG-INFO +1 -1
  3. {elementpath-5.0.0 → elementpath-5.0.2}/doc/conf.py +1 -1
  4. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/__init__.py +1 -1
  5. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/compare.py +15 -6
  6. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/datetime.py +1 -1
  7. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/etree.py +1 -1
  8. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/helpers.py +11 -10
  9. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/protocols.py +38 -43
  10. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/regex/categories_fallback.py +0 -1
  11. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/schema_proxy.py +84 -47
  12. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/serialization.py +11 -6
  13. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/tree_builders.py +18 -19
  14. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath1/_xpath1_operators.py +6 -4
  15. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath1/xpath1_parser.py +4 -2
  16. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath2/_xpath2_functions.py +5 -0
  17. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath2/_xpath2_operators.py +1 -2
  18. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath2/xpath2_parser.py +10 -3
  19. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath30/_xpath30_functions.py +10 -6
  20. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath31/_xpath31_functions.py +6 -4
  21. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath_context.py +17 -17
  22. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath_nodes.py +48 -51
  23. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath_selectors.py +22 -5
  24. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath_tokens.py +2 -2
  25. {elementpath-5.0.0 → elementpath-5.0.2/elementpath.egg-info}/PKG-INFO +1 -1
  26. {elementpath-5.0.0 → elementpath-5.0.2}/pyproject.toml +1 -1
  27. {elementpath-5.0.0 → elementpath-5.0.2}/tests/mypy_tests/protocols.py +1 -1
  28. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_helpers.py +12 -7
  29. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_schema_proxy.py +241 -1
  30. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_selectors.py +11 -1
  31. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath1_parser.py +37 -13
  32. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath2_functions.py +6 -0
  33. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath2_parser.py +25 -0
  34. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath30.py +31 -0
  35. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath_nodes.py +3 -3
  36. {elementpath-5.0.0 → elementpath-5.0.2}/tox.ini +26 -13
  37. {elementpath-5.0.0 → elementpath-5.0.2}/LICENSE +0 -0
  38. {elementpath-5.0.0 → elementpath-5.0.2}/MANIFEST.in +0 -0
  39. {elementpath-5.0.0 → elementpath-5.0.2}/README.rst +0 -0
  40. {elementpath-5.0.0 → elementpath-5.0.2}/doc/Makefile +0 -0
  41. {elementpath-5.0.0 → elementpath-5.0.2}/doc/advanced.rst +0 -0
  42. {elementpath-5.0.0 → elementpath-5.0.2}/doc/index.rst +0 -0
  43. {elementpath-5.0.0 → elementpath-5.0.2}/doc/introduction.rst +0 -0
  44. {elementpath-5.0.0 → elementpath-5.0.2}/doc/make.bat +0 -0
  45. {elementpath-5.0.0 → elementpath-5.0.2}/doc/pratt_api.rst +0 -0
  46. {elementpath-5.0.0 → elementpath-5.0.2}/doc/requirements.txt +0 -0
  47. {elementpath-5.0.0 → elementpath-5.0.2}/doc/xpath_api.rst +0 -0
  48. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/aliases.py +0 -0
  49. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/collations.py +0 -0
  50. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/__init__.py +0 -0
  51. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/atomic_types.py +0 -0
  52. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/binary.py +0 -0
  53. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/numeric.py +0 -0
  54. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/proxies.py +0 -0
  55. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/qname.py +0 -0
  56. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/string.py +0 -0
  57. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/untyped.py +0 -0
  58. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/datatypes/uri.py +0 -0
  59. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/decoder.py +0 -0
  60. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/exceptions.py +0 -0
  61. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/extras/__init__.py +0 -0
  62. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/extras/pathnodes.py +0 -0
  63. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/namespaces.py +0 -0
  64. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/py.typed +0 -0
  65. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/regex/__init__.py +0 -0
  66. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/regex/character_classes.py +0 -0
  67. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/regex/codepoints.py +0 -0
  68. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/regex/patterns.py +0 -0
  69. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/regex/unicode_blocks.py +0 -0
  70. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/regex/unicode_categories.py +0 -0
  71. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/regex/unicode_subsets.py +0 -0
  72. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/sequence_types.py +0 -0
  73. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/tdop.py +0 -0
  74. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/validators/__init__.py +0 -0
  75. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/validators/analyze-string.xsd +0 -0
  76. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/validators/schema-for-json.xsd +0 -0
  77. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath1/__init__.py +0 -0
  78. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath1/_xpath1_axes.py +0 -0
  79. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath1/_xpath1_functions.py +0 -0
  80. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath2/__init__.py +0 -0
  81. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath2/_xpath2_constructors.py +0 -0
  82. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath3.py +0 -0
  83. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath30/__init__.py +0 -0
  84. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath30/_translation_maps.py +0 -0
  85. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath30/_xpath30_operators.py +0 -0
  86. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath30/xpath30_helpers.py +0 -0
  87. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath30/xpath30_parser.py +0 -0
  88. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath31/__init__.py +0 -0
  89. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath31/_xpath31_operators.py +0 -0
  90. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath/xpath31/xpath31_parser.py +0 -0
  91. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath.egg-info/SOURCES.txt +0 -0
  92. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath.egg-info/dependency_links.txt +0 -0
  93. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath.egg-info/requires.txt +0 -0
  94. {elementpath-5.0.0 → elementpath-5.0.2}/elementpath.egg-info/top_level.txt +0 -0
  95. {elementpath-5.0.0 → elementpath-5.0.2}/requirements-dev.txt +0 -0
  96. {elementpath-5.0.0 → elementpath-5.0.2}/scripts/generate_codepoints.py +0 -0
  97. {elementpath-5.0.0 → elementpath-5.0.2}/setup.cfg +0 -0
  98. {elementpath-5.0.0 → elementpath-5.0.2}/tests/__init__.py +0 -0
  99. {elementpath-5.0.0 → elementpath-5.0.2}/tests/memory_profiling.py +0 -0
  100. {elementpath-5.0.0 → elementpath-5.0.2}/tests/mypy_tests/advanced.py +0 -0
  101. {elementpath-5.0.0 → elementpath-5.0.2}/tests/mypy_tests/selectors.py +0 -0
  102. {elementpath-5.0.0 → elementpath-5.0.2}/tests/resources/analyze-string.xsd +0 -0
  103. {elementpath-5.0.0 → elementpath-5.0.2}/tests/resources/external_entity.xml +0 -0
  104. {elementpath-5.0.0 → elementpath-5.0.2}/tests/resources/sample.xml +0 -0
  105. {elementpath-5.0.0 → elementpath-5.0.2}/tests/resources/schema-for-json.xsd +0 -0
  106. {elementpath-5.0.0 → elementpath-5.0.2}/tests/resources/unparsed_entity.xml +0 -0
  107. {elementpath-5.0.0 → elementpath-5.0.2}/tests/resources/unused_external_entity.xml +0 -0
  108. {elementpath-5.0.0 → elementpath-5.0.2}/tests/resources/unused_unparsed_entity.xml +0 -0
  109. {elementpath-5.0.0 → elementpath-5.0.2}/tests/resources/with_entity.xml +0 -0
  110. {elementpath-5.0.0 → elementpath-5.0.2}/tests/run_all_tests.py +0 -0
  111. {elementpath-5.0.0 → elementpath-5.0.2}/tests/run_typing_tests.py +0 -0
  112. {elementpath-5.0.0 → elementpath-5.0.2}/tests/run_w3c_tests.py +0 -0
  113. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_collations.py +0 -0
  114. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_compare.py +0 -0
  115. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_datatypes.py +0 -0
  116. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_decoder.py +0 -0
  117. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_etree.py +0 -0
  118. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_exceptions.py +0 -0
  119. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_namespaces.py +0 -0
  120. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_package.py +0 -0
  121. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_pathnodes.py +0 -0
  122. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_regex.py +0 -0
  123. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_schema_context.py +0 -0
  124. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_sequence_types.py +0 -0
  125. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_serialization.py +0 -0
  126. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_tdop_parser.py +0 -0
  127. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_tree_builders.py +0 -0
  128. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_validators.py +0 -0
  129. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath2_constructors.py +0 -0
  130. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath31.py +0 -0
  131. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath_context.py +0 -0
  132. {elementpath-5.0.0 → elementpath-5.0.2}/tests/test_xpath_tokens.py +0 -0
  133. {elementpath-5.0.0 → elementpath-5.0.2}/tests/xpath_test_class.py +0 -0
@@ -2,6 +2,15 @@
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
+
10
+ `v5.0.1`_ (2025-05-11)
11
+ ======================
12
+ * Fix XDM type labeling with element and xsi:type substitutions (issue #89)
13
+
5
14
  `v5.0.0`_ (2025-04-27)
6
15
  ======================
7
16
  * Replace SafeXMLParser with SafeExpatParser
@@ -502,3 +511,5 @@ CHANGELOG
502
511
  .. _v4.7.0: https://github.com/sissaschool/elementpath/compare/v4.6.0...v4.7.0
503
512
  .. _v4.8.0: https://github.com/sissaschool/elementpath/compare/v4.7.0...v4.8.0
504
513
  .. _v5.0.0: https://github.com/sissaschool/elementpath/compare/v4.8.0...v5.0.0
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elementpath
3
- Version: 5.0.0
3
+ Version: 5.0.2
4
4
  Summary: XPath 1.0/2.0/3.0/3.1 parsers and selectors for ElementTree and lxml
5
5
  Author-email: Davide Brunato <brunato@sissa.it>
6
6
  License-Expression: MIT
@@ -31,7 +31,7 @@ author = 'Davide Brunato'
31
31
  # The short X.Y version
32
32
  version = '5.0'
33
33
  # The full version, including alpha/beta/rc tags
34
- release = '5.0.0'
34
+ release = '5.0.2'
35
35
 
36
36
  # -- General configuration ---------------------------------------------------
37
37
 
@@ -7,7 +7,7 @@
7
7
  #
8
8
  # @author Davide Brunato <brunato@sissa.it>
9
9
  #
10
- __version__ = '5.0.0'
10
+ __version__ = '5.0.2'
11
11
  __author__ = "Davide Brunato"
12
12
  __contact__ = "brunato@sissa.it"
13
13
  __copyright__ = "Copyright 2018-2025, SISSA"
@@ -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, XPathNode) ^ isinstance(value2, XPathNode):
72
- return False
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
- result = cm.strcoll(e1.tag, e2.tag)
191
- if result:
192
- return result
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 math.isnan(value2):
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': # type: ignore[attr-defined]
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 is None:
262
+ if not name:
263
263
  return False
264
- elif wildcard == '*' or wildcard == '*:*':
264
+ elif wildcard in ('*', '{*}*'):
265
265
  return True
266
- elif wildcard.startswith('*:'):
267
- if name.startswith('{'):
268
- return name.endswith(f'}}{wildcard[2:]}')
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
- else:
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,16 +10,14 @@
10
10
  """
11
11
  Define type hints protocols for XPath related objects.
12
12
  """
13
- from collections.abc import Hashable, Iterator, Iterable, ItemsView, Mapping, Sequence, Sized
14
- from typing import overload, Any, Optional, Protocol, Union, TypeVar, TYPE_CHECKING
13
+ from collections.abc import Callable, Hashable, Iterator, Iterable, \
14
+ ItemsView, Mapping, Sequence, Sized
15
+ from typing import overload, Any, Optional, Protocol, Union, TypeVar
15
16
  from xml.etree.ElementTree import Element, ElementTree
16
17
 
17
18
  from collections.abc import MutableMapping
18
19
  from elementpath.aliases import NamespacesType, NsmapType
19
20
 
20
- if TYPE_CHECKING:
21
- from elementpath.schema_proxy import AbstractSchemaProxy
22
-
23
21
  _T = TypeVar("_T")
24
22
  _AnyStr = Union[str, bytes]
25
23
 
@@ -67,7 +65,7 @@ class ElementProtocol(Sized, Hashable, Protocol):
67
65
  def get(self, key: str, default: Optional[_T] = None) -> Union[str, _T, None]: ...
68
66
 
69
67
  @property
70
- def tag(self) -> str: ...
68
+ def tag(self) -> Union[str, Callable[[], 'ElementProtocol']]: ...
71
69
 
72
70
  @property
73
71
  def text(self) -> Optional[str]: ...
@@ -146,6 +144,9 @@ class XsdComponentProtocol(XsdValidatorProtocol, Protocol):
146
144
  @property
147
145
  def parent(self) -> Optional['XsdComponentProtocol']: ...
148
146
 
147
+ @property
148
+ def ref(self) -> Optional['XsdComponentProtocol']: ...
149
+
149
150
 
150
151
  class XsdTypeProtocol(XsdComponentProtocol, Protocol):
151
152
 
@@ -260,17 +261,29 @@ class XsdTypeProtocol(XsdComponentProtocol, Protocol):
260
261
  """
261
262
  ...
262
263
 
264
+ @property
265
+ def model_group(self) -> Optional['XsdGroupProtocol']:
266
+ """
267
+ A model group if it's a complexType with mixed or element-only content, `None` otherwise.
268
+ """
269
+ ...
263
270
 
264
- class XsdAttributeProtocol(XsdComponentProtocol, Protocol):
265
271
 
266
- @property
267
- def type(self) -> Optional[XsdTypeProtocol]: ...
272
+ class XsdComplexTypeProtocol(XsdComponentProtocol, Protocol):
268
273
 
269
274
  @property
270
- def ref(self) -> Optional[Any]: ...
275
+ def attributes(self) -> 'XsdAttributeGroupProtocol': ...
276
+
277
+
278
+ class XsdGroupProtocol(XsdComponentProtocol, Protocol):
271
279
 
280
+ def iter_elements(self) -> Iterator['XsdElementProtocol']: ...
272
281
 
273
- XsdXPathNodeType = Union['XsdSchemaProtocol', 'XsdElementProtocol']
282
+
283
+ class XsdAttributeProtocol(XsdComponentProtocol, Protocol):
284
+
285
+ @property
286
+ def type(self) -> Optional[XsdTypeProtocol]: ...
274
287
 
275
288
 
276
289
  class XsdAttributeGroupProtocol(XsdComponentProtocol, Protocol):
@@ -296,62 +309,40 @@ class XsdAttributeGroupProtocol(XsdComponentProtocol, Protocol):
296
309
 
297
310
  def __hash__(self) -> int: ...
298
311
 
299
- @property
300
- def ref(self) -> Optional[Any]: ...
301
-
302
312
 
303
313
  class XsdElementProtocol(XsdComponentProtocol, ElementProtocol, Protocol):
304
314
 
305
- def __iter__(self) -> Iterator['XsdElementProtocol']: ...
306
-
307
- def find(
308
- self, path: str, namespaces: Optional[NamespacesType] = ...
309
- ) -> Optional[XsdXPathNodeType]: ...
310
- def iter(self, tag: Optional[str] = ...) -> Iterator['XsdElementProtocol']: ...
311
-
312
315
  @property
313
316
  def name(self) -> Optional[str]: ...
314
317
 
315
318
  @property
316
319
  def type(self) -> Optional[XsdTypeProtocol]: ...
317
320
 
318
- @property
319
- def ref(self) -> Optional[Any]: ...
320
-
321
321
  @property
322
322
  def attrib(self) -> XsdAttributeGroupProtocol: ...
323
323
 
324
- @property
325
- def xpath_proxy(self) -> 'AbstractSchemaProxy': ...
326
-
324
+ def __iter__(self) -> Iterator['XsdElementProtocol']: ...
327
325
 
328
- GT = TypeVar("GT")
329
- XsdGlobalValue = Union[GT, tuple[ElementProtocol, Any]]
326
+ def find(self, path: str, namespaces: Optional[NamespacesType] = ...) \
327
+ -> Union['XsdElementProtocol', None]: ...
330
328
 
331
329
 
332
330
  class GlobalMapsProtocol(Protocol):
333
331
 
334
332
  @property
335
- def types(self) -> Mapping[str, XsdGlobalValue[XsdTypeProtocol]]: ...
333
+ def types(self) -> Mapping[str, XsdTypeProtocol]: ...
336
334
 
337
335
  @property
338
- def attributes(self) -> Mapping[str, XsdGlobalValue[XsdAttributeProtocol]]: ...
336
+ def attributes(self) -> Mapping[str, XsdAttributeProtocol]: ...
339
337
 
340
338
  @property
341
- def elements(self) -> Mapping[str, XsdGlobalValue[XsdElementProtocol]]: ...
339
+ def elements(self) -> Mapping[str, XsdElementProtocol]: ...
342
340
 
343
341
  @property
344
342
  def substitution_groups(self) -> Mapping[str, set[Any]]: ...
345
343
 
346
344
 
347
- class XsdSchemaProtocol(XsdValidatorProtocol, ElementProtocol, Protocol):
348
-
349
- def __iter__(self) -> Iterator[XsdXPathNodeType]: ...
350
-
351
- def find(
352
- self, path: str, namespaces: Optional[NamespacesType] = ...
353
- ) -> Optional[XsdXPathNodeType]: ...
354
- def iter(self, tag: Optional[str] = ...) -> Iterator[XsdXPathNodeType]: ...
345
+ class XsdSchemaProtocol(XsdValidatorProtocol, Protocol):
355
346
 
356
347
  @property
357
348
  def validity(self) -> str: ...
@@ -365,10 +356,13 @@ class XsdSchemaProtocol(XsdValidatorProtocol, ElementProtocol, Protocol):
365
356
  @property
366
357
  def attrib(self) -> MutableMapping[Optional[str], 'XsdAttributeProtocol']: ...
367
358
 
368
- @property
369
- def xpath_proxy(self) -> 'AbstractSchemaProxy': ...
359
+ def __iter__(self) -> Iterator['XsdXPathNodeType']: ...
360
+
361
+ def find(self, path: str, namespaces: Optional[NamespacesType] = ...) \
362
+ -> Union['XsdSchemaProtocol', 'XsdElementProtocol', None]: ...
370
363
 
371
364
 
365
+ XsdXPathNodeType = Union[XsdSchemaProtocol, XsdElementProtocol]
372
366
  DocumentType = Union[ElementTree, DocumentProtocol]
373
367
  ElementType = Union[Element, ElementProtocol]
374
368
  SchemaElemType = Union[XsdSchemaProtocol, XsdElementProtocol]
@@ -386,4 +380,5 @@ __all__ = ['ElementProtocol', 'EtreeElementProtocol', 'LxmlAttribProtocol',
386
380
  'XsdValidatorProtocol', 'XsdComponentProtocol', 'XsdTypeProtocol',
387
381
  'XsdAttributeProtocol', 'XsdAttributeGroupProtocol', 'XsdElementProtocol',
388
382
  'GlobalMapsProtocol', 'XsdSchemaProtocol', 'DocumentType', 'ElementType',
389
- 'SchemaElemType', 'CommentType', 'ProcessingInstructionType', 'AttribType']
383
+ 'SchemaElemType', 'CommentType', 'ProcessingInstructionType',
384
+ 'AttribType', 'XsdXPathNodeType']
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env python
2
1
  #
3
2
  # Copyright (c), 2025, SISSA (International School for Advanced Studies).
4
3
  # All rights reserved.
@@ -10,19 +10,20 @@
10
10
  from abc import ABCMeta, abstractmethod
11
11
  from collections.abc import Iterator
12
12
  from functools import lru_cache
13
- from typing import TYPE_CHECKING, Any, Optional, Union
13
+ from typing import cast, Any, Optional, Union
14
14
 
15
15
  from elementpath.exceptions import ElementPathTypeError
16
16
  from elementpath.protocols import XsdTypeProtocol, XsdAttributeProtocol, \
17
- XsdElementProtocol, XsdSchemaProtocol
17
+ XsdElementProtocol, XsdSchemaProtocol, XsdAttributeGroupProtocol
18
+ from elementpath.namespaces import XSD_ANY_ATOMIC_TYPE
18
19
  from elementpath.datatypes import AtomicType
19
20
  from elementpath.etree import is_etree_element
20
21
  from elementpath.xpath_context import XPathSchemaContext
21
-
22
- if TYPE_CHECKING:
23
- from elementpath.xpath_tokens import XPath2ParserType
22
+ from elementpath.xpath_tokens import XPath2ParserType
24
23
 
25
24
  PathResult = Union[XsdSchemaProtocol, XsdElementProtocol, XsdAttributeProtocol]
25
+ FindAttrType = Optional[XsdAttributeProtocol]
26
+ FindElemType = Optional[XsdElementProtocol]
26
27
 
27
28
 
28
29
  class AbstractSchemaProxy(metaclass=ABCMeta):
@@ -33,7 +34,7 @@ class AbstractSchemaProxy(metaclass=ABCMeta):
33
34
  :param schema: a schema instance compatible with the XsdSchemaProtocol.
34
35
  :param base_element: the schema element used as base item for static analysis.
35
36
  """
36
- __slots__ = ('_schema', '_base_element', '_find', '_is_fully_valid')
37
+ __slots__ = ('_schema', '_base_element', '_is_fully_valid')
37
38
 
38
39
  def __init__(self, schema: XsdSchemaProtocol,
39
40
  base_element: Optional[XsdElementProtocol] = None) -> None:
@@ -48,12 +49,6 @@ class AbstractSchemaProxy(metaclass=ABCMeta):
48
49
 
49
50
  self._schema = schema
50
51
  self._base_element: Optional[XsdElementProtocol] = base_element
51
-
52
- if self._base_element is not None:
53
- self._find = self._base_element.find
54
- else:
55
- self._find = self._schema.find
56
-
57
52
  self._is_fully_valid = False
58
53
 
59
54
  @property
@@ -109,24 +104,26 @@ class AbstractSchemaProxy(metaclass=ABCMeta):
109
104
  def find(self, path: str, namespaces: Optional[dict[str, str]] = None) \
110
105
  -> Optional[PathResult]:
111
106
  """
112
- Find a schema element or attribute using an XPath expression.
107
+ Find a schema element or attribute using an XPath expression. Currently unused
108
+ in the code, it may be deprecated in the future.
113
109
 
114
110
  :param path: an XPath expression that selects an element or an attribute node.
115
111
  :param namespaces: an optional mapping from namespace prefix to namespace URI.
116
112
  :return: The first matching schema component, or ``None`` if there is no match.
117
113
  """
118
- return self._find(path, namespaces)
114
+ if self._base_element is not None:
115
+ return self._base_element.find(path, namespaces=namespaces)
116
+ return self._schema.find(path, namespaces)
119
117
 
120
118
  @lru_cache(maxsize=None)
121
119
  def cached_find(self, expanded_path: str) -> Optional[PathResult]:
122
120
  """
123
- Find a schema element or attribute using an expanded path as XPath expression.
124
-
125
- :param expanded_path: an XPath expression with qualified names already resolved \
126
- to expanded form.
127
- :return: The first matching schema component, or ``None`` if there is no match.
121
+ Find a schema element or attribute using an expanded XPath expression.
122
+ Currently unused in the code, it may be deprecated in the future.
128
123
  """
129
- return self._find(expanded_path)
124
+ if self._base_element is not None:
125
+ return self._base_element.find(expanded_path)
126
+ return self._schema.find(expanded_path)
130
127
 
131
128
  @property
132
129
  def xsd_version(self) -> str:
@@ -139,58 +136,98 @@ class AbstractSchemaProxy(metaclass=ABCMeta):
139
136
 
140
137
  def get_type(self, qname: str) -> Optional[XsdTypeProtocol]:
141
138
  """
142
- Get the XSD global type from the schema's scope. A concrete implementation must
143
- return an object that supports the protocols `XsdTypeProtocol`, or `None` if
144
- the global type is not found.
139
+ Get the XSD global type from the schema's scope.
145
140
 
146
141
  :param qname: the fully qualified name of the type to retrieve.
147
142
  :returns: an object that represents an XSD type or `None`.
148
143
  """
149
- xsd_type = self._schema.maps.types.get(qname)
150
- if isinstance(xsd_type, tuple):
151
- return None
152
- return xsd_type
144
+ return self._schema.maps.types.get(qname)
153
145
 
154
146
  def get_attribute(self, qname: str) -> Optional[XsdAttributeProtocol]:
155
147
  """
156
- Get the XSD global attribute from the schema's scope. A concrete implementation must
157
- return an object that supports the protocol `XsdAttributeProtocol`, or `None` if
158
- the global attribute is not found.
148
+ Get the XSD global attribute from the schema's scope.
159
149
 
160
150
  :param qname: the fully qualified name of the attribute to retrieve.
161
- :returns: an object that represents an XSD attribute or `None`.
151
+ :returns: an object that represents an XSD type or `None`.
162
152
  """
163
- xsd_attribute = self._schema.maps.attributes.get(qname)
164
- if isinstance(xsd_attribute, tuple):
165
- return None
166
- return xsd_attribute
153
+ return self._schema.maps.attributes.get(qname)
167
154
 
168
155
  def get_element(self, qname: str) -> Optional[XsdElementProtocol]:
169
156
  """
170
- Get the XSD global element from the schema's scope. A concrete implementation must
171
- return an object that supports the protocol `XsdElementProtocol` interface, or
172
- `None` if the global element is not found.
157
+ Get the XSD global element from the schema's scope.
173
158
 
174
159
  :param qname: the fully qualified name of the element to retrieve.
175
- :returns: an object that represents an XSD element or `None`.
160
+ :returns: an object that represents an XSD type or `None`.
176
161
  """
177
- xsd_element = self._schema.maps.elements.get(qname)
178
- if isinstance(xsd_element, tuple):
179
- return None
180
- return xsd_element
162
+ return self._schema.maps.elements.get(qname)
181
163
 
182
164
  def get_substitution_group(self, qname: str) -> Optional[set[XsdElementProtocol]]:
183
165
  """
184
- Get a substitution group. A concrete implementation must return a list containing
185
- substitution elements or `None` if the substitution group is not found. Moreover,
186
- each item of the returned list must be an object that implements the
187
- `AbstractXsdElement` interface.
166
+ Get a substitution group. Currently unused in the code, it may be deprecated
167
+ in the future.
188
168
 
189
169
  :param qname: the fully qualified name of the substitution group to retrieve.
190
170
  :returns: a list containing substitution elements or `None`.
191
171
  """
192
172
  return self._schema.maps.substitution_groups.get(qname)
193
173
 
174
+ def get_attribute_type(self, name: str, parent_type: Optional[XsdTypeProtocol] = None) \
175
+ -> Optional[XsdTypeProtocol]:
176
+ """
177
+ Get the XSD attribute type if the provided name is matching in the scope,
178
+ otherwise return None.
179
+
180
+ :param name: the name of the attribute to retrieve.
181
+ :param parent_type: an optional XSD type that represents the scope where matching \
182
+ the attribute name. If not provided, the scope is assumed to be the global scope.
183
+ """
184
+ if parent_type is None:
185
+ try:
186
+ return self._schema.maps.attributes[name].type
187
+ except KeyError:
188
+ return None
189
+ elif hasattr(parent_type, 'attributes'):
190
+ attributes = cast(XsdAttributeGroupProtocol, parent_type.attributes)
191
+ if name in attributes:
192
+ return attributes[name].type
193
+ elif None in attributes and attributes[None].is_matching(name):
194
+ try:
195
+ return self._schema.maps.attributes[name].type
196
+ except KeyError:
197
+ return None
198
+ elif name.startswith('{http://www.w3.org/2001/XMLSchema-instance}'):
199
+ return self._schema.maps.types.get(XSD_ANY_ATOMIC_TYPE)
200
+
201
+ return None
202
+
203
+ def get_child_type(self, name: str, parent_type: Optional[XsdTypeProtocol] = None) \
204
+ -> Optional[XsdTypeProtocol]:
205
+ """
206
+ Get the child XSD type if the provided name is matching in the scope,
207
+ otherwise return None.
208
+
209
+ :param name: the name of the child element to match.
210
+ :param parent_type: an optional XSD type that represents the scope where matching \
211
+ the child element name. If `None` or not provided the scope is the schema.
212
+ """
213
+ if parent_type is None:
214
+ try:
215
+ return self._schema.maps.elements[name].type
216
+ except KeyError:
217
+ return None
218
+ elif (content := parent_type.model_group) is not None:
219
+ for xsd_element in content.iter_elements():
220
+ if xsd_element.is_matching(name):
221
+ if xsd_element.name == name:
222
+ return xsd_element.type
223
+ else:
224
+ # a wildcard or a substitute
225
+ try:
226
+ return self._schema.maps.elements[name].type
227
+ except KeyError:
228
+ return None
229
+ return None
230
+
194
231
  @abstractmethod
195
232
  def is_instance(self, obj: Any, type_qname: str) -> bool:
196
233
  """
@@ -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 == SER_PARAM_OMIT_XML_DECLARATION:
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 = item.obj
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
- item, encoding='utf-8', method=method, **kwargs
313
+ elem, encoding='utf-8', method=method, **kwargs
310
314
  )
311
315
  except TypeError:
312
- ck = etree_module.tostring(item, encoding='utf-8', method=method)
313
- chunks.append(ck.decode('utf-8').rstrip(item.tail))
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(item.tail))
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