ssc_codegen 0.8.6__tar.gz → 0.8.8__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.
Potentially problematic release.
This version of ssc_codegen might be problematic. Click here for more details.
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/PKG-INFO +1 -1
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/pyproject.toml +1 -1
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/__init__.py +1 -1
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_build/main.py +7 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/document.py +44 -18
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/document_utlis.py +52 -37
- ssc_codegen-0.8.8/ssc_codegen/pseudo_selectors.py +64 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/.gitignore +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/LICENSE +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/README.md +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/__init__.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/base.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_array.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_cast.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_core.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_selectors.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_string.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_/nodes_validate.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_build/__init__.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/ast_build/utils.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/main.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/compiler.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/consts.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/go_goquery.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/helpers.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/js_pure.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/py_base.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/py_bs4.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/py_selectolax.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/templates/__init__.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/templates/go_goquery.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/converters/templates/js_pure.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/json_to_scc.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/logs.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/schema.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/static_checker/__init__.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/static_checker/base.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/static_checker/callbacks.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/str_utils.py +0 -0
- {ssc_codegen-0.8.6 → ssc_codegen-0.8.8}/ssc_codegen/tokens.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.8
|
|
4
4
|
Summary: Python-dsl code converter to html parser for web scraping
|
|
5
5
|
Project-URL: Documentation, https://github.com/vypivshiy/selector_schema_codegen#readme
|
|
6
6
|
Project-URL: Issues, https://github.com/vypivshiy/selector_schema_codegen/issues
|
|
@@ -288,6 +288,13 @@ def _unwrap_default_node(
|
|
|
288
288
|
default_type = VariableType.OPTIONAL_INT
|
|
289
289
|
case VariableType.FLOAT:
|
|
290
290
|
default_type = VariableType.OPTIONAL_FLOAT
|
|
291
|
+
case VariableType.LIST_STRING:
|
|
292
|
+
default_type = VariableType.OPTIONAL_LIST_STRING
|
|
293
|
+
case VariableType.LIST_INT:
|
|
294
|
+
default_type = VariableType.OPTIONAL_LIST_INT
|
|
295
|
+
case VariableType.LIST_FLOAT:
|
|
296
|
+
default_type = VariableType.OPTIONAL_LIST_FLOAT
|
|
297
|
+
# TODO: warning for BOOL type
|
|
291
298
|
case _:
|
|
292
299
|
assert_never(ret_type) # type: ignore
|
|
293
300
|
expr_default_start = ExprDefaultValueStart(kwargs={"value": value})
|
|
@@ -4,7 +4,7 @@ import logging
|
|
|
4
4
|
from typing import Type, Pattern
|
|
5
5
|
|
|
6
6
|
from cssselect import SelectorSyntaxError
|
|
7
|
-
from typing_extensions import Self
|
|
7
|
+
from typing_extensions import Self, assert_never
|
|
8
8
|
|
|
9
9
|
from ssc_codegen.ast_ import (
|
|
10
10
|
BaseAstNode,
|
|
@@ -63,6 +63,11 @@ from ssc_codegen.document_utlis import (
|
|
|
63
63
|
unverbosify_regex,
|
|
64
64
|
is_ignore_case_regex,
|
|
65
65
|
)
|
|
66
|
+
from ssc_codegen.pseudo_selectors import (
|
|
67
|
+
parse_pseudo_xpath_query,
|
|
68
|
+
parse_pseudo_css_query,
|
|
69
|
+
PseudoAction,
|
|
70
|
+
)
|
|
66
71
|
from ssc_codegen.json_struct import Json
|
|
67
72
|
from ssc_codegen.schema import BaseSchema
|
|
68
73
|
from ssc_codegen.selector_utils import validate_css_query, validate_xpath_query
|
|
@@ -72,6 +77,8 @@ LOGGER = logging.getLogger("ssc_gen")
|
|
|
72
77
|
|
|
73
78
|
|
|
74
79
|
class BaseDocument:
|
|
80
|
+
LOGGER = LOGGER
|
|
81
|
+
|
|
75
82
|
def __init__(self) -> None:
|
|
76
83
|
self._stack: list[BaseAstNode] = []
|
|
77
84
|
|
|
@@ -98,14 +105,6 @@ class BaseDocument:
|
|
|
98
105
|
return VariableType.DOCUMENT
|
|
99
106
|
return self.stack[-1].ret_type
|
|
100
107
|
|
|
101
|
-
@staticmethod
|
|
102
|
-
def _raise_wrong_type_error(
|
|
103
|
-
type_: VariableType, *expected: VariableType
|
|
104
|
-
) -> None:
|
|
105
|
-
fmt_types = "(" + ",".join(i.name for i in expected) + ")"
|
|
106
|
-
msg = f"Expected type(s): {fmt_types}, got {type_.name}"
|
|
107
|
-
raise SyntaxError(msg)
|
|
108
|
-
|
|
109
108
|
def _add(self, expr: BaseAstNode) -> None:
|
|
110
109
|
self._stack.append(expr)
|
|
111
110
|
|
|
@@ -140,6 +139,7 @@ class HTMLDocument(BaseDocument):
|
|
|
140
139
|
- accept: DOCUMENT, return DOCUMENT
|
|
141
140
|
"""
|
|
142
141
|
query = " ".join(query.splitlines())
|
|
142
|
+
query, action = parse_pseudo_css_query(query)
|
|
143
143
|
try:
|
|
144
144
|
validate_css_query(query)
|
|
145
145
|
except SelectorSyntaxError:
|
|
@@ -153,13 +153,29 @@ class HTMLDocument(BaseDocument):
|
|
|
153
153
|
)
|
|
154
154
|
|
|
155
155
|
self._add(ExprCss(kwargs={"query": query}))
|
|
156
|
+
self._pseudo_query_action_to_expr(action)
|
|
156
157
|
return self
|
|
157
158
|
|
|
159
|
+
def _pseudo_query_action_to_expr(
|
|
160
|
+
self, action: tuple[PseudoAction | None, str | None]
|
|
161
|
+
) -> None:
|
|
162
|
+
if action[0]:
|
|
163
|
+
match action[0]:
|
|
164
|
+
case "text":
|
|
165
|
+
self.text()
|
|
166
|
+
case "raw":
|
|
167
|
+
self.raw()
|
|
168
|
+
case "attr":
|
|
169
|
+
self.attr(action[1]) # type: ignore
|
|
170
|
+
case _:
|
|
171
|
+
assert_never(action[0])
|
|
172
|
+
|
|
158
173
|
def xpath(self, query: str) -> Self:
|
|
159
174
|
"""Xpath query. returns first founded element
|
|
160
175
|
- accept: DOCUMENT, return DOCUMENT
|
|
161
176
|
"""
|
|
162
177
|
query = " ".join(query.splitlines())
|
|
178
|
+
query, action = parse_pseudo_xpath_query(query)
|
|
163
179
|
try:
|
|
164
180
|
validate_xpath_query(query)
|
|
165
181
|
except SelectorSyntaxError:
|
|
@@ -172,22 +188,15 @@ class HTMLDocument(BaseDocument):
|
|
|
172
188
|
self.stack_last_ret.name,
|
|
173
189
|
)
|
|
174
190
|
self._add(ExprXpath(kwargs={"query": query}))
|
|
191
|
+
self._pseudo_query_action_to_expr(action)
|
|
175
192
|
return self
|
|
176
|
-
# match self.stack_last_ret:
|
|
177
|
-
# case VariableType.DOCUMENT:
|
|
178
|
-
# self._add(HtmlXpathExpression(query=query))
|
|
179
|
-
# case VariableType.ANY:
|
|
180
|
-
# self._add(HtmlXpathExpression(query=query))
|
|
181
|
-
# case _:
|
|
182
|
-
# self._raise_wrong_type_error(
|
|
183
|
-
# self.stack_last_ret, VariableType.DOCUMENT
|
|
184
|
-
# )
|
|
185
193
|
|
|
186
194
|
def css_all(self, query: str) -> Self:
|
|
187
195
|
"""Css query. returns all founded elements
|
|
188
196
|
- accept: DOCUMENT, return: LIST_DOCUMENT
|
|
189
197
|
"""
|
|
190
198
|
query = " ".join(query.splitlines())
|
|
199
|
+
query, action = parse_pseudo_css_query(query)
|
|
191
200
|
try:
|
|
192
201
|
validate_css_query(query)
|
|
193
202
|
except SelectorSyntaxError:
|
|
@@ -199,6 +208,7 @@ class HTMLDocument(BaseDocument):
|
|
|
199
208
|
self.stack_last_ret.name,
|
|
200
209
|
)
|
|
201
210
|
self._add(ExprCssAll(kwargs={"query": query}))
|
|
211
|
+
self._pseudo_query_action_to_expr(action)
|
|
202
212
|
return self
|
|
203
213
|
|
|
204
214
|
def xpath_all(self, query: str) -> Self:
|
|
@@ -206,6 +216,7 @@ class HTMLDocument(BaseDocument):
|
|
|
206
216
|
- accept: DOCUMENT, return: LIST_DOCUMENT
|
|
207
217
|
"""
|
|
208
218
|
query = " ".join(query.splitlines())
|
|
219
|
+
query, action = parse_pseudo_xpath_query(query)
|
|
209
220
|
try:
|
|
210
221
|
validate_xpath_query(query)
|
|
211
222
|
except SelectorSyntaxError as e:
|
|
@@ -217,6 +228,7 @@ class HTMLDocument(BaseDocument):
|
|
|
217
228
|
self.stack_last_ret.name,
|
|
218
229
|
)
|
|
219
230
|
self._add(ExprXpathAll(kwargs={"query": query}))
|
|
231
|
+
self._pseudo_query_action_to_expr(action)
|
|
220
232
|
return self
|
|
221
233
|
|
|
222
234
|
def attr(self, key: str) -> Self:
|
|
@@ -768,6 +780,13 @@ class AssertDocument(BaseDocument):
|
|
|
768
780
|
- accept DOCUMENT, return DOCUMENT
|
|
769
781
|
"""
|
|
770
782
|
query = " ".join(query.splitlines())
|
|
783
|
+
new_query, action = parse_pseudo_css_query(query)
|
|
784
|
+
if action[0]:
|
|
785
|
+
LOGGER.warning(
|
|
786
|
+
"is_css(%s) not support pseudo parse classes, skip", repr(query)
|
|
787
|
+
)
|
|
788
|
+
query = new_query
|
|
789
|
+
|
|
771
790
|
try:
|
|
772
791
|
validate_css_query(query)
|
|
773
792
|
except SelectorSyntaxError:
|
|
@@ -790,6 +809,13 @@ class AssertDocument(BaseDocument):
|
|
|
790
809
|
- accept DOCUMENT, return DOCUMENT
|
|
791
810
|
"""
|
|
792
811
|
query = " ".join(query.splitlines())
|
|
812
|
+
new_query, action = parse_pseudo_xpath_query(query)
|
|
813
|
+
if action[0]:
|
|
814
|
+
LOGGER.warning(
|
|
815
|
+
"is_xpath(%s) not support pseudo parse classes, skip",
|
|
816
|
+
repr(query),
|
|
817
|
+
)
|
|
818
|
+
query = new_query
|
|
793
819
|
try:
|
|
794
820
|
validate_xpath_query(query)
|
|
795
821
|
except SelectorSyntaxError:
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import re
|
|
2
3
|
from typing import TYPE_CHECKING, Pattern
|
|
3
|
-
import
|
|
4
|
+
from typing_extensions import assert_never
|
|
5
|
+
|
|
4
6
|
from ssc_codegen.ast_ import (
|
|
5
7
|
BaseAstNode,
|
|
6
8
|
ExprIsCss,
|
|
@@ -10,6 +12,12 @@ from ssc_codegen.ast_ import (
|
|
|
10
12
|
ExprXpath,
|
|
11
13
|
ExprCssAll,
|
|
12
14
|
)
|
|
15
|
+
from ssc_codegen.pseudo_selectors import (
|
|
16
|
+
parse_pseudo_xpath_query,
|
|
17
|
+
pseudo_action_to_pseudo_xpath,
|
|
18
|
+
parse_pseudo_css_query,
|
|
19
|
+
pseudo_action_to_pseudo_css,
|
|
20
|
+
)
|
|
13
21
|
from ssc_codegen.selector_utils import css_to_xpath, xpath_to_css
|
|
14
22
|
from ssc_codegen.static_checker.base import AnalyzeResult
|
|
15
23
|
|
|
@@ -18,7 +26,6 @@ LOGGER = logging.getLogger("ssc_gen")
|
|
|
18
26
|
if TYPE_CHECKING:
|
|
19
27
|
from .document import BaseDocument
|
|
20
28
|
|
|
21
|
-
|
|
22
29
|
# https://stackoverflow.com/a/14919203
|
|
23
30
|
CM1_RX = r"(?m)(?<!\\)((\\{2})*)#.*$"
|
|
24
31
|
CM2_RX = r"(\\)?((\\{2})*)(#)"
|
|
@@ -88,35 +95,34 @@ def convert_css_to_xpath(
|
|
|
88
95
|
new_stack: list[BaseAstNode] = []
|
|
89
96
|
|
|
90
97
|
for expr in old_stack:
|
|
98
|
+
if expr.kind not in (ExprCss.kind, ExprCssAll.kind, ExprIsCss.kind):
|
|
99
|
+
new_stack.append(expr)
|
|
100
|
+
continue
|
|
101
|
+
query = expr.kwargs["query"]
|
|
102
|
+
query, pseudo_action = parse_pseudo_css_query(query)
|
|
103
|
+
new_query = css_to_xpath(query, prefix=prefix)
|
|
104
|
+
if pseudo_action[0]:
|
|
105
|
+
new_query += pseudo_action_to_pseudo_css(*pseudo_action)
|
|
91
106
|
match expr.kind:
|
|
92
107
|
case ExprCss.kind:
|
|
93
|
-
|
|
94
|
-
kwargs={
|
|
95
|
-
"query": css_to_xpath(
|
|
96
|
-
expr.kwargs["query"], prefix=prefix
|
|
97
|
-
)
|
|
98
|
-
}
|
|
99
|
-
)
|
|
108
|
+
new_stack.append(ExprXpath(kwargs={"query": new_query}))
|
|
100
109
|
case ExprCssAll.kind:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"query":
|
|
104
|
-
|
|
105
|
-
)
|
|
106
|
-
}
|
|
110
|
+
new_stack.append(
|
|
111
|
+
ExprXpathAll( # type: ignore[assignment]
|
|
112
|
+
kwargs={"query": new_query}
|
|
113
|
+
)
|
|
107
114
|
)
|
|
108
115
|
case ExprIsCss.kind:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
+
new_stack.append(
|
|
117
|
+
ExprIsXpath( # type: ignore[assignment]
|
|
118
|
+
kwargs={
|
|
119
|
+
"query": new_query,
|
|
120
|
+
"msg": expr.kwargs["msg"],
|
|
121
|
+
}
|
|
122
|
+
)
|
|
116
123
|
)
|
|
117
124
|
case _:
|
|
118
|
-
|
|
119
|
-
new_stack.append(new_expr)
|
|
125
|
+
assert_never(expr.kind)
|
|
120
126
|
doc._stack = new_stack
|
|
121
127
|
return doc
|
|
122
128
|
|
|
@@ -126,24 +132,33 @@ def convert_xpath_to_css(doc: "BaseDocument") -> "BaseDocument":
|
|
|
126
132
|
old_stack = doc.stack.copy()
|
|
127
133
|
new_stack: list[BaseAstNode] = []
|
|
128
134
|
for expr in old_stack:
|
|
135
|
+
if expr.kind not in (
|
|
136
|
+
ExprXpath.kind,
|
|
137
|
+
ExprXpathAll.kind,
|
|
138
|
+
ExprIsXpath.kind,
|
|
139
|
+
):
|
|
140
|
+
new_stack.append(expr)
|
|
141
|
+
continue
|
|
142
|
+
query = expr.kwargs["query"]
|
|
143
|
+
query, pseudo_action = parse_pseudo_xpath_query(query)
|
|
144
|
+
new_query = xpath_to_css(query)
|
|
145
|
+
if pseudo_action[0]:
|
|
146
|
+
new_query += pseudo_action_to_pseudo_xpath(*pseudo_action)
|
|
129
147
|
match expr.kind:
|
|
130
148
|
case ExprXpath.kind:
|
|
131
|
-
|
|
132
|
-
kwargs={"query": xpath_to_css(expr.kwargs["query"])}
|
|
133
|
-
)
|
|
149
|
+
new_stack.append(ExprCss(kwargs={"query": new_query}))
|
|
134
150
|
case ExprXpathAll.kind:
|
|
135
|
-
|
|
136
|
-
kwargs={"query": xpath_to_css(expr.kwargs["query"])}
|
|
137
|
-
)
|
|
151
|
+
new_stack.append(ExprCssAll(kwargs={"query": new_query}))
|
|
138
152
|
case ExprIsXpath.kind:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
153
|
+
new_stack.append(
|
|
154
|
+
ExprIsCss(
|
|
155
|
+
kwargs={
|
|
156
|
+
"query": new_query,
|
|
157
|
+
"msg": expr.kwargs["msg"],
|
|
158
|
+
}
|
|
159
|
+
)
|
|
144
160
|
)
|
|
145
161
|
case _:
|
|
146
|
-
|
|
147
|
-
new_stack.append(new_expr)
|
|
162
|
+
assert_never(expr.kind)
|
|
148
163
|
doc._stack = new_stack
|
|
149
164
|
return doc
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from typing import Literal
|
|
3
|
+
|
|
4
|
+
PseudoAction = Literal["text", "raw", "attr"]
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def parse_pseudo_css_query(
|
|
8
|
+
query: str,
|
|
9
|
+
) -> tuple[str, tuple[PseudoAction | None, str | None]]:
|
|
10
|
+
"""parse css selector and extract pseudo class action"""
|
|
11
|
+
query = query.strip()
|
|
12
|
+
|
|
13
|
+
if query.endswith("::text"):
|
|
14
|
+
return query[: -len("::text")], ("text", None)
|
|
15
|
+
|
|
16
|
+
if query.endswith("::raw"):
|
|
17
|
+
return query[: -len("::raw")], ("raw", None)
|
|
18
|
+
|
|
19
|
+
if match := re.search(r"::attr\(([^)]+)\)$", query):
|
|
20
|
+
return query[: match.start()], ("attr", match.group(1))
|
|
21
|
+
|
|
22
|
+
return query, (None, None)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def parse_pseudo_xpath_query(
|
|
26
|
+
query: str,
|
|
27
|
+
) -> tuple[str, tuple[PseudoAction | None, str | None]]:
|
|
28
|
+
"""parse xpath selector and extract pseudo class action"""
|
|
29
|
+
query = query.strip()
|
|
30
|
+
|
|
31
|
+
if query.endswith("/text()"):
|
|
32
|
+
return query[: -len("/text()")], ("text", None)
|
|
33
|
+
|
|
34
|
+
if query.endswith("/raw()"):
|
|
35
|
+
return query[: -len("/raw()")], ("raw", None)
|
|
36
|
+
|
|
37
|
+
if match := re.search(r"/@([\w:-]+)$", query):
|
|
38
|
+
return query[: match.start()], ("attr", match.group(1))
|
|
39
|
+
|
|
40
|
+
return query, (None, None)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def pseudo_action_to_pseudo_css(action: PseudoAction, arg: str | None) -> str:
|
|
44
|
+
match action:
|
|
45
|
+
case "text":
|
|
46
|
+
return "::text"
|
|
47
|
+
case "raw":
|
|
48
|
+
return "::raw"
|
|
49
|
+
case "attr":
|
|
50
|
+
return f"::attr({arg})"
|
|
51
|
+
case _:
|
|
52
|
+
return ""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def pseudo_action_to_pseudo_xpath(action: PseudoAction, arg: str | None) -> str:
|
|
56
|
+
match action:
|
|
57
|
+
case "text":
|
|
58
|
+
return "/text()"
|
|
59
|
+
case "raw":
|
|
60
|
+
return "/raw()"
|
|
61
|
+
case "attr":
|
|
62
|
+
return f"/@{arg}"
|
|
63
|
+
case _:
|
|
64
|
+
return ""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|