elementpath 5.1.1__tar.gz → 5.1.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 (143) hide show
  1. {elementpath-5.1.1 → elementpath-5.1.2}/CHANGELOG.rst +6 -0
  2. {elementpath-5.1.1/elementpath.egg-info → elementpath-5.1.2}/PKG-INFO +1 -1
  3. {elementpath-5.1.1 → elementpath-5.1.2}/doc/conf.py +1 -1
  4. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/__init__.py +1 -1
  5. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/any_types.py +2 -1
  6. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/untyped.py +1 -1
  7. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/helpers.py +7 -0
  8. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/regex/character_classes.py +27 -3
  9. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/serialization.py +25 -8
  10. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_tokens/base.py +5 -1
  11. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_tokens/functions.py +4 -6
  12. {elementpath-5.1.1 → elementpath-5.1.2/elementpath.egg-info}/PKG-INFO +1 -1
  13. {elementpath-5.1.1 → elementpath-5.1.2}/pyproject.toml +1 -1
  14. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_regex.py +4 -0
  15. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath2_parser.py +15 -1
  16. {elementpath-5.1.1 → elementpath-5.1.2}/tox.ini +18 -7
  17. {elementpath-5.1.1 → elementpath-5.1.2}/LICENSE +0 -0
  18. {elementpath-5.1.1 → elementpath-5.1.2}/MANIFEST.in +0 -0
  19. {elementpath-5.1.1 → elementpath-5.1.2}/README.rst +0 -0
  20. {elementpath-5.1.1 → elementpath-5.1.2}/doc/Makefile +0 -0
  21. {elementpath-5.1.1 → elementpath-5.1.2}/doc/advanced.rst +0 -0
  22. {elementpath-5.1.1 → elementpath-5.1.2}/doc/index.rst +0 -0
  23. {elementpath-5.1.1 → elementpath-5.1.2}/doc/introduction.rst +0 -0
  24. {elementpath-5.1.1 → elementpath-5.1.2}/doc/make.bat +0 -0
  25. {elementpath-5.1.1 → elementpath-5.1.2}/doc/pratt_api.rst +0 -0
  26. {elementpath-5.1.1 → elementpath-5.1.2}/doc/requirements.txt +0 -0
  27. {elementpath-5.1.1 → elementpath-5.1.2}/doc/xpath_api.rst +0 -0
  28. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/aliases.py +0 -0
  29. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/collations.py +0 -0
  30. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/compare.py +0 -0
  31. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/__init__.py +0 -0
  32. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/binary.py +0 -0
  33. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/datetime.py +0 -0
  34. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/lists.py +0 -0
  35. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/numeric.py +0 -0
  36. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/proxies.py +0 -0
  37. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/qname.py +0 -0
  38. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/string.py +0 -0
  39. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/datatypes/uri.py +0 -0
  40. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/decoder.py +0 -0
  41. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/etree.py +0 -0
  42. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/exceptions.py +0 -0
  43. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/extras/__init__.py +0 -0
  44. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/extras/pathnodes.py +0 -0
  45. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/namespaces.py +0 -0
  46. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/protocols.py +0 -0
  47. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/py.typed +0 -0
  48. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/regex/__init__.py +0 -0
  49. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/regex/categories_fallback.py +0 -0
  50. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/regex/codepoints.py +0 -0
  51. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/regex/patterns.py +0 -0
  52. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/regex/unicode_blocks.py +0 -0
  53. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/regex/unicode_categories.py +0 -0
  54. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/regex/unicode_subsets.py +0 -0
  55. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/schema_proxy.py +0 -0
  56. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/sequence_types.py +0 -0
  57. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/sequences.py +0 -0
  58. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/tdop.py +0 -0
  59. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/tree_builders.py +0 -0
  60. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/validators/__init__.py +0 -0
  61. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/validators/analyze-string.xsd +0 -0
  62. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/validators/schema-for-json.xsd +0 -0
  63. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath1/__init__.py +0 -0
  64. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath1/_xpath1_axes.py +0 -0
  65. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath1/_xpath1_functions.py +0 -0
  66. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath1/_xpath1_operators.py +0 -0
  67. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath1/xpath1_parser.py +0 -0
  68. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath2/__init__.py +0 -0
  69. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath2/_xpath2_constructors.py +0 -0
  70. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath2/_xpath2_functions.py +0 -0
  71. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath2/_xpath2_operators.py +0 -0
  72. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath2/xpath2_parser.py +0 -0
  73. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath3.py +0 -0
  74. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath30/__init__.py +0 -0
  75. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath30/_translation_maps.py +0 -0
  76. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath30/_xpath30_functions.py +0 -0
  77. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath30/_xpath30_operators.py +0 -0
  78. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath30/xpath30_helpers.py +0 -0
  79. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath30/xpath30_parser.py +0 -0
  80. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath31/__init__.py +0 -0
  81. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath31/_xpath31_functions.py +0 -0
  82. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath31/_xpath31_operators.py +0 -0
  83. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath31/xpath31_parser.py +0 -0
  84. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_context.py +0 -0
  85. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_nodes.py +0 -0
  86. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_selectors.py +0 -0
  87. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_tokens/__init__.py +0 -0
  88. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_tokens/arrays.py +0 -0
  89. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_tokens/axes.py +0 -0
  90. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_tokens/contructors.py +0 -0
  91. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_tokens/maps.py +0 -0
  92. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath/xpath_tokens/tokens.py +0 -0
  93. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath.egg-info/SOURCES.txt +0 -0
  94. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath.egg-info/dependency_links.txt +0 -0
  95. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath.egg-info/requires.txt +0 -0
  96. {elementpath-5.1.1 → elementpath-5.1.2}/elementpath.egg-info/top_level.txt +0 -0
  97. {elementpath-5.1.1 → elementpath-5.1.2}/requirements-dev.txt +0 -0
  98. {elementpath-5.1.1 → elementpath-5.1.2}/scripts/generate_codepoints.py +0 -0
  99. {elementpath-5.1.1 → elementpath-5.1.2}/setup.cfg +0 -0
  100. {elementpath-5.1.1 → elementpath-5.1.2}/tests/__init__.py +0 -0
  101. {elementpath-5.1.1 → elementpath-5.1.2}/tests/memory_profiling.py +0 -0
  102. {elementpath-5.1.1 → elementpath-5.1.2}/tests/mypy_tests/advanced.py +0 -0
  103. {elementpath-5.1.1 → elementpath-5.1.2}/tests/mypy_tests/protocols.py +0 -0
  104. {elementpath-5.1.1 → elementpath-5.1.2}/tests/mypy_tests/selectors.py +0 -0
  105. {elementpath-5.1.1 → elementpath-5.1.2}/tests/resources/analyze-string.xsd +0 -0
  106. {elementpath-5.1.1 → elementpath-5.1.2}/tests/resources/external_entity.xml +0 -0
  107. {elementpath-5.1.1 → elementpath-5.1.2}/tests/resources/sample.xml +0 -0
  108. {elementpath-5.1.1 → elementpath-5.1.2}/tests/resources/schema-for-json.xsd +0 -0
  109. {elementpath-5.1.1 → elementpath-5.1.2}/tests/resources/unparsed_entity.xml +0 -0
  110. {elementpath-5.1.1 → elementpath-5.1.2}/tests/resources/unused_external_entity.xml +0 -0
  111. {elementpath-5.1.1 → elementpath-5.1.2}/tests/resources/unused_unparsed_entity.xml +0 -0
  112. {elementpath-5.1.1 → elementpath-5.1.2}/tests/resources/with_entity.xml +0 -0
  113. {elementpath-5.1.1 → elementpath-5.1.2}/tests/run_all_tests.py +0 -0
  114. {elementpath-5.1.1 → elementpath-5.1.2}/tests/run_typing_tests.py +0 -0
  115. {elementpath-5.1.1 → elementpath-5.1.2}/tests/run_w3c_tests.py +0 -0
  116. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_collations.py +0 -0
  117. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_compare.py +0 -0
  118. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_datatypes.py +0 -0
  119. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_decoder.py +0 -0
  120. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_etree.py +0 -0
  121. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_exceptions.py +0 -0
  122. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_helpers.py +0 -0
  123. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_namespaces.py +0 -0
  124. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_package.py +0 -0
  125. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_pathnodes.py +0 -0
  126. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_schema_context.py +0 -0
  127. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_schema_proxy.py +0 -0
  128. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_selectors.py +0 -0
  129. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_sequence_types.py +0 -0
  130. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_sequences.py +0 -0
  131. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_serialization.py +0 -0
  132. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_tdop_parser.py +0 -0
  133. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_tree_builders.py +0 -0
  134. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_validators.py +0 -0
  135. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath1_parser.py +0 -0
  136. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath2_constructors.py +0 -0
  137. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath2_functions.py +0 -0
  138. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath30.py +0 -0
  139. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath31.py +0 -0
  140. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath_context.py +0 -0
  141. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath_nodes.py +0 -0
  142. {elementpath-5.1.1 → elementpath-5.1.2}/tests/test_xpath_tokens.py +0 -0
  143. {elementpath-5.1.1 → elementpath-5.1.2}/tests/xpath_test_class.py +0 -0
@@ -2,6 +2,11 @@
2
2
  CHANGELOG
3
3
  *********
4
4
 
5
+ `v5.1.2`_ (2026-06-13)
6
+ ======================
7
+ * Include PR #101 (invalid sequence type error code for external functions)
8
+ * Fix issues #100 and #102
9
+
5
10
  `v5.1.1`_ (2026-01-20)
6
11
  ======================
7
12
  * Fix external function registrations (issue #099)
@@ -537,3 +542,4 @@ CHANGELOG
537
542
  .. _v5.0.4: https://github.com/sissaschool/elementpath/compare/v5.0.3...v5.0.4
538
543
  .. _v5.1.0: https://github.com/sissaschool/elementpath/compare/v5.0.4...v5.1.0
539
544
  .. _v5.1.1: https://github.com/sissaschool/elementpath/compare/v5.1.0...v5.1.1
545
+ .. _v5.1.2: https://github.com/sissaschool/elementpath/compare/v5.1.1...v5.1.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elementpath
3
- Version: 5.1.1
3
+ Version: 5.1.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.1'
33
33
  # The full version, including alpha/beta/rc tags
34
- release = '5.1.1'
34
+ release = '5.1.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.1.1'
10
+ __version__ = '5.1.2'
11
11
  __author__ = "Davide Brunato"
12
12
  __contact__ = "brunato@sissa.it"
13
13
  __copyright__ = "Copyright 2018-2026, SISSA"
@@ -7,6 +7,7 @@
7
7
  #
8
8
  # @author Davide Brunato <brunato@sissa.it>
9
9
  #
10
+ import sys
10
11
  from abc import ABCMeta, abstractmethod
11
12
  from types import MappingProxyType
12
13
  from typing import Any
@@ -58,7 +59,7 @@ class AtomicTypeMeta(ABCMeta):
58
59
  extended_name = f'{{{namespace}}}{name}' if namespace else name
59
60
  prefixed_name = f'{prefix}:{name}' if prefix else name
60
61
 
61
- if not cls.__doc__:
62
+ if not cls.__doc__ and sys.flags.optimize < 2:
62
63
  if ver := getattr(cls, '_xsd_version', ''):
63
64
  ver = ver + ' '
64
65
  params = getattr(cls, '__init__', None).__doc__ or ''
@@ -32,7 +32,7 @@ class UntypedAtomic(AnyAtomicType):
32
32
  name = 'untypedAtomic'
33
33
  value: str
34
34
 
35
- __slots__ = ('value', '_xsd_version', 'parser')
35
+ __slots__ = ('value', 'parser')
36
36
 
37
37
  @classmethod
38
38
  def make(cls, value: Any, **kwargs: Any) -> 'UntypedAtomic':
@@ -112,6 +112,13 @@ class LazyPattern:
112
112
  self._compiled = re.compile(self._pattern, self._flags)
113
113
  return self._compiled.search(string)
114
114
 
115
+ def split(self, string: str) -> list[str]:
116
+ try:
117
+ return self._compiled.split(string)
118
+ except AttributeError:
119
+ self._compiled = re.compile(self._pattern, self._flags)
120
+ return self._compiled.split(string)
121
+
115
122
 
116
123
  class Patterns:
117
124
  """
@@ -14,6 +14,7 @@ from collections.abc import Callable, Iterator, MutableSet
14
14
  from itertools import chain
15
15
  from typing import AbstractSet, Any, Optional, Union
16
16
 
17
+ from elementpath.helpers import LazyPattern
17
18
  from .codepoints import RegexError
18
19
  from .unicode_subsets import UnicodeSubset, lazy_subset, unicode_subset, unicode_category
19
20
 
@@ -87,6 +88,29 @@ CHARACTER_ESCAPES: dict[str, Union[str, Callable[[], UnicodeSubset]]] = {
87
88
  '\\W': w_shortcut,
88
89
  }
89
90
 
91
+ re_char_set = LazyPattern(r'(\\[nrt|.\-^?*+{}()\]sSdDiIcCwW]|\\[pP]{[a-zA-Z\-0-9]+})')
92
+
93
+
94
+ def get_charset_parts(charset: str) -> list[str]:
95
+ parts: list[str] = []
96
+ prev = ''
97
+ for part in re_char_set.split(charset):
98
+ if not part:
99
+ continue
100
+ elif part.endswith('-') and part != '\\-':
101
+ prev = part
102
+ elif part.startswith('-') and parts:
103
+ parts[-1] += prev + part
104
+ prev = ''
105
+ else:
106
+ parts.append(prev + part)
107
+ prev = ''
108
+ else:
109
+ if prev:
110
+ parts.append(prev)
111
+
112
+ return parts
113
+
90
114
 
91
115
  class CharacterClass(MutableSet[int]):
92
116
  """
@@ -96,7 +120,7 @@ class CharacterClass(MutableSet[int]):
96
120
  :param xsd_version: the reference XSD version for syntax variants. Defaults to '1.0'.
97
121
  TODO: implement __ior__, __iand__, __ixor__ operators for a full mutable set class.
98
122
  """
99
- _re_char_set = re.compile(r'(?<!.-)(\\[nrt|.\-^?*+{}()\]sSdDiIcCwW]|\\[pP]{[a-zA-Z\-0-9]+})')
123
+ _re_char_set = re.compile(r'(\\[nrt|.\-^?*+{}()\]sSdDiIcCwW]|\\[pP]{[a-zA-Z\-0-9]+})')
100
124
  _re_unicode_ref = re.compile(r'\\([pP]){([\w-]+)}')
101
125
 
102
126
  __slots__ = 'xsd_version', 'positive', 'negative'
@@ -172,7 +196,7 @@ class CharacterClass(MutableSet[int]):
172
196
  if isinstance(charset, int):
173
197
  charset = chr(charset)
174
198
 
175
- for part in self._re_char_set.split(charset):
199
+ for part in get_charset_parts(charset):
176
200
  if part in CHARACTER_ESCAPES:
177
201
  value = CHARACTER_ESCAPES[part]
178
202
  if isinstance(value, str):
@@ -204,7 +228,7 @@ class CharacterClass(MutableSet[int]):
204
228
  if isinstance(charset, int):
205
229
  charset = chr(charset)
206
230
 
207
- for part in self._re_char_set.split(charset):
231
+ for part in get_charset_parts(charset):
208
232
  if part in CHARACTER_ESCAPES:
209
233
  value = CHARACTER_ESCAPES[part]
210
234
  if isinstance(value, str):
@@ -38,6 +38,31 @@ SER_PARAM_STANDALONE = '{%s}standalone' % XSLT_XQUERY_SERIALIZATION_NAMESPACE
38
38
  SER_PARAM_ITEM_SEPARATOR = '{%s}item-separator' % XSLT_XQUERY_SERIALIZATION_NAMESPACE
39
39
 
40
40
 
41
+ class MapEncodingDict(dict[AnyAtomicType | None, Any]):
42
+ """
43
+ A bridge dict-type for encoding XPath maps to JSON.
44
+ """
45
+ def __init__(self, items: Any) -> None:
46
+ self[None] = None
47
+ self._items = items
48
+
49
+ def __getitem__(self, key: AnyAtomicType | None, /) -> Any:
50
+ for k, v in self._items:
51
+ if key == k:
52
+ return v
53
+ raise KeyError(key)
54
+
55
+ def __iter__(self) -> Iterator[AnyAtomicType | None]:
56
+ for k, _ in self._items:
57
+ yield k
58
+
59
+ def __len__(self) -> int:
60
+ return len(self._items)
61
+
62
+ def items(self) -> Any:
63
+ return self._items
64
+
65
+
41
66
  def get_serialization_params(params: Union[None, ElementNode, XPathMap] = None,
42
67
  token: Optional[XPathToken] = None) -> dict['str', Any]:
43
68
 
@@ -337,14 +362,6 @@ def serialize_to_json(elements: Iterable[Any],
337
362
  if etree_module is None:
338
363
  etree_module = ElementTree
339
364
 
340
- class MapEncodingDict(dict): # type: ignore[type-arg]
341
- def __init__(self, items: Any) -> None:
342
- self[None] = None
343
- self._items = items
344
-
345
- def items(self) -> Any:
346
- return self._items
347
-
348
365
  class XPathEncoder(json.JSONEncoder):
349
366
 
350
367
  def default(self, obj: Any) -> Any:
@@ -154,6 +154,10 @@ class XPathToken(Token[ta.XPathTokenType]):
154
154
  return True
155
155
  return not self._items[1].label.endswith('function')
156
156
 
157
+ def is_reference(self) -> bool:
158
+ """Is `True` if the token is a function reference, `False` otherwise."""
159
+ return False
160
+
157
161
  ###
158
162
  # Tokens tree analysis methods
159
163
  def iter_leaf_elements(self) -> Iterator[str]:
@@ -321,7 +325,7 @@ class XPathToken(Token[ta.XPathTokenType]):
321
325
  else:
322
326
  return default
323
327
  else:
324
- if isinstance(token, XPathToken) and callable(token) and token.is_reference():
328
+ if token.is_reference():
325
329
  return token # It's a function reference
326
330
 
327
331
  item = None
@@ -227,10 +227,8 @@ class XPathFunction(XPathToken):
227
227
  else:
228
228
  return None
229
229
 
230
- def is_reference(self) -> int:
231
- if not isinstance(self.nargs, int):
232
- return False
233
- return self.nargs and not len(self._items)
230
+ def is_reference(self) -> bool:
231
+ return isinstance(self.nargs, int) and self.nargs != 0 and not self._items
234
232
 
235
233
  def nud(self) -> 'XPathFunction':
236
234
  if not self.parser.parse_arguments:
@@ -413,12 +411,12 @@ class ExternalFunction(XPathFunction):
413
411
  for k, (arg, st) in enumerate(zip(args, self.sequence_types), start=1):
414
412
  if not match_sequence_type(arg, st, self.parser):
415
413
  msg = f"{ordinal(k)} argument does not match sequence type {st!r}"
416
- raise self.error('XPDY0050', msg)
414
+ raise self.error('XPTY0004', msg)
417
415
 
418
416
  result = self.callback(*args)
419
417
  if not match_sequence_type(result, self.sequence_types[-1], self.parser):
420
418
  msg = f"Result does not match sequence type {self.sequence_types[-1]!r}"
421
- raise self.error('XPDY0050', msg)
419
+ raise self.error('XPTY0004', msg)
422
420
  return result
423
421
 
424
422
  return self.callback(*args)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: elementpath
3
- Version: 5.1.1
3
+ Version: 5.1.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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "elementpath"
7
- version = "5.1.1"
7
+ version = "5.1.2"
8
8
  description = "XPath 1.0/2.0/3.0/3.1 parsers and selectors for ElementTree and lxml"
9
9
  readme = "README.rst"
10
10
  license = "MIT"
@@ -799,6 +799,10 @@ class TestPatterns(unittest.TestCase):
799
799
  self.assertIsNone(pattern.search('first\tsecond\tthird'))
800
800
  self.assertEqual(pattern.search('first second third').group(0), 'first second third')
801
801
 
802
+ def test_issue_100(self):
803
+ self.assertNotIn('\\s', translate_pattern(r"[\s\-',]{1,255}"))
804
+ self.assertNotIn('\\s', translate_pattern(r"[\-\s',]{1,255}"))
805
+
802
806
  def test_dot_wildcard(self):
803
807
  regex = translate_pattern('.+', anchors=False)
804
808
  self.assertEqual(regex, '^([^\\r\\n]+)$(?!\\n\\Z)')
@@ -1287,7 +1287,7 @@ class XPath2ParserTest(test_xpath1_parser.XPath1ParserTest):
1287
1287
  self.check_selector('//@*/local-name()', root, result)
1288
1288
  self.check_selector('//@*/name()', root, result)
1289
1289
 
1290
- def test_external_function_registration(self):
1290
+ def test_external_function_registration__issue_099(self):
1291
1291
  parser = self.parser.__class__(namespaces={'tns0': 'https://elementpath.test/tns0'})
1292
1292
 
1293
1293
  def foo(x):
@@ -1354,6 +1354,20 @@ class XPath2ParserTest(test_xpath1_parser.XPath1ParserTest):
1354
1354
 
1355
1355
  assert issubclass(token_class, ProxyToken)
1356
1356
  self.assertEqual(token_class.lookup_name, 'foo')
1357
+ self.assertFalse(hasattr(token_class, 'nargs'))
1358
+ self.assertIsNone(token_class.namespace)
1359
+
1360
+ token_class = parser.symbol_table['{https://elementpath.test/tns0}foo']
1361
+ self.assertTrue(issubclass(token_class, XPathFunction))
1362
+
1363
+ assert issubclass(token_class, XPathFunction)
1364
+ self.assertEqual(token_class.lookup_name, '{https://elementpath.test/tns0}foo')
1365
+ self.assertEqual(token_class.nargs, 2)
1366
+ self.assertEqual(token_class.namespace, 'https://elementpath.test/tns0')
1367
+
1368
+ with self.assertRaises(ValueError) as ctx:
1369
+ parser.external_function(foo2, 'foo', prefix='tns0')
1370
+ self.assertEqual(str(ctx.exception), "function 'tns0:foo' is already registered")
1357
1371
 
1358
1372
  token = parser.parse('foo(8)')
1359
1373
  self.assertEqual(token.evaluate(), '8')
@@ -4,10 +4,10 @@
4
4
  # and then run "tox" from this directory.
5
5
 
6
6
  [tox]
7
- min_version = 4.0
7
+ min_version = 4.50
8
8
  envlist =
9
- py{310,311,312,313,314,314,315,315-lxml,py3}, docs, flake8,
10
- mypy-py{310,311,312,313,314,315,py3}, pytest, coverage,
9
+ flake8, py{310,311,312,313,314,314,314t,314-opt,315,315-lxml,py310,py311},
10
+ docs, mypy-py{310,311,312,313,314,315}, pytest, coverage,
11
11
  xmlschema{401,410}, w3c-xsdtests, w3c-qt3tests
12
12
  skip_missing_interpreters = true
13
13
  work_dir = {tox_root}/../.tox/elementpath
@@ -22,6 +22,18 @@ set_env =
22
22
  py313: TEST_UNICODE_INSTALLATION = 6.2.0
23
23
  commands = python -m unittest
24
24
 
25
+ [testenv:py314-opt]
26
+ deps =
27
+ lxml
28
+ xmlschema~=4.2.0
29
+ commands = python -OO -m unittest
30
+
31
+ [testenv:py314t]
32
+ deps =
33
+ lxml
34
+ xmlschema~=4.2.0
35
+ commands = python -Xgil=0 -m unittest
36
+
25
37
  [testenv:py315]
26
38
  deps =
27
39
  xmlschema~=4.2.0
@@ -54,10 +66,9 @@ commands =
54
66
  flake8 elementpath
55
67
  flake8 tests
56
68
 
57
- [testenv:mypy-py{310,311,312,313,314,315,py3}]
69
+ [testenv:mypy-py{310,311,312,313,314,315}]
58
70
  deps =
59
- mypy==1.19.1; platform_python_implementation != 'PyPy'
60
- mypy==1.18.2; platform_python_implementation == 'PyPy'
71
+ mypy==2.1.0
61
72
  xmlschema~=4.2.0
62
73
  lxml-stubs
63
74
  commands =
@@ -89,7 +100,7 @@ set_env =
89
100
  xmlschema420: VERSION = 4.2.0
90
101
  change_dir = {env_tmp_dir}
91
102
  deps =
92
- mypy==1.19.1
103
+ mypy==2.1.0
93
104
  lxml
94
105
  lxml-stubs
95
106
  jinja2
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes