ssc_codegen 0.9.2__tar.gz → 0.9.3__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.
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/PKG-INFO +5 -5
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/README.md +4 -4
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/pyproject.toml +1 -1
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/__init__.py +1 -1
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/nodes_selectors.py +2 -2
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/cli/main.py +2 -2
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/go_goquery.py +2 -1
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/js_pure.py +18 -4
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/py_bs4.py +16 -6
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/py_parsel.py +16 -4
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/py_selectolax.py +16 -4
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/document.py +11 -3
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/pseudo_selectors.py +10 -4
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/.gitignore +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/LICENSE +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/__init__.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/base.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/nodes_array.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/nodes_cast.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/nodes_core.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/nodes_filter.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/nodes_string.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_/nodes_validate.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_build/__init__.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_build/main.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/ast_build/utils.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/compiler.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/consts.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/helpers.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/py_base.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/templates/__init__.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/templates/go_goquery.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/converters/templates/js_pure.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/document_utlis.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/json_to_scc.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/logs.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/schema.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/static_checker/__init__.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/static_checker/base.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/static_checker/callbacks.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/str_utils.py +0 -0
- {ssc_codegen-0.9.2 → ssc_codegen-0.9.3}/ssc_codegen/tokens.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ssc_codegen
|
|
3
|
-
Version: 0.9.
|
|
3
|
+
Version: 0.9.3
|
|
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
|
|
@@ -45,9 +45,9 @@ Project solving next problems:
|
|
|
45
45
|
- generate types or type annotations if possible and documentation
|
|
46
46
|
- simplify code support
|
|
47
47
|
- portability to other languages and HTML parser backends
|
|
48
|
-
- designed for server-side-render pages
|
|
49
|
-
For REST-API, GRAPHQL use tools like [mitmproxy2swagger](https://github.com/alufers/mitmproxy2swagger)
|
|
50
|
-
|
|
48
|
+
- designed for SSR (server-side-render) pages parsers.
|
|
49
|
+
For REST-API, GRAPHQL endpoints use tools like [mitmproxy2swagger](https://github.com/alufers/mitmproxy2swagger)
|
|
50
|
+
|
|
51
51
|
|
|
52
52
|
## Support converters
|
|
53
53
|
|
|
@@ -59,7 +59,7 @@ Current support converters
|
|
|
59
59
|
| ... | parsel | Y | Y | N | ... | ... |
|
|
60
60
|
| ... | selectolax (lexbor) | N | Y | N | ... | ... |
|
|
61
61
|
| js (ES6) | pure (firefox/chrome/nodejs) | Y | Y | Y | Array, Map** | prettier |
|
|
62
|
-
| go (1.10+) | goquery
|
|
62
|
+
| go (1.10+) | goquery (WIP) | N | Y | N | struct(+json anchors), array, map | gofmt |
|
|
63
63
|
|
|
64
64
|
- **CSS3** means support next selectors:
|
|
65
65
|
- basic: (`tag`, `.class`, `#id`, `tag1,tag2`)
|
|
@@ -11,9 +11,9 @@ Project solving next problems:
|
|
|
11
11
|
- generate types or type annotations if possible and documentation
|
|
12
12
|
- simplify code support
|
|
13
13
|
- portability to other languages and HTML parser backends
|
|
14
|
-
- designed for server-side-render pages
|
|
15
|
-
For REST-API, GRAPHQL use tools like [mitmproxy2swagger](https://github.com/alufers/mitmproxy2swagger)
|
|
16
|
-
|
|
14
|
+
- designed for SSR (server-side-render) pages parsers.
|
|
15
|
+
For REST-API, GRAPHQL endpoints use tools like [mitmproxy2swagger](https://github.com/alufers/mitmproxy2swagger)
|
|
16
|
+
|
|
17
17
|
|
|
18
18
|
## Support converters
|
|
19
19
|
|
|
@@ -25,7 +25,7 @@ Current support converters
|
|
|
25
25
|
| ... | parsel | Y | Y | N | ... | ... |
|
|
26
26
|
| ... | selectolax (lexbor) | N | Y | N | ... | ... |
|
|
27
27
|
| js (ES6) | pure (firefox/chrome/nodejs) | Y | Y | Y | Array, Map** | prettier |
|
|
28
|
-
| go (1.10+) | goquery
|
|
28
|
+
| go (1.10+) | goquery (WIP) | N | Y | N | struct(+json anchors), array, map | gofmt |
|
|
29
29
|
|
|
30
30
|
- **CSS3** means support next selectors:
|
|
31
31
|
- basic: (`tag`, `.class`, `#id`, `tag1,tag2`)
|
|
@@ -36,8 +36,8 @@ class ExprXpathAll(BaseAstNode[KW_EXPR_SELECTOR_LIKE, ARGS_EXPR_SELECTOR_LIKE]):
|
|
|
36
36
|
ret_type: VariableType = VariableType.LIST_DOCUMENT
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
KW_EXPR_ATTR = TypedDict("KW_EXPR_ATTR", {"key": str})
|
|
40
|
-
ARGS_EXPR_ATTR = tuple[str]
|
|
39
|
+
KW_EXPR_ATTR = TypedDict("KW_EXPR_ATTR", {"key": tuple[str, ...]})
|
|
40
|
+
ARGS_EXPR_ATTR = tuple[tuple[str, ...]]
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
@dataclass(kw_only=True)
|
|
@@ -216,8 +216,8 @@ def gen_js(
|
|
|
216
216
|
converter = import_converter(f"js_{lib.value}")
|
|
217
217
|
if fmt:
|
|
218
218
|
commands: list[str] = [
|
|
219
|
-
|
|
220
|
-
|
|
219
|
+
"prettier --write --tab-width 4 --single-quote --trailing-comma all --semi {}",
|
|
220
|
+
]
|
|
221
221
|
fmt_cmd = create_fmt_cmd(ssc_files, prefix, suffix, out, commands)
|
|
222
222
|
else:
|
|
223
223
|
fmt_cmd = []
|
|
@@ -1264,4 +1264,5 @@ def pre_list_str_rm_prefix_and_suffix(
|
|
|
1264
1264
|
# TODO impl ExprHasAttr, ExprListHasAttr
|
|
1265
1265
|
# TODO: impl Filter Expr
|
|
1266
1266
|
# TODO: impl ExprListUnique
|
|
1267
|
-
# TODO:
|
|
1267
|
+
# TODO: impl ExprStringMapReplace, ExprListStringMapReplace
|
|
1268
|
+
# TODO: impl ExprAttr, ExprListAttr for multiple extract keys
|
|
@@ -175,6 +175,12 @@ def make_js_docstring(value: str) -> str:
|
|
|
175
175
|
return docstr_start + docstr_parts + docstr_end
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
def py_sequence_to_js_array(values: tuple[str, ...] | list[str]) -> str:
|
|
179
|
+
"""note: value should be wrapper to"""
|
|
180
|
+
val_arr = str(values)
|
|
181
|
+
return "[" + val_arr[1:-1] + "]"
|
|
182
|
+
|
|
183
|
+
|
|
178
184
|
@CONVERTER(Docstring.kind)
|
|
179
185
|
def pre_docstring(node: Docstring) -> str:
|
|
180
186
|
value = node.kwargs["value"]
|
|
@@ -631,15 +637,23 @@ def pre_xpath_all(node: ExprXpathAll) -> str:
|
|
|
631
637
|
@CONVERTER(ExprGetHtmlAttr.kind)
|
|
632
638
|
def pre_html_attr(node: ExprGetHtmlAttr) -> str:
|
|
633
639
|
prv, nxt = prev_next_var(node)
|
|
634
|
-
|
|
635
|
-
|
|
640
|
+
keys = node.kwargs["key"]
|
|
641
|
+
if len(keys) == 1:
|
|
642
|
+
key = keys[0]
|
|
643
|
+
return f"let {nxt} = {prv}.getAttribute({key!r});"
|
|
644
|
+
js_keys = py_sequence_to_js_array(keys)
|
|
645
|
+
return f"let {nxt} = {js_keys}.map(k => {prv}?.getAttribute(k)).filter(Boolean); "
|
|
636
646
|
|
|
637
647
|
|
|
638
648
|
@CONVERTER(ExprGetHtmlAttrAll.kind)
|
|
639
649
|
def pre_html_attr_all(node: ExprGetHtmlAttrAll) -> str:
|
|
640
650
|
prv, nxt = prev_next_var(node)
|
|
641
|
-
|
|
642
|
-
|
|
651
|
+
keys = node.kwargs["key"]
|
|
652
|
+
if len(keys) == 1:
|
|
653
|
+
key = keys[0]
|
|
654
|
+
return f"let {nxt} = {prv}.map(e => e.getAttribute({key!r})); "
|
|
655
|
+
js_keys = py_sequence_to_js_array(keys)
|
|
656
|
+
return f"let {nxt} = Array.from({prv}).flatMap(e => {js_keys}.map(k => e.getAttribute(k)).filter(Boolean)); "
|
|
643
657
|
|
|
644
658
|
|
|
645
659
|
@CONVERTER(ExprGetHtmlText.kind)
|
|
@@ -104,11 +104,14 @@ def pre_html_attr(node: ExprGetHtmlAttr) -> str:
|
|
|
104
104
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
105
105
|
)
|
|
106
106
|
prv, nxt = prev_next_var(node)
|
|
107
|
-
|
|
108
|
-
#
|
|
107
|
+
keys = node.kwargs["key"]
|
|
108
|
+
# bs4 maybe return list of attrs, set string for API consistence
|
|
109
|
+
if len(keys) == 1:
|
|
110
|
+
key = keys[0]
|
|
111
|
+
return indent + f"{nxt} = ' '.join({prv}.get_attribute_list({key!r}))"
|
|
109
112
|
return (
|
|
110
113
|
indent
|
|
111
|
-
+ f"{nxt} =
|
|
114
|
+
+ f"{nxt} = [' '.join({prv}.get_attribute_list(k)) for k in {keys} if {prv}.get(k)]"
|
|
112
115
|
)
|
|
113
116
|
|
|
114
117
|
|
|
@@ -118,11 +121,18 @@ def pre_html_attr_all(node: ExprGetHtmlAttrAll) -> str:
|
|
|
118
121
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
119
122
|
)
|
|
120
123
|
prv, nxt = prev_next_var(node)
|
|
121
|
-
|
|
122
|
-
# as default, bs4 return list of attrs, set string for API consistence
|
|
124
|
+
keys = node.kwargs["key"]
|
|
125
|
+
# as default, bs4 maybe return list of attrs, set string for API consistence
|
|
126
|
+
if len(keys) == 1:
|
|
127
|
+
key = keys[0]
|
|
128
|
+
return (
|
|
129
|
+
indent
|
|
130
|
+
+ f"{nxt} = [' '.join(e.get_attribute_list({key!r})) for e in {prv} if e.get({key!r})]"
|
|
131
|
+
)
|
|
132
|
+
# [tag.get(attr) for tag in tags for attr in ('src', 'href') if tag.has_attr(attr)]
|
|
123
133
|
return (
|
|
124
134
|
indent
|
|
125
|
-
+ f"{nxt} = [
|
|
135
|
+
+ f"{nxt} = [' '.join(e.get_attribute_list(k)) for e in {prv} for k in {keys} if e.get(k)]"
|
|
126
136
|
)
|
|
127
137
|
|
|
128
138
|
|
|
@@ -124,8 +124,14 @@ def pre_html_attr(node: ExprGetHtmlAttr) -> str:
|
|
|
124
124
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
125
125
|
)
|
|
126
126
|
prv, nxt = prev_next_var(node)
|
|
127
|
-
|
|
128
|
-
|
|
127
|
+
keys = node.kwargs["key"]
|
|
128
|
+
if len(keys) == 1:
|
|
129
|
+
key = keys[0]
|
|
130
|
+
return indent + f"{nxt} = {prv}.attrib[{key!r}]"
|
|
131
|
+
return (
|
|
132
|
+
indent
|
|
133
|
+
+ f"{nxt} = [{prv}.attrib[k] for k in {keys} if {prv}.attrib.get(k)]"
|
|
134
|
+
)
|
|
129
135
|
|
|
130
136
|
|
|
131
137
|
@CONVERTER(ExprGetHtmlAttrAll.kind)
|
|
@@ -134,8 +140,14 @@ def pre_html_attr_all(node: ExprGetHtmlAttrAll) -> str:
|
|
|
134
140
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
135
141
|
)
|
|
136
142
|
prv, nxt = prev_next_var(node)
|
|
137
|
-
|
|
138
|
-
|
|
143
|
+
keys = node.kwargs["key"]
|
|
144
|
+
if len(keys) == 1:
|
|
145
|
+
key = keys[0]
|
|
146
|
+
return indent + f"{nxt} = [e.attrib[{key!r}] for e in {prv}]"
|
|
147
|
+
return (
|
|
148
|
+
indent
|
|
149
|
+
+ f"{nxt} = [e.attrib[k] for e in {prv} for k in {keys} if e.attrib.get(k)]"
|
|
150
|
+
)
|
|
139
151
|
|
|
140
152
|
|
|
141
153
|
@CONVERTER(ExprGetHtmlText.kind)
|
|
@@ -110,8 +110,14 @@ def pre_html_attr(node: ExprGetHtmlAttr) -> str:
|
|
|
110
110
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
111
111
|
)
|
|
112
112
|
prv, nxt = prev_next_var(node)
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
keys = node.kwargs["key"]
|
|
114
|
+
if len(keys) == 1:
|
|
115
|
+
key = keys[0]
|
|
116
|
+
return indent + f"{nxt} = {prv}.attributes[{key!r}]"
|
|
117
|
+
return (
|
|
118
|
+
indent
|
|
119
|
+
+ f"{nxt} = [{prv}.attributes[k] for k in {keys} if {prv}.attributes.get(k)]"
|
|
120
|
+
)
|
|
115
121
|
|
|
116
122
|
|
|
117
123
|
@CONVERTER(ExprGetHtmlAttrAll.kind)
|
|
@@ -120,8 +126,14 @@ def pre_html_attr_all(node: ExprGetHtmlAttrAll) -> str:
|
|
|
120
126
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
121
127
|
)
|
|
122
128
|
prv, nxt = prev_next_var(node)
|
|
123
|
-
|
|
124
|
-
|
|
129
|
+
keys = node.kwargs["key"]
|
|
130
|
+
if len(keys) == 1:
|
|
131
|
+
key = keys[0]
|
|
132
|
+
return indent + f"{nxt} = [e.attributes[{key!r}] for e in {prv}]"
|
|
133
|
+
return (
|
|
134
|
+
indent
|
|
135
|
+
+ f"{nxt} = [e.attributes[k] for e in {prv} for k in {keys} if e.attributes.get(k)]"
|
|
136
|
+
)
|
|
125
137
|
|
|
126
138
|
|
|
127
139
|
@CONVERTER(ExprGetHtmlText.kind)
|
|
@@ -187,7 +187,7 @@ class HTMLDocument(BaseDocument):
|
|
|
187
187
|
case "raw":
|
|
188
188
|
self.raw()
|
|
189
189
|
case "attr":
|
|
190
|
-
self.attr(action[1]) # type: ignore
|
|
190
|
+
self.attr(*action[1]) # type: ignore
|
|
191
191
|
case _:
|
|
192
192
|
assert_never(action[0])
|
|
193
193
|
|
|
@@ -252,7 +252,7 @@ class HTMLDocument(BaseDocument):
|
|
|
252
252
|
self._pseudo_query_action_to_expr(action)
|
|
253
253
|
return self
|
|
254
254
|
|
|
255
|
-
def attr(self, key: str) -> Self:
|
|
255
|
+
def attr(self, *key: str) -> Self:
|
|
256
256
|
"""Extract attribute value by name
|
|
257
257
|
|
|
258
258
|
- accept DOCUMENT, return STRING
|
|
@@ -266,7 +266,15 @@ class HTMLDocument(BaseDocument):
|
|
|
266
266
|
case VariableType.LIST_DOCUMENT:
|
|
267
267
|
self._add(ExprGetHtmlAttrAll(kwargs={"key": key}))
|
|
268
268
|
case VariableType.DOCUMENT:
|
|
269
|
-
|
|
269
|
+
if len(key) == 1:
|
|
270
|
+
self._add(ExprGetHtmlAttr(kwargs={"key": key}))
|
|
271
|
+
else:
|
|
272
|
+
self._add(
|
|
273
|
+
ExprGetHtmlAttr(
|
|
274
|
+
kwargs={"key": key},
|
|
275
|
+
ret_type=VariableType.LIST_STRING,
|
|
276
|
+
)
|
|
277
|
+
)
|
|
270
278
|
case _:
|
|
271
279
|
LOGGER.warning(
|
|
272
280
|
"attr(%s): Expected type(s) %s got %s",
|
|
@@ -6,7 +6,7 @@ PseudoAction = Literal["text", "raw", "attr"]
|
|
|
6
6
|
|
|
7
7
|
def parse_pseudo_css_query(
|
|
8
8
|
query: str,
|
|
9
|
-
) -> tuple[str, tuple[PseudoAction | None, str | None]]:
|
|
9
|
+
) -> tuple[str, tuple[PseudoAction | None, str | tuple[str, ...] | None]]:
|
|
10
10
|
"""parse css selector and extract pseudo class action"""
|
|
11
11
|
query = query.strip()
|
|
12
12
|
|
|
@@ -17,14 +17,17 @@ def parse_pseudo_css_query(
|
|
|
17
17
|
return query[: -len("::raw")], ("raw", None)
|
|
18
18
|
|
|
19
19
|
if match := re.search(r"::attr\(([^)]+)\)$", query):
|
|
20
|
-
return query[: match.start()], (
|
|
20
|
+
return query[: match.start()], (
|
|
21
|
+
"attr",
|
|
22
|
+
tuple([i.strip() for i in match.group(1).split(",")]),
|
|
23
|
+
)
|
|
21
24
|
|
|
22
25
|
return query, (None, None)
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
def parse_pseudo_xpath_query(
|
|
26
29
|
query: str,
|
|
27
|
-
) -> tuple[str, tuple[PseudoAction | None, str | None]]:
|
|
30
|
+
) -> tuple[str, tuple[PseudoAction | None, str | tuple[str, ...] | None]]:
|
|
28
31
|
"""parse xpath selector and extract pseudo class action"""
|
|
29
32
|
query = query.strip()
|
|
30
33
|
|
|
@@ -35,7 +38,10 @@ def parse_pseudo_xpath_query(
|
|
|
35
38
|
return query[: -len("/raw()")], ("raw", None)
|
|
36
39
|
|
|
37
40
|
if match := re.search(r"/@([\w:-]+)$", query):
|
|
38
|
-
return query[: match.start()], (
|
|
41
|
+
return query[: match.start()], (
|
|
42
|
+
"attr",
|
|
43
|
+
tuple([i.strip() for i in match.group(1).split(",")]),
|
|
44
|
+
)
|
|
39
45
|
|
|
40
46
|
return query, (None, None)
|
|
41
47
|
|
|
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
|