elementpath 4.6.0__tar.gz → 4.7.0__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 (131) hide show
  1. {elementpath-4.6.0 → elementpath-4.7.0}/CHANGELOG.rst +6 -0
  2. {elementpath-4.6.0 → elementpath-4.7.0}/PKG-INFO +1 -1
  3. {elementpath-4.6.0 → elementpath-4.7.0}/doc/conf.py +2 -2
  4. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/__init__.py +1 -1
  5. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/etree.py +26 -6
  6. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/tree_builders.py +58 -37
  7. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath2/_xpath2_constructors.py +14 -0
  8. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath2/xpath2_parser.py +3 -0
  9. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath30/_xpath30_functions.py +5 -2
  10. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath31/_xpath31_operators.py +2 -0
  11. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath_context.py +17 -22
  12. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath_nodes.py +63 -56
  13. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath_selectors.py +59 -29
  14. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath.egg-info/PKG-INFO +1 -1
  15. {elementpath-4.6.0 → elementpath-4.7.0}/setup.py +1 -1
  16. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_selectors.py +44 -0
  17. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath31.py +33 -0
  18. {elementpath-4.6.0 → elementpath-4.7.0}/tox.ini +3 -2
  19. {elementpath-4.6.0 → elementpath-4.7.0}/.coveragerc +0 -0
  20. {elementpath-4.6.0 → elementpath-4.7.0}/LICENSE +0 -0
  21. {elementpath-4.6.0 → elementpath-4.7.0}/MANIFEST.in +0 -0
  22. {elementpath-4.6.0 → elementpath-4.7.0}/README.rst +0 -0
  23. {elementpath-4.6.0 → elementpath-4.7.0}/doc/Makefile +0 -0
  24. {elementpath-4.6.0 → elementpath-4.7.0}/doc/advanced.rst +0 -0
  25. {elementpath-4.6.0 → elementpath-4.7.0}/doc/index.rst +0 -0
  26. {elementpath-4.6.0 → elementpath-4.7.0}/doc/introduction.rst +0 -0
  27. {elementpath-4.6.0 → elementpath-4.7.0}/doc/make.bat +0 -0
  28. {elementpath-4.6.0 → elementpath-4.7.0}/doc/pratt_api.rst +0 -0
  29. {elementpath-4.6.0 → elementpath-4.7.0}/doc/requirements.txt +0 -0
  30. {elementpath-4.6.0 → elementpath-4.7.0}/doc/xpath_api.rst +0 -0
  31. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/_typing.py +0 -0
  32. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/aliases.py +0 -0
  33. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/collations.py +0 -0
  34. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/compare.py +0 -0
  35. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/__init__.py +0 -0
  36. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/atomic_types.py +0 -0
  37. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/binary.py +0 -0
  38. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/datetime.py +0 -0
  39. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/numeric.py +0 -0
  40. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/proxies.py +0 -0
  41. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/qname.py +0 -0
  42. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/string.py +0 -0
  43. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/untyped.py +0 -0
  44. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/datatypes/uri.py +0 -0
  45. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/decoder.py +0 -0
  46. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/exceptions.py +0 -0
  47. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/helpers.py +0 -0
  48. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/namespaces.py +0 -0
  49. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/protocols.py +0 -0
  50. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/py.typed +0 -0
  51. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/regex/__init__.py +0 -0
  52. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/regex/character_classes.py +0 -0
  53. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/regex/codepoints.py +0 -0
  54. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/regex/patterns.py +0 -0
  55. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/regex/unicode_blocks.py +0 -0
  56. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/regex/unicode_categories.py +0 -0
  57. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/regex/unicode_subsets.py +0 -0
  58. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/schema_proxy.py +0 -0
  59. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/sequence_types.py +0 -0
  60. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/serialization.py +0 -0
  61. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/tdop.py +0 -0
  62. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/validators/__init__.py +0 -0
  63. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/validators/analyze-string.xsd +0 -0
  64. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/validators/schema-for-json.xsd +0 -0
  65. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath1/__init__.py +0 -0
  66. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath1/_xpath1_axes.py +0 -0
  67. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath1/_xpath1_functions.py +0 -0
  68. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath1/_xpath1_operators.py +0 -0
  69. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath1/xpath1_parser.py +0 -0
  70. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath2/__init__.py +0 -0
  71. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath2/_xpath2_functions.py +0 -0
  72. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath2/_xpath2_operators.py +0 -0
  73. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath3.py +0 -0
  74. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath30/__init__.py +0 -0
  75. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath30/_translation_maps.py +0 -0
  76. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath30/_xpath30_operators.py +0 -0
  77. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath30/xpath30_helpers.py +0 -0
  78. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath30/xpath30_parser.py +0 -0
  79. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath31/__init__.py +0 -0
  80. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath31/_xpath31_functions.py +0 -0
  81. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath31/xpath31_parser.py +0 -0
  82. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath/xpath_tokens.py +0 -0
  83. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath.egg-info/SOURCES.txt +0 -0
  84. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath.egg-info/dependency_links.txt +0 -0
  85. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath.egg-info/requires.txt +0 -0
  86. {elementpath-4.6.0 → elementpath-4.7.0}/elementpath.egg-info/top_level.txt +0 -0
  87. {elementpath-4.6.0 → elementpath-4.7.0}/mypy.ini +0 -0
  88. {elementpath-4.6.0 → elementpath-4.7.0}/requirements-dev.txt +0 -0
  89. {elementpath-4.6.0 → elementpath-4.7.0}/scripts/generate_codepoints.py +0 -0
  90. {elementpath-4.6.0 → elementpath-4.7.0}/setup.cfg +0 -0
  91. {elementpath-4.6.0 → elementpath-4.7.0}/tests/__init__.py +0 -0
  92. {elementpath-4.6.0 → elementpath-4.7.0}/tests/execute_w3c_tests.py +0 -0
  93. {elementpath-4.6.0 → elementpath-4.7.0}/tests/memory_profiling.py +0 -0
  94. {elementpath-4.6.0 → elementpath-4.7.0}/tests/mypy_tests/advanced.py +0 -0
  95. {elementpath-4.6.0 → elementpath-4.7.0}/tests/mypy_tests/protocols.py +0 -0
  96. {elementpath-4.6.0 → elementpath-4.7.0}/tests/mypy_tests/selectors.py +0 -0
  97. {elementpath-4.6.0 → elementpath-4.7.0}/tests/resources/analyze-string.xsd +0 -0
  98. {elementpath-4.6.0 → elementpath-4.7.0}/tests/resources/external_entity.xml +0 -0
  99. {elementpath-4.6.0 → elementpath-4.7.0}/tests/resources/sample.xml +0 -0
  100. {elementpath-4.6.0 → elementpath-4.7.0}/tests/resources/schema-for-json.xsd +0 -0
  101. {elementpath-4.6.0 → elementpath-4.7.0}/tests/resources/unparsed_entity.xml +0 -0
  102. {elementpath-4.6.0 → elementpath-4.7.0}/tests/resources/unused_external_entity.xml +0 -0
  103. {elementpath-4.6.0 → elementpath-4.7.0}/tests/resources/unused_unparsed_entity.xml +0 -0
  104. {elementpath-4.6.0 → elementpath-4.7.0}/tests/resources/with_entity.xml +0 -0
  105. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_collations.py +0 -0
  106. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_compare.py +0 -0
  107. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_datatypes.py +0 -0
  108. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_elementpath.py +0 -0
  109. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_etree.py +0 -0
  110. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_exceptions.py +0 -0
  111. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_helpers.py +0 -0
  112. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_namespaces.py +0 -0
  113. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_package.py +0 -0
  114. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_regex.py +0 -0
  115. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_schema_context.py +0 -0
  116. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_schema_proxy.py +0 -0
  117. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_sequence_types.py +0 -0
  118. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_serialization.py +0 -0
  119. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_tdop_parser.py +0 -0
  120. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_tree_builders.py +0 -0
  121. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_typing.py +0 -0
  122. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_validators.py +0 -0
  123. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath1_parser.py +0 -0
  124. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath2_constructors.py +0 -0
  125. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath2_functions.py +0 -0
  126. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath2_parser.py +0 -0
  127. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath30.py +0 -0
  128. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath_context.py +0 -0
  129. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath_nodes.py +0 -0
  130. {elementpath-4.6.0 → elementpath-4.7.0}/tests/test_xpath_tokens.py +0 -0
  131. {elementpath-4.6.0 → elementpath-4.7.0}/tests/xpath_test_class.py +0 -0
@@ -2,6 +2,11 @@
2
2
  CHANGELOG
3
3
  *********
4
4
 
5
+ `v4.7.0`_ (2024-12-20)
6
+ ======================
7
+ * Fix *fragment* argument usage (issue #81)
8
+ * Fix constructors nud() to skip argument check with XP31+ arrow operator (issue #83)
9
+
5
10
  `v4.6.0`_ (2024-10-27)
6
11
  ======================
7
12
  * Fix XsdAttributeGroupProtocol
@@ -478,3 +483,4 @@ CHANGELOG
478
483
  .. _v4.4.0: https://github.com/sissaschool/elementpath/compare/v4.3.0...v4.4.0
479
484
  .. _v4.5.0: https://github.com/sissaschool/elementpath/compare/v4.4.0...v4.5.0
480
485
  .. _v4.6.0: https://github.com/sissaschool/elementpath/compare/v4.5.0...v4.6.0
486
+ .. _v4.7.0: https://github.com/sissaschool/elementpath/compare/v4.6.0...v4.7.0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: elementpath
3
- Version: 4.6.0
3
+ Version: 4.7.0
4
4
  Summary: XPath 1.0/2.0/3.0/3.1 parsers and selectors for ElementTree and lxml
5
5
  Home-page: https://github.com/sissaschool/elementpath
6
6
  Author: Davide Brunato
@@ -29,9 +29,9 @@ copyright = '2018-2024, SISSA (International School for Advanced Studies)'
29
29
  author = 'Davide Brunato'
30
30
 
31
31
  # The short X.Y version
32
- version = '4.6'
32
+ version = '4.7'
33
33
  # The full version, including alpha/beta/rc tags
34
- release = '4.6.0'
34
+ release = '4.7.0'
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__ = '4.6.0'
10
+ __version__ = '4.7.0'
11
11
  __author__ = "Davide Brunato"
12
12
  __contact__ = "brunato@sissa.it"
13
13
  __copyright__ = "Copyright 2018-2024, SISSA"
@@ -106,7 +106,17 @@ def is_etree_element(obj: Any) -> bool:
106
106
 
107
107
 
108
108
  def is_lxml_etree_element(obj: Any) -> bool:
109
- return is_etree_element(obj) and hasattr(obj, 'getparent') and hasattr(obj, 'nsmap')
109
+ return is_etree_element(obj) and \
110
+ hasattr(obj, 'getparent') and \
111
+ hasattr(obj, 'nsmap') and \
112
+ obj.__class__.__module__ in ('lxml.etree', 'lxml.html')
113
+
114
+
115
+ def is_etree_element_instance(obj: Any) -> bool:
116
+ """Strictly checks that the objects is an ElementTree or lxml.etree Element."""
117
+ return isinstance(obj, ElementTree.Element) or \
118
+ isinstance(obj, PyElementTree.Element) or \
119
+ is_lxml_etree_element(obj)
110
120
 
111
121
 
112
122
  def is_etree_document(obj: Any) -> bool:
@@ -114,7 +124,17 @@ def is_etree_document(obj: Any) -> bool:
114
124
 
115
125
 
116
126
  def is_lxml_etree_document(obj: Any) -> bool:
117
- return is_etree_document(obj) and hasattr(obj, 'xpath') and hasattr(obj, 'xslt')
127
+ return is_etree_document(obj) and \
128
+ hasattr(obj, 'xpath') and \
129
+ hasattr(obj, 'xslt') and \
130
+ obj.__class__.__module__ in ('lxml.etree', 'lxml.html')
131
+
132
+
133
+ def is_etree_document_instance(obj: Any) -> bool:
134
+ """Strictly checks that the objects is an ElementTree or lxml.etree document."""
135
+ return isinstance(obj, ElementTree.ElementTree) or \
136
+ isinstance(obj, PyElementTree.ElementTree) or \
137
+ is_lxml_etree_document(obj)
118
138
 
119
139
 
120
140
  def etree_iter_strings(elem: Union[DocumentProtocol, ElementProtocol],
@@ -237,7 +257,7 @@ def etree_tostring(elem: ElementProtocol,
237
257
  return indent + line
238
258
 
239
259
  etree_module: Any
240
- if not is_etree_element(elem):
260
+ if not is_etree_element_instance(elem):
241
261
  raise TypeError(f"{elem!r} is not an Element")
242
262
  elif isinstance(elem, PyElementTree.Element):
243
263
  etree_module = PyElementTree
@@ -308,6 +328,6 @@ def etree_tostring(elem: ElementProtocol,
308
328
 
309
329
 
310
330
  __all__ = ['ElementTree', 'PyElementTree', 'SafeXMLParser', 'defuse_xml',
311
- 'is_etree_element', 'is_lxml_etree_element', 'is_etree_document',
312
- 'is_lxml_etree_document', 'etree_iter_strings', 'etree_deep_equal',
313
- 'etree_iter_paths', 'etree_tostring']
331
+ 'is_etree_element', 'is_lxml_etree_element', 'is_etree_element_instance',
332
+ 'is_etree_document', 'is_lxml_etree_document', 'is_etree_document_instance',
333
+ 'etree_iter_strings', 'etree_deep_equal', 'etree_iter_paths', 'etree_tostring']
@@ -14,7 +14,7 @@ from elementpath.aliases import NamespacesType
14
14
  from elementpath.exceptions import ElementPathTypeError
15
15
  from elementpath.protocols import ElementProtocol, LxmlElementProtocol, \
16
16
  DocumentProtocol, LxmlDocumentProtocol, XsdElementProtocol
17
- from elementpath.etree import is_etree_document, is_etree_element
17
+ from elementpath.etree import is_etree_document, is_etree_element, is_etree_element_instance
18
18
  from elementpath.xpath_nodes import SchemaElemType, ChildNodeType, \
19
19
  ElementMapType, TextNode, CommentNode, ProcessingInstructionNode, \
20
20
  ElementNode, SchemaElementNode, DocumentNode
@@ -36,7 +36,7 @@ def is_schema(obj: Any) -> bool:
36
36
  def get_node_tree(root: RootArgType,
37
37
  namespaces: Optional[NamespacesType] = None,
38
38
  uri: Optional[str] = None,
39
- fragment: Optional[bool] = False) -> Union[DocumentNode, ElementNode]:
39
+ fragment: Optional[bool] = None) -> Union[DocumentNode, ElementNode]:
40
40
  """
41
41
  Returns a tree of XPath nodes that wrap the provided root tree.
42
42
 
@@ -44,64 +44,69 @@ def get_node_tree(root: RootArgType,
44
44
  :param namespaces: an optional mapping from prefixes to namespace URIs, \
45
45
  Ignored if root is a lxml etree or a schema structure.
46
46
  :param uri: an optional URI associated with the root element or the document.
47
- :param fragment: if `True` a root element is considered a fragment, if `False` \
48
- a root element is considered the root of an XML document. If the root is a \
49
- document node or an ElementTree instance, and fragment is `True` then use the \
50
- root element and returns an element node. If `None` is provided, the root node \
51
- kind is preserved.
47
+ :param fragment: if `True` is provided the root is considered a fragment. In this \
48
+ case if `root` is an ElementTree instance skips it and use the root Element. If \
49
+ `False` is provided creates a dummy document when the root is an Element instance. \
50
+ For default the root node kind is preserved.
52
51
  """
53
52
  if isinstance(root, (DocumentNode, ElementNode)):
54
53
  if uri is not None and root.uri is None:
55
54
  root.uri = uri
56
55
 
57
- if fragment and isinstance(root, DocumentNode):
58
- root_node = root.getroot()
59
- if root_node.uri is None:
60
- root_node.uri = root.uri
61
- return root_node
56
+ if fragment:
57
+ if isinstance(root, DocumentNode):
58
+ root_node = root.getroot()
59
+ if root_node.uri is None:
60
+ root_node.uri = root.uri
61
+ return root_node
62
+ elif fragment is False and \
63
+ isinstance(root, ElementNode) and \
64
+ is_etree_element_instance(root.elem):
65
+ return root.get_document_node(replace=False)
62
66
 
63
67
  return root
64
68
 
65
- # If a fragment is requested remove the ElementTree instance
66
- if is_etree_document(root):
67
- _root = cast(DocumentProtocol, root).getroot() if fragment else root
68
- elif is_etree_element(root) and not callable(cast(ElementProtocol, root).tag):
69
- _root = root
70
- else:
69
+ if not is_etree_document(root) and \
70
+ (not is_etree_element(root) or callable(cast(ElementProtocol, root).tag)):
71
71
  msg = "invalid root {!r}, an Element or an ElementTree or a schema node required"
72
72
  raise ElementPathTypeError(msg.format(root))
73
-
74
- if hasattr(_root, 'xpath'):
73
+ elif hasattr(root, 'xpath'):
75
74
  # a lxml element tree data
76
75
  return build_lxml_node_tree(
77
- cast(LxmlRootType, _root), uri, fragment
76
+ cast(LxmlRootType, root), uri, fragment
78
77
  )
79
- elif hasattr(_root, 'xsd_version') and hasattr(_root, 'maps'):
78
+ elif hasattr(root, 'xsd_version') and hasattr(root, 'maps'):
80
79
  # a schema or a schema node
81
80
  return build_schema_node_tree(
82
81
  cast(SchemaElemType, root), uri
83
82
  )
84
83
  else:
85
84
  return build_node_tree(
86
- cast(ElementTreeRootType, root), namespaces, uri
85
+ cast(ElementTreeRootType, root), namespaces, uri, fragment
87
86
  )
88
87
 
89
88
 
90
89
  def build_node_tree(root: ElementTreeRootType,
91
90
  namespaces: Optional[NamespacesType] = None,
92
- uri: Optional[str] = None) -> Union[DocumentNode, ElementNode]:
91
+ uri: Optional[str] = None,
92
+ fragment: Optional[bool] = None) -> Union[DocumentNode, ElementNode]:
93
93
  """
94
94
  Returns a tree of XPath nodes that wrap the provided root tree.
95
95
 
96
96
  :param root: an Element or an ElementTree.
97
97
  :param namespaces: an optional mapping from prefixes to namespace URIs.
98
98
  :param uri: an optional URI associated with the document or the root element.
99
+ :param fragment: if `True` is provided the root is considered a fragment. In this \
100
+ case if `root` is an ElementTree instance skips it and use the root Element. If \
101
+ `False` is provided creates a dummy document when the root is an Element instance. \
102
+ For default the root node kind is preserved.
99
103
  """
100
104
  elem: ElementProtocol
101
105
  parent: Any
102
106
  elements: Any
103
107
  child: ChildNodeType
104
108
  children: Iterator[Any]
109
+ document: Optional[DocumentProtocol]
105
110
 
106
111
  position = 1
107
112
  if namespaces:
@@ -111,10 +116,18 @@ def build_node_tree(root: ElementTreeRootType,
111
116
 
112
117
  if hasattr(root, 'parse'):
113
118
  document = cast(DocumentProtocol, root)
119
+ root_elem = document.getroot()
120
+ else:
121
+ document = None
122
+ root_elem = root
123
+
124
+ if fragment and root_elem is not None:
125
+ document = None # Explicitly requested a fragment: don't create a document node
126
+
127
+ if document is not None:
114
128
  document_node = DocumentNode(document, uri, position)
115
129
  position += 1
116
130
 
117
- root_elem = document.getroot()
118
131
  if root_elem is None:
119
132
  return document_node
120
133
 
@@ -123,8 +136,9 @@ def build_node_tree(root: ElementTreeRootType,
123
136
  elements = document_node.elements
124
137
  document_node.children.append(root_node)
125
138
  else:
139
+ assert root_elem is not None
126
140
  document_node = None
127
- elem = root
141
+ elem = root_elem
128
142
  root_node = ElementNode(elem, None, position, namespaces)
129
143
  root_node.elements = elements = {}
130
144
  if uri is not None:
@@ -176,7 +190,14 @@ def build_node_tree(root: ElementTreeRootType,
176
190
  try:
177
191
  children, parent = iterators.pop(), ancestors.pop()
178
192
  except IndexError:
179
- return root_node if document_node is None else document_node
193
+ if document_node is not None:
194
+ return document_node
195
+ elif fragment is False and \
196
+ isinstance(root_node, ElementNode) and \
197
+ is_etree_element_instance(root_node.elem):
198
+ return root_node.get_document_node(replace=False)
199
+ else:
200
+ return root_node
180
201
  else:
181
202
  if (tail := parent.children[-1].elem.tail) is not None:
182
203
  parent.children.append(TextNode(tail, parent, position))
@@ -185,17 +206,19 @@ def build_node_tree(root: ElementTreeRootType,
185
206
 
186
207
  def build_lxml_node_tree(root: LxmlRootType,
187
208
  uri: Optional[str] = None,
188
- fragment: Optional[bool] = False) -> Union[DocumentNode, ElementNode]:
209
+ fragment: Optional[bool] = None) -> Union[DocumentNode, ElementNode]:
189
210
  """
190
211
  Returns a tree of XPath nodes that wrap the provided lxml root tree.
191
212
 
192
213
  :param root: a lxml Element or a lxml ElementTree.
193
214
  :param uri: an optional URI associated with the document or the root element.
194
- :param fragment: if `True` a root element is considered a fragment, if `False` \
195
- a root element is considered the root of an XML document. If `None` is provided, \
196
- the root node kind is preserved.
215
+ :param fragment: if `True` is provided the root is considered a fragment. In this \
216
+ case if `root` is an ElementTree instance skips it and use the root Element. If \
217
+ `False` is provided creates a dummy document when the root is an Element instance. \
218
+ For default the root node kind is preserved.
197
219
  """
198
220
  root_node: Union[DocumentNode, ElementNode]
221
+ document: Optional[LxmlDocumentProtocol]
199
222
  parent: Any
200
223
  elements: Any
201
224
  child: ChildNodeType
@@ -204,18 +227,16 @@ def build_lxml_node_tree(root: LxmlRootType,
204
227
  position = 1
205
228
 
206
229
  if fragment:
207
- # Explicitly requested a fragment: don't create a document node.
208
- document = None
230
+ document = None # Explicitly requested a fragment: don't create a document node
209
231
  elif hasattr(root, 'parse'):
210
- # A document (ElementTree instance)
211
232
  document = cast(LxmlDocumentProtocol, root)
212
- elif root.getparent() is None and (
233
+ elif fragment is False or root.getparent() is None and (
213
234
  any(True for _sibling in root.itersiblings(preceding=True)) or
214
235
  any(True for _sibling in root.itersiblings())):
215
- # A root element with siblings, create a document for them.
236
+ # Despite a document is not explicitly requested create a dummy document
237
+ # because the root element has siblings
216
238
  document = root.getroottree()
217
239
  else:
218
- # Explicitly provided a non-root element: do not parse root's siblings.
219
240
  document = None
220
241
 
221
242
  if document is not None:
@@ -363,6 +363,8 @@ def evaluate_datetime_stamp_type(self: XPathConstructor, context: ContextType =
363
363
  def nud_datetime_stamp_type(self: XPathConstructor) -> XPathConstructor:
364
364
  if self.parser.xsd_version == '1.0':
365
365
  raise self.wrong_syntax("xs:dateTimeStamp is not recognized unless XSD 1.1 is enabled")
366
+ if not self.parser.parse_arguments:
367
+ return self
366
368
 
367
369
  try:
368
370
  self.parser.advance('(')
@@ -422,6 +424,9 @@ def cast_notation_type(self: XPathConstructor, value: AtomicType) -> Notation:
422
424
 
423
425
  @method('NOTATION')
424
426
  def nud_notation_type(self: XPathConstructor) -> None:
427
+ if not self.parser.parse_arguments:
428
+ return
429
+
425
430
  self.parser.advance('(')
426
431
  if self.parser.next_token.symbol == ')':
427
432
  raise self.error('XPST0017', 'expected exactly one argument')
@@ -454,6 +459,9 @@ def cast_boolean_type(self: XPathConstructor, value: AtomicType) -> bool:
454
459
 
455
460
  @method('boolean')
456
461
  def nud_boolean_type_and_function(self: XPathConstructor) -> XPathConstructor:
462
+ if not self.parser.parse_arguments:
463
+ return self
464
+
457
465
  self.parser.advance('(')
458
466
  if self.parser.next_token.symbol == ')':
459
467
  msg = 'Too few arguments: expected at least 1 argument'
@@ -502,6 +510,9 @@ def cast_string_type(self: XPathConstructor, value: AtomicType) -> str:
502
510
 
503
511
  @method('string')
504
512
  def nud_string_type_and_function(self: XPathConstructor) -> XPathConstructor:
513
+ if not self.parser.parse_arguments:
514
+ return self
515
+
505
516
  try:
506
517
  self.parser.advance('(')
507
518
  if self.label != 'function' or self.parser.next_token.symbol != ')':
@@ -576,6 +587,9 @@ def cast_datetime_type(self: XPathConstructor, value: AtomicType) \
576
587
  @method('QName')
577
588
  @method('dateTime')
578
589
  def nud_qname_and_datetime(self: XPathConstructor) -> XPathConstructor:
590
+ if not self.parser.parse_arguments:
591
+ return self
592
+
579
593
  try:
580
594
  self.parser.advance('(')
581
595
  self[0:] = self.parser.expression(5),
@@ -249,6 +249,9 @@ class XPath2Parser(XPath1Parser):
249
249
  of the module where the method is called.
250
250
  """
251
251
  def nud_(self: XPathConstructor) -> XPathConstructor:
252
+ if not self.parser.parse_arguments:
253
+ return self
254
+
252
255
  try:
253
256
  self.parser.advance('(')
254
257
  self[0:] = self.parser.expression(5),
@@ -1492,7 +1492,7 @@ def evaluate_parse_xml_fragment_function(self: XPathFunction, context: ContextTy
1492
1492
  else:
1493
1493
  root = etree.XML(arg)
1494
1494
  except etree.ParseError as err:
1495
- # A not parsable fragment try to parse including XML data in a dummy element.
1495
+ # A not parsable fragment: try to parse including XML data in a dummy element.
1496
1496
  try:
1497
1497
  dummy_element_node = get_node_tree(
1498
1498
  root=etree.XML(f'<document>{arg}</document>'),
@@ -1502,7 +1502,7 @@ def evaluate_parse_xml_fragment_function(self: XPathFunction, context: ContextTy
1502
1502
  raise self.error('FODC0006', str(err)) from None
1503
1503
  else:
1504
1504
  assert isinstance(dummy_element_node, ElementNode)
1505
- return DocumentNode.from_element_node(dummy_element_node)
1505
+ return dummy_element_node.get_document_node(replace=True)
1506
1506
  else:
1507
1507
  return cast(DocumentNode, get_node_tree(
1508
1508
  root=etree.ElementTree(root),
@@ -1955,6 +1955,9 @@ def cast_error_type(self: XPathConstructor, value: AtomicType) -> Emptiable[None
1955
1955
  @method('error')
1956
1956
  def nud_error_type_and_function(self: XPathConstructor) -> XPathConstructor:
1957
1957
  self.clear()
1958
+ if not self.parser.parse_arguments:
1959
+ return self
1960
+
1958
1961
  try:
1959
1962
  self.parser.advance('(')
1960
1963
  if self.namespace == XSD_NAMESPACE:
@@ -235,6 +235,8 @@ def led_arrow_operator(self: XPathToken, left: XPathToken) -> XPathToken:
235
235
  self.parser.advance()
236
236
  elif isinstance(next_token, XPathFunction):
237
237
  self[:] = left, next_token
238
+ if next_token.label == 'kind test':
239
+ raise next_token.wrong_syntax()
238
240
  self.parser.advance() # Skip static evaluation of function arguments
239
241
  else:
240
242
  next_token.expected('(name)', ':', 'Q{', '(')
@@ -19,9 +19,9 @@ from elementpath.protocols import ElementProtocol, DocumentProtocol
19
19
  from elementpath.exceptions import ElementPathTypeError
20
20
  from elementpath.tdop import Token
21
21
  from elementpath.datatypes import AnyAtomicType, AtomicType, Timezone, Language
22
- from elementpath.etree import is_etree_element, is_etree_document
22
+ from elementpath.etree import is_etree_element, is_etree_element_instance, is_etree_document
23
23
  from elementpath.xpath_nodes import ChildNodeType, XPathNode, AttributeNode, NamespaceNode, \
24
- CommentNode, ProcessingInstructionNode, ElementNode, DocumentNode, SchemaElementNode
24
+ CommentNode, ProcessingInstructionNode, ElementNode, DocumentNode
25
25
  from elementpath.tree_builders import RootArgType, get_node_tree
26
26
 
27
27
  if TYPE_CHECKING:
@@ -60,10 +60,11 @@ class XPathContext:
60
60
  This can be useful when the dynamic context has additional namespaces and root \
61
61
  is an Element or an ElementTree instance of the standard library.
62
62
  :param uri: an optional URI associated with the root element or the document.
63
- :param fragment: if `True` a root element is considered a fragment, if `False` \
64
- a root element is considered the root of an XML document, and a dummy document \
65
- is created for selection. In this case the dummy document value is not included \
66
- in the results. If `None` is provided, the root node kind is preserved.
63
+ :param fragment: if `True` is provided the root is considered a fragment. In this \
64
+ case if `root` is an ElementTree instance skips it and use the root Element. If \
65
+ `False` is provided creates a dummy document when the root is an Element instance. \
66
+ In this case the dummy document value is not included in results. For default the \
67
+ root node kind is preserved.
67
68
  :param item: the context item. A `None` value means that the context is positioned on \
68
69
  the document node.
69
70
  :param position: the current position of the node within the input sequence.
@@ -104,7 +105,7 @@ class XPathContext:
104
105
  root: Optional[RootArgType] = None,
105
106
  namespaces: Optional[NamespacesType] = None,
106
107
  uri: Optional[str] = None,
107
- fragment: Optional[bool] = False,
108
+ fragment: Optional[bool] = None,
108
109
  item: Optional[ItemArgType] = None,
109
110
  position: int = 1,
110
111
  size: int = 1,
@@ -142,15 +143,11 @@ class XPathContext:
142
143
 
143
144
  if isinstance(self.root, DocumentNode):
144
145
  self.document = self.root
145
- elif fragment or isinstance(self.root, SchemaElementNode):
146
- pass
147
- elif isinstance(self.root, ElementNode):
148
- # Creates a dummy document
149
- document = cast(DocumentProtocol, self.etree.ElementTree(self.root.elem))
150
- self.document = DocumentNode(document, self.root.uri, position=0)
151
- self.document.children.append(self.root)
152
- self.document.elements = cast(Dict[ElementProtocol, ElementNode], self.root.elements)
153
- # self.root.parent = self.document TODO for v5? It's necessary?
146
+ elif fragment is None and \
147
+ isinstance(self.root, ElementNode) and \
148
+ is_etree_element_instance(self.root.elem):
149
+ # Creates a dummy document that will be not included in results
150
+ self.document = self.root.get_document_node(replace=False, as_parent=False)
154
151
 
155
152
  self.position = position
156
153
  self.size = size
@@ -214,12 +211,10 @@ class XPathContext:
214
211
  else:
215
212
  module_name = 'xml.etree.ElementTree'
216
213
 
217
- if not isinstance(module_name, str) or not module_name.startswith('lxml.'):
218
- etree_module_name = 'xml.etree.ElementTree'
214
+ if module_name in ('lxml.etree', 'lxml.html'):
215
+ self._etree: ModuleType = importlib.import_module('lxml.etree')
219
216
  else:
220
- etree_module_name = 'lxml.etree'
221
-
222
- self._etree: ModuleType = importlib.import_module(etree_module_name)
217
+ self._etree = importlib.import_module('xml.etree.ElementTree')
223
218
 
224
219
  return self._etree
225
220
 
@@ -255,7 +250,7 @@ class XPathContext:
255
250
  def get_context_item(self, item: ItemArgType,
256
251
  namespaces: Optional[NamespacesType] = None,
257
252
  uri: Optional[str] = None,
258
- fragment: Optional[bool] = False) -> ItemType:
253
+ fragment: Optional[bool] = None) -> ItemType:
259
254
  """
260
255
  Checks the item and returns an item suitable for XPath processing.
261
256
  For XML trees and elements try a match with an existing node in the