ssc_codegen 0.8.5__tar.gz → 0.8.7__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.5 → ssc_codegen-0.8.7}/PKG-INFO +1 -1
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/pyproject.toml +1 -1
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/__init__.py +1 -1
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_/__init__.py +2 -1
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_/nodes_validate.py +16 -2
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/go_goquery.py +49 -3
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/js_pure.py +40 -3
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/py_base.py +39 -3
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/templates/go_goquery.py +39 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/document.py +120 -20
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/document_utlis.py +52 -37
- ssc_codegen-0.8.7/ssc_codegen/pseudo_selectors.py +64 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/static_checker/base.py +3 -1
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/static_checker/callbacks.py +1 -1
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/tokens.py +5 -3
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/.gitignore +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/LICENSE +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/README.md +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/_compat.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_/base.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_/nodes_array.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_/nodes_cast.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_/nodes_core.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_/nodes_selectors.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_/nodes_string.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_build/__init__.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_build/main.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/ast_build/utils.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/cli/__init__.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/cli/cli_callbacks.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/cli/cli_utils.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/cli/code_callbacks.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/cli/consts.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/cli/main.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/cli/runtime_parse_runners.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/compiler.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/consts.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/__init__.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/base.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/helpers.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/py_bs4.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/py_parsel.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/py_selectolax.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/templates/__init__.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/converters/templates/js_pure.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/json_struct.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/json_to_scc.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/logs.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/schema.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/selector_utils.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/static_checker/__init__.py +0 -0
- {ssc_codegen-0.8.5 → ssc_codegen-0.8.7}/ssc_codegen/str_utils.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.7
|
|
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
|
|
@@ -29,4 +29,5 @@ from .nodes_string import ExprStringTrim, ExprStringRegex, ExprStringSplit, Expr
|
|
|
29
29
|
ExprListStringRegexSub, \
|
|
30
30
|
ExprStringRmPrefix, ExprStringRmSuffix, ExprStringRmPrefixAndSuffix, ExprListStringRmPrefixAndSuffix, \
|
|
31
31
|
ExprListStringRmPrefix, ExprListStringRmSuffix
|
|
32
|
-
from .nodes_validate import ExprIsCss, ExprIsEqual,
|
|
32
|
+
from .nodes_validate import ExprIsCss, ExprIsEqual, ExprStringIsRegex, ExprIsXpath, ExprIsNotEqual, ExprIsContains, \
|
|
33
|
+
ExprListStringAnyRegex, ExprListStringAllRegex
|
|
@@ -59,12 +59,26 @@ ARGS_IS_REGEX = tuple[str, bool, str]
|
|
|
59
59
|
|
|
60
60
|
|
|
61
61
|
@dataclass(kw_only=True)
|
|
62
|
-
class
|
|
63
|
-
kind: ClassVar[TokenType] = TokenType.
|
|
62
|
+
class ExprStringIsRegex(BaseAstNode[KW_IS_REGEX, ARGS_IS_REGEX]):
|
|
63
|
+
kind: ClassVar[TokenType] = TokenType.IS_STRING_REGEX_MATCH
|
|
64
64
|
accept_type: VariableType = VariableType.STRING
|
|
65
65
|
ret_type: VariableType = VariableType.STRING
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
@dataclass(kw_only=True)
|
|
69
|
+
class ExprListStringAnyRegex(BaseAstNode[KW_IS_REGEX, ARGS_IS_REGEX]):
|
|
70
|
+
kind: ClassVar[TokenType] = TokenType.ANY_LIST_STRING_REGEX_MATCH
|
|
71
|
+
accept_type: VariableType = VariableType.LIST_STRING
|
|
72
|
+
ret_type: VariableType = VariableType.LIST_STRING
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(kw_only=True)
|
|
76
|
+
class ExprListStringAllRegex(BaseAstNode[KW_IS_REGEX, ARGS_IS_REGEX]):
|
|
77
|
+
kind: ClassVar[TokenType] = TokenType.ALL_LIST_STRING_REGEX_MATCH
|
|
78
|
+
accept_type: VariableType = VariableType.LIST_STRING
|
|
79
|
+
ret_type: VariableType = VariableType.LIST_STRING
|
|
80
|
+
|
|
81
|
+
|
|
68
82
|
KW_IS_SELECT = TypedDict("KW_IS_SELECT", {"query": str, "msg": str})
|
|
69
83
|
ARGS_IS_SELECT = tuple[str, str]
|
|
70
84
|
|
|
@@ -90,7 +90,7 @@ from ssc_codegen.ast_ import (
|
|
|
90
90
|
ExprIsEqual,
|
|
91
91
|
ExprIsNotEqual,
|
|
92
92
|
ExprIsContains,
|
|
93
|
-
|
|
93
|
+
ExprStringIsRegex,
|
|
94
94
|
ExprIsCss,
|
|
95
95
|
ExprToInt,
|
|
96
96
|
ExprToListInt,
|
|
@@ -122,6 +122,8 @@ from ssc_codegen.ast_ import (
|
|
|
122
122
|
ExprListStringRmSuffix,
|
|
123
123
|
ExprStringRmPrefixAndSuffix,
|
|
124
124
|
ExprListStringRmPrefixAndSuffix,
|
|
125
|
+
ExprListStringAnyRegex,
|
|
126
|
+
ExprListStringAllRegex,
|
|
125
127
|
)
|
|
126
128
|
from ssc_codegen.converters.base import BaseCodeConverter
|
|
127
129
|
from ssc_codegen.converters.helpers import (
|
|
@@ -166,6 +168,8 @@ from ssc_codegen.converters.templates.go_goquery import (
|
|
|
166
168
|
J2_PRE_LIST_STR_RM_SUFFIX,
|
|
167
169
|
J2_PRE_STR_RM_PREFIX_AND_SUFFIX,
|
|
168
170
|
J2_PRE_LIST_STR_RM_PREFIX_AND_SUFFIX,
|
|
171
|
+
J2_PRE_LIST_STR_ANY_IS_RE,
|
|
172
|
+
J2_PRE_LIST_STR_ALL_IS_RE,
|
|
169
173
|
)
|
|
170
174
|
from ssc_codegen.str_utils import (
|
|
171
175
|
wrap_backtick,
|
|
@@ -853,8 +857,8 @@ def pre_is_contains(node: ExprIsContains) -> str:
|
|
|
853
857
|
return J2_PRE_IS_CONTAINS.render(**context)
|
|
854
858
|
|
|
855
859
|
|
|
856
|
-
@CONVERTER(
|
|
857
|
-
def pre_is_regex(node:
|
|
860
|
+
@CONVERTER(ExprStringIsRegex.kind)
|
|
861
|
+
def pre_is_regex(node: ExprStringIsRegex) -> str:
|
|
858
862
|
prv, nxt = prev_next_var(node)
|
|
859
863
|
pattern, ignore_case, msg = node.unpack_args()
|
|
860
864
|
|
|
@@ -880,6 +884,48 @@ def pre_is_regex(node: ExprIsRegex) -> str:
|
|
|
880
884
|
return J2_PRE_IS_REGEX.render(**context)
|
|
881
885
|
|
|
882
886
|
|
|
887
|
+
@CONVERTER(ExprListStringAnyRegex.kind)
|
|
888
|
+
def pre_list_str_any_is_regex(node: ExprListStringAnyRegex) -> str:
|
|
889
|
+
prv, nxt = prev_next_var(node)
|
|
890
|
+
pattern, ignore_case, msg = node.unpack_args()
|
|
891
|
+
if ignore_case:
|
|
892
|
+
pattern = f"(?i){pattern}"
|
|
893
|
+
pattern = wrap_backtick(pattern)
|
|
894
|
+
msg = wrap_double_quotes(msg)
|
|
895
|
+
context = {
|
|
896
|
+
"prv": prv,
|
|
897
|
+
"nxt": nxt,
|
|
898
|
+
"pattern": pattern, # Already processed with ignore_case and backticks
|
|
899
|
+
"msg": msg,
|
|
900
|
+
"have_default_expr": have_default_expr(node),
|
|
901
|
+
"is_pre_validate_parent": is_pre_validate_parent(node),
|
|
902
|
+
"is_last_var_no_ret": is_last_var_no_ret(node),
|
|
903
|
+
"return_type": RETURN_ERR_TYPES.get(get_last_ret_type(node), "nil"),
|
|
904
|
+
}
|
|
905
|
+
return J2_PRE_LIST_STR_ANY_IS_RE.render(**context)
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
@CONVERTER(ExprListStringAllRegex.kind)
|
|
909
|
+
def pre_list_str_all_is_regex(node: ExprListStringAllRegex) -> str:
|
|
910
|
+
prv, nxt = prev_next_var(node)
|
|
911
|
+
pattern, ignore_case, msg = node.unpack_args()
|
|
912
|
+
if ignore_case:
|
|
913
|
+
pattern = f"(?i){pattern}"
|
|
914
|
+
pattern = wrap_backtick(pattern)
|
|
915
|
+
msg = wrap_double_quotes(msg)
|
|
916
|
+
context = {
|
|
917
|
+
"prv": prv,
|
|
918
|
+
"nxt": nxt,
|
|
919
|
+
"pattern": pattern, # Already processed with ignore_case and backticks
|
|
920
|
+
"msg": msg,
|
|
921
|
+
"have_default_expr": have_default_expr(node),
|
|
922
|
+
"is_pre_validate_parent": is_pre_validate_parent(node),
|
|
923
|
+
"is_last_var_no_ret": is_last_var_no_ret(node),
|
|
924
|
+
"return_type": RETURN_ERR_TYPES.get(get_last_ret_type(node), "nil"),
|
|
925
|
+
}
|
|
926
|
+
return J2_PRE_LIST_STR_ALL_IS_RE.render(**context)
|
|
927
|
+
|
|
928
|
+
|
|
883
929
|
@CONVERTER(ExprIsCss.kind)
|
|
884
930
|
def pre_is_css(node: ExprIsCss) -> str:
|
|
885
931
|
prv, nxt = prev_next_var(node)
|
|
@@ -54,7 +54,7 @@ from ssc_codegen.ast_ import (
|
|
|
54
54
|
ExprIsEqual,
|
|
55
55
|
ExprIsNotEqual,
|
|
56
56
|
ExprIsContains,
|
|
57
|
-
|
|
57
|
+
ExprStringIsRegex,
|
|
58
58
|
ExprToInt,
|
|
59
59
|
ExprToListInt,
|
|
60
60
|
ExprToFloat,
|
|
@@ -81,6 +81,8 @@ from ssc_codegen.ast_ import (
|
|
|
81
81
|
ExprListStringRmSuffix,
|
|
82
82
|
ExprStringRmPrefixAndSuffix,
|
|
83
83
|
ExprListStringRmPrefixAndSuffix,
|
|
84
|
+
ExprListStringAnyRegex,
|
|
85
|
+
ExprListStringAllRegex,
|
|
84
86
|
)
|
|
85
87
|
from ssc_codegen.converters.base import BaseCodeConverter
|
|
86
88
|
from ssc_codegen.converters.helpers import (
|
|
@@ -442,11 +444,14 @@ def pre_is_contains(node: ExprIsContains) -> str:
|
|
|
442
444
|
return expr + f"let {nxt} = {prv};"
|
|
443
445
|
|
|
444
446
|
|
|
445
|
-
@CONVERTER(
|
|
446
|
-
def pre_is_regex(node:
|
|
447
|
+
@CONVERTER(ExprStringIsRegex.kind)
|
|
448
|
+
def pre_is_regex(node: ExprStringIsRegex) -> str:
|
|
447
449
|
prv, nxt = prev_next_var(node)
|
|
448
450
|
pattern, ignore_case, msg = node.unpack_args()
|
|
449
451
|
pattern = f"/{node.pattern}/g"
|
|
452
|
+
if ignore_case:
|
|
453
|
+
pattern += "i"
|
|
454
|
+
|
|
450
455
|
expr = f"if ({prv}.match({pattern}) === null) throw new Error({msg!r});"
|
|
451
456
|
if is_last_var_no_ret(node):
|
|
452
457
|
return expr
|
|
@@ -454,6 +459,38 @@ def pre_is_regex(node: ExprIsRegex) -> str:
|
|
|
454
459
|
return expr + f"let {nxt} = {prv};"
|
|
455
460
|
|
|
456
461
|
|
|
462
|
+
@CONVERTER(ExprListStringAnyRegex.kind)
|
|
463
|
+
def pre_list_str_any_is_regex(node: ExprListStringAnyRegex) -> str:
|
|
464
|
+
# a.some(i => (new RegExp("foo")).test(i))
|
|
465
|
+
prv, nxt = prev_next_var(node)
|
|
466
|
+
pattern, ignore_case, msg = node.unpack_args()
|
|
467
|
+
pattern = f"/{node.pattern}/g"
|
|
468
|
+
if ignore_case:
|
|
469
|
+
pattern += "i"
|
|
470
|
+
|
|
471
|
+
expr = f"if (!{prv}.some(i => (new RegExp({pattern})).test(i))) throw new Error({msg!r});"
|
|
472
|
+
if is_last_var_no_ret(node):
|
|
473
|
+
return expr
|
|
474
|
+
# HACK: avoid recalc variables
|
|
475
|
+
return expr + f"let {nxt} = {prv};"
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
@CONVERTER(ExprListStringAllRegex.kind)
|
|
479
|
+
def pre_list_str_all_is_regex(node: ExprListStringAllRegex) -> str:
|
|
480
|
+
prv, nxt = prev_next_var(node)
|
|
481
|
+
# a.every(i => (new RegExp("foo")).test)
|
|
482
|
+
pattern, ignore_case, msg = node.unpack_args()
|
|
483
|
+
pattern = f"/{node.pattern}/g"
|
|
484
|
+
if ignore_case:
|
|
485
|
+
pattern += "i"
|
|
486
|
+
|
|
487
|
+
expr = f"if (!{prv}.every(i => (new RegExp({pattern})).test(i))) throw new Error({msg!r});"
|
|
488
|
+
if is_last_var_no_ret(node):
|
|
489
|
+
return expr
|
|
490
|
+
# HACK: avoid recalc variables
|
|
491
|
+
return expr + f"let {nxt} = {prv};"
|
|
492
|
+
|
|
493
|
+
|
|
457
494
|
@CONVERTER(ExprIsCss.kind)
|
|
458
495
|
def pre_is_css(node: ExprIsCss) -> str:
|
|
459
496
|
prv, nxt = prev_next_var(node)
|
|
@@ -72,7 +72,7 @@ from ssc_codegen.ast_ import (
|
|
|
72
72
|
ExprIsEqual,
|
|
73
73
|
ExprIsNotEqual,
|
|
74
74
|
ExprIsContains,
|
|
75
|
-
|
|
75
|
+
ExprStringIsRegex,
|
|
76
76
|
ExprToInt,
|
|
77
77
|
ExprToListInt,
|
|
78
78
|
ExprToFloat,
|
|
@@ -85,6 +85,8 @@ from ssc_codegen.ast_ import (
|
|
|
85
85
|
ExprListStringRmSuffix,
|
|
86
86
|
ExprStringRmPrefixAndSuffix,
|
|
87
87
|
ExprListStringRmPrefixAndSuffix,
|
|
88
|
+
ExprListStringAnyRegex,
|
|
89
|
+
ExprListStringAllRegex,
|
|
88
90
|
)
|
|
89
91
|
from ssc_codegen.converters.base import (
|
|
90
92
|
BaseCodeConverter,
|
|
@@ -222,7 +224,9 @@ class BasePyCodeConverter(BaseCodeConverter):
|
|
|
222
224
|
ExprIsEqual.kind: pre_is_equal,
|
|
223
225
|
ExprIsNotEqual.kind: pre_is_not_equal,
|
|
224
226
|
ExprIsContains.kind: pre_is_contains,
|
|
225
|
-
|
|
227
|
+
ExprStringIsRegex.kind: pre_is_regex,
|
|
228
|
+
ExprListStringAllRegex.kind: pre_list_str_all_is_regex,
|
|
229
|
+
ExprListStringAnyRegex.kind: pre_list_str_any_is_regex,
|
|
226
230
|
ExprToInt.kind: pre_to_int,
|
|
227
231
|
ExprToListInt.kind: pre_to_list_int,
|
|
228
232
|
ExprToFloat.kind: pre_to_float,
|
|
@@ -770,7 +774,7 @@ def pre_is_contains(node: ExprIsContains) -> str:
|
|
|
770
774
|
return expr + "\n" + indent + f"{nxt} = {prv}"
|
|
771
775
|
|
|
772
776
|
|
|
773
|
-
def pre_is_regex(node:
|
|
777
|
+
def pre_is_regex(node: ExprStringIsRegex) -> str:
|
|
774
778
|
indent = (
|
|
775
779
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
776
780
|
)
|
|
@@ -783,6 +787,38 @@ def pre_is_regex(node: ExprIsRegex) -> str:
|
|
|
783
787
|
return expr + "\n" + indent + f"{nxt} = {prv}"
|
|
784
788
|
|
|
785
789
|
|
|
790
|
+
def pre_list_str_any_is_regex(node: ExprListStringAnyRegex) -> str:
|
|
791
|
+
indent = (
|
|
792
|
+
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
793
|
+
)
|
|
794
|
+
prv, nxt = prev_next_var(node)
|
|
795
|
+
pattern, ignore_case, msg = node.unpack_args()
|
|
796
|
+
expr = (
|
|
797
|
+
indent
|
|
798
|
+
+ f"assert any(re.search({pattern!r}, i) for i in {prv}), {msg!r}"
|
|
799
|
+
)
|
|
800
|
+
if is_last_var_no_ret(node):
|
|
801
|
+
return expr
|
|
802
|
+
# HACK: avoid recalc variables
|
|
803
|
+
return expr + "\n" + indent + f"{nxt} = {prv}"
|
|
804
|
+
|
|
805
|
+
|
|
806
|
+
def pre_list_str_all_is_regex(node: ExprListStringAllRegex) -> str:
|
|
807
|
+
indent = (
|
|
808
|
+
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
809
|
+
)
|
|
810
|
+
prv, nxt = prev_next_var(node)
|
|
811
|
+
pattern, ignore_case, msg = node.unpack_args()
|
|
812
|
+
expr = (
|
|
813
|
+
indent
|
|
814
|
+
+ f"assert all(re.search({pattern!r}, i) for i in {prv}), {msg!r}"
|
|
815
|
+
)
|
|
816
|
+
if is_last_var_no_ret(node):
|
|
817
|
+
return expr
|
|
818
|
+
# HACK: avoid recalc variables
|
|
819
|
+
return expr + "\n" + indent + f"{nxt} = {prv}"
|
|
820
|
+
|
|
821
|
+
|
|
786
822
|
def pre_to_int(node: ExprToInt) -> str:
|
|
787
823
|
indent = (
|
|
788
824
|
INDENT_DEFAULT_BODY if have_default_expr(node) else INDENT_METHOD_BODY
|
|
@@ -409,3 +409,42 @@ for _, {{ tmp_var }} := range {{ prv }} {
|
|
|
409
409
|
{{nxt}} = append({{ nxt }}, {{ tmp_var }});
|
|
410
410
|
}
|
|
411
411
|
""")
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
J2_PRE_LIST_STR_ANY_IS_RE = Template("""
|
|
415
|
+
re{{ nxt }} := regexp.MustCompile({{ pattern }});
|
|
416
|
+
for _, {{ tmp_var }} := range {{ prv }} {
|
|
417
|
+
if re{{ nxt }}.MatchString({{ tmp_var }}) {
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
{% if have_default_expr %}
|
|
421
|
+
panic(fmt.Errorf({{ msg }}));
|
|
422
|
+
{% elif is_pre_validate_parent %}
|
|
423
|
+
return nil, fmt.Errorf({{ msg }});
|
|
424
|
+
{% else %}
|
|
425
|
+
return {{ return_type }}, fmt.Errorf({{ msg }});
|
|
426
|
+
{% endif %}
|
|
427
|
+
}
|
|
428
|
+
{% if not is_last_var_no_ret %}
|
|
429
|
+
{{ nxt }} := {{ prv }};
|
|
430
|
+
{% endif %}
|
|
431
|
+
""")
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
J2_PRE_LIST_STR_ALL_IS_RE = Template("""
|
|
435
|
+
re{{ nxt }} := regexp.MustCompile({{ pattern }});
|
|
436
|
+
for _, {{ tmp_var }} := range {{ prv }} {
|
|
437
|
+
if !re{{ nxt }}.MatchString({{ tmp_var }}) {
|
|
438
|
+
{% if have_default_expr %}
|
|
439
|
+
panic(fmt.Errorf({{ msg }}));
|
|
440
|
+
{% elif is_pre_validate_parent %}
|
|
441
|
+
return nil, fmt.Errorf({{ msg }});
|
|
442
|
+
{% else %}
|
|
443
|
+
return {{ return_type }}, fmt.Errorf({{ msg }});
|
|
444
|
+
{% endif %}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
{% if not is_last_var_no_ret %}
|
|
448
|
+
{{ nxt }} := {{ prv }};
|
|
449
|
+
{% endif %}
|
|
450
|
+
""")
|
|
@@ -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,
|
|
@@ -41,7 +41,7 @@ from ssc_codegen.ast_ import (
|
|
|
41
41
|
ExprIsEqual,
|
|
42
42
|
ExprIsNotEqual,
|
|
43
43
|
ExprIsContains,
|
|
44
|
-
|
|
44
|
+
ExprStringIsRegex,
|
|
45
45
|
ExprNested,
|
|
46
46
|
ExprToInt,
|
|
47
47
|
ExprToListInt,
|
|
@@ -55,12 +55,19 @@ from ssc_codegen.ast_ import (
|
|
|
55
55
|
ExprListStringRmSuffix,
|
|
56
56
|
ExprStringRmPrefixAndSuffix,
|
|
57
57
|
ExprListStringRmPrefixAndSuffix,
|
|
58
|
+
ExprListStringAllRegex,
|
|
59
|
+
ExprListStringAnyRegex,
|
|
58
60
|
)
|
|
59
61
|
from ssc_codegen.document_utlis import (
|
|
60
62
|
analyze_re_expression,
|
|
61
63
|
unverbosify_regex,
|
|
62
64
|
is_ignore_case_regex,
|
|
63
65
|
)
|
|
66
|
+
from ssc_codegen.pseudo_selectors import (
|
|
67
|
+
parse_pseudo_xpath_query,
|
|
68
|
+
parse_pseudo_css_query,
|
|
69
|
+
PseudoAction,
|
|
70
|
+
)
|
|
64
71
|
from ssc_codegen.json_struct import Json
|
|
65
72
|
from ssc_codegen.schema import BaseSchema
|
|
66
73
|
from ssc_codegen.selector_utils import validate_css_query, validate_xpath_query
|
|
@@ -70,6 +77,8 @@ LOGGER = logging.getLogger("ssc_gen")
|
|
|
70
77
|
|
|
71
78
|
|
|
72
79
|
class BaseDocument:
|
|
80
|
+
LOGGER = LOGGER
|
|
81
|
+
|
|
73
82
|
def __init__(self) -> None:
|
|
74
83
|
self._stack: list[BaseAstNode] = []
|
|
75
84
|
|
|
@@ -96,14 +105,6 @@ class BaseDocument:
|
|
|
96
105
|
return VariableType.DOCUMENT
|
|
97
106
|
return self.stack[-1].ret_type
|
|
98
107
|
|
|
99
|
-
@staticmethod
|
|
100
|
-
def _raise_wrong_type_error(
|
|
101
|
-
type_: VariableType, *expected: VariableType
|
|
102
|
-
) -> None:
|
|
103
|
-
fmt_types = "(" + ",".join(i.name for i in expected) + ")"
|
|
104
|
-
msg = f"Expected type(s): {fmt_types}, got {type_.name}"
|
|
105
|
-
raise SyntaxError(msg)
|
|
106
|
-
|
|
107
108
|
def _add(self, expr: BaseAstNode) -> None:
|
|
108
109
|
self._stack.append(expr)
|
|
109
110
|
|
|
@@ -138,6 +139,7 @@ class HTMLDocument(BaseDocument):
|
|
|
138
139
|
- accept: DOCUMENT, return DOCUMENT
|
|
139
140
|
"""
|
|
140
141
|
query = " ".join(query.splitlines())
|
|
142
|
+
query, action = parse_pseudo_css_query(query)
|
|
141
143
|
try:
|
|
142
144
|
validate_css_query(query)
|
|
143
145
|
except SelectorSyntaxError:
|
|
@@ -151,13 +153,29 @@ class HTMLDocument(BaseDocument):
|
|
|
151
153
|
)
|
|
152
154
|
|
|
153
155
|
self._add(ExprCss(kwargs={"query": query}))
|
|
156
|
+
self._pseudo_query_action_to_expr(action)
|
|
154
157
|
return self
|
|
155
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
|
+
|
|
156
173
|
def xpath(self, query: str) -> Self:
|
|
157
174
|
"""Xpath query. returns first founded element
|
|
158
175
|
- accept: DOCUMENT, return DOCUMENT
|
|
159
176
|
"""
|
|
160
177
|
query = " ".join(query.splitlines())
|
|
178
|
+
query, action = parse_pseudo_xpath_query(query)
|
|
161
179
|
try:
|
|
162
180
|
validate_xpath_query(query)
|
|
163
181
|
except SelectorSyntaxError:
|
|
@@ -170,22 +188,15 @@ class HTMLDocument(BaseDocument):
|
|
|
170
188
|
self.stack_last_ret.name,
|
|
171
189
|
)
|
|
172
190
|
self._add(ExprXpath(kwargs={"query": query}))
|
|
191
|
+
self._pseudo_query_action_to_expr(action)
|
|
173
192
|
return self
|
|
174
|
-
# match self.stack_last_ret:
|
|
175
|
-
# case VariableType.DOCUMENT:
|
|
176
|
-
# self._add(HtmlXpathExpression(query=query))
|
|
177
|
-
# case VariableType.ANY:
|
|
178
|
-
# self._add(HtmlXpathExpression(query=query))
|
|
179
|
-
# case _:
|
|
180
|
-
# self._raise_wrong_type_error(
|
|
181
|
-
# self.stack_last_ret, VariableType.DOCUMENT
|
|
182
|
-
# )
|
|
183
193
|
|
|
184
194
|
def css_all(self, query: str) -> Self:
|
|
185
195
|
"""Css query. returns all founded elements
|
|
186
196
|
- accept: DOCUMENT, return: LIST_DOCUMENT
|
|
187
197
|
"""
|
|
188
198
|
query = " ".join(query.splitlines())
|
|
199
|
+
query, action = parse_pseudo_css_query(query)
|
|
189
200
|
try:
|
|
190
201
|
validate_css_query(query)
|
|
191
202
|
except SelectorSyntaxError:
|
|
@@ -197,6 +208,7 @@ class HTMLDocument(BaseDocument):
|
|
|
197
208
|
self.stack_last_ret.name,
|
|
198
209
|
)
|
|
199
210
|
self._add(ExprCssAll(kwargs={"query": query}))
|
|
211
|
+
self._pseudo_query_action_to_expr(action)
|
|
200
212
|
return self
|
|
201
213
|
|
|
202
214
|
def xpath_all(self, query: str) -> Self:
|
|
@@ -204,6 +216,7 @@ class HTMLDocument(BaseDocument):
|
|
|
204
216
|
- accept: DOCUMENT, return: LIST_DOCUMENT
|
|
205
217
|
"""
|
|
206
218
|
query = " ".join(query.splitlines())
|
|
219
|
+
query, action = parse_pseudo_xpath_query(query)
|
|
207
220
|
try:
|
|
208
221
|
validate_xpath_query(query)
|
|
209
222
|
except SelectorSyntaxError as e:
|
|
@@ -215,6 +228,7 @@ class HTMLDocument(BaseDocument):
|
|
|
215
228
|
self.stack_last_ret.name,
|
|
216
229
|
)
|
|
217
230
|
self._add(ExprXpathAll(kwargs={"query": query}))
|
|
231
|
+
self._pseudo_query_action_to_expr(action)
|
|
218
232
|
return self
|
|
219
233
|
|
|
220
234
|
def attr(self, key: str) -> Self:
|
|
@@ -766,6 +780,13 @@ class AssertDocument(BaseDocument):
|
|
|
766
780
|
- accept DOCUMENT, return DOCUMENT
|
|
767
781
|
"""
|
|
768
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
|
+
|
|
769
790
|
try:
|
|
770
791
|
validate_css_query(query)
|
|
771
792
|
except SelectorSyntaxError:
|
|
@@ -788,6 +809,13 @@ class AssertDocument(BaseDocument):
|
|
|
788
809
|
- accept DOCUMENT, return DOCUMENT
|
|
789
810
|
"""
|
|
790
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
|
|
791
819
|
try:
|
|
792
820
|
validate_xpath_query(query)
|
|
793
821
|
except SelectorSyntaxError:
|
|
@@ -953,6 +981,78 @@ class AssertDocument(BaseDocument):
|
|
|
953
981
|
)
|
|
954
982
|
return self
|
|
955
983
|
|
|
984
|
+
def any_is_re(
|
|
985
|
+
self, pattern: str | Pattern, msg: str = "", ignore_case: bool = False
|
|
986
|
+
) -> Self:
|
|
987
|
+
"""assert any value matched in array of strings by regex.
|
|
988
|
+
If in generated code check failed - throw exception with passed msg
|
|
989
|
+
|
|
990
|
+
EXPR DO NOT MODIFY variable
|
|
991
|
+
|
|
992
|
+
- accept LIST_STRING, return LIST_STRING
|
|
993
|
+
"""
|
|
994
|
+
if not isinstance(pattern, str):
|
|
995
|
+
ignore_case = is_ignore_case_regex(pattern)
|
|
996
|
+
pattern = unverbosify_regex(pattern)
|
|
997
|
+
analyze_re_expression(pattern, allow_empty_groups=True)
|
|
998
|
+
if self.stack_last_ret != VariableType.LIST_STRING:
|
|
999
|
+
LOGGER.warning(
|
|
1000
|
+
"any_is_re(%s): Expected type(s) %s got %s",
|
|
1001
|
+
repr(pattern),
|
|
1002
|
+
VariableType.LIST_STRING.name,
|
|
1003
|
+
self.stack_last_ret.name,
|
|
1004
|
+
)
|
|
1005
|
+
|
|
1006
|
+
self._add(
|
|
1007
|
+
ExprListStringAnyRegex(
|
|
1008
|
+
kwargs={
|
|
1009
|
+
"pattern": pattern,
|
|
1010
|
+
"ignore_case": ignore_case,
|
|
1011
|
+
"msg": msg,
|
|
1012
|
+
}
|
|
1013
|
+
)
|
|
1014
|
+
)
|
|
1015
|
+
return self
|
|
1016
|
+
|
|
1017
|
+
def all_is_re(
|
|
1018
|
+
self, pattern: str | Pattern, msg: str = "", ignore_case: bool = False
|
|
1019
|
+
) -> Self:
|
|
1020
|
+
"""assert all value matched in array of strings by regex.
|
|
1021
|
+
If in generated code check failed - throw exception with passed msg
|
|
1022
|
+
|
|
1023
|
+
EXPR DO NOT MODIFY variable
|
|
1024
|
+
|
|
1025
|
+
- accept LIST_STRING, return LIST_STRING
|
|
1026
|
+
"""
|
|
1027
|
+
if not isinstance(pattern, str):
|
|
1028
|
+
ignore_case = is_ignore_case_regex(pattern)
|
|
1029
|
+
pattern = unverbosify_regex(pattern)
|
|
1030
|
+
analyze_re_expression(pattern, allow_empty_groups=True)
|
|
1031
|
+
if self.stack_last_ret != VariableType.LIST_STRING:
|
|
1032
|
+
LOGGER.warning(
|
|
1033
|
+
"all_is_re(%s): Expected type(s) %s got %s",
|
|
1034
|
+
repr(pattern),
|
|
1035
|
+
VariableType.LIST_STRING.name,
|
|
1036
|
+
self.stack_last_ret.name,
|
|
1037
|
+
)
|
|
1038
|
+
|
|
1039
|
+
self._add(
|
|
1040
|
+
ExprListStringAllRegex(
|
|
1041
|
+
kwargs={
|
|
1042
|
+
"pattern": pattern,
|
|
1043
|
+
"ignore_case": ignore_case,
|
|
1044
|
+
"msg": msg,
|
|
1045
|
+
}
|
|
1046
|
+
)
|
|
1047
|
+
)
|
|
1048
|
+
return self
|
|
1049
|
+
|
|
1050
|
+
def is_re(
|
|
1051
|
+
self, pattern: str | Pattern, msg: str = "", ignore_case: bool = False
|
|
1052
|
+
) -> Self:
|
|
1053
|
+
"""shortcut of is_regex() method"""
|
|
1054
|
+
return self.is_regex(pattern, msg, ignore_case)
|
|
1055
|
+
|
|
956
1056
|
def is_regex(
|
|
957
1057
|
self, pattern: str | Pattern, msg: str = "", ignore_case: bool = False
|
|
958
1058
|
) -> Self:
|
|
@@ -976,7 +1076,7 @@ class AssertDocument(BaseDocument):
|
|
|
976
1076
|
)
|
|
977
1077
|
|
|
978
1078
|
self._add(
|
|
979
|
-
|
|
1079
|
+
ExprStringIsRegex(
|
|
980
1080
|
kwargs={
|
|
981
1081
|
"pattern": pattern,
|
|
982
1082
|
"ignore_case": ignore_case,
|
|
@@ -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 ""
|
|
@@ -62,7 +62,9 @@ FMT_MAPPING_METHODS = {
|
|
|
62
62
|
TokenType.IS_CONTAINS: "is_contains",
|
|
63
63
|
TokenType.IS_CSS: "is_css",
|
|
64
64
|
TokenType.IS_XPATH: "is_xpath",
|
|
65
|
-
TokenType.
|
|
65
|
+
TokenType.IS_STRING_REGEX_MATCH: "is_regex",
|
|
66
|
+
TokenType.ANY_LIST_STRING_REGEX_MATCH: "any_is_re",
|
|
67
|
+
TokenType.ALL_LIST_STRING_REGEX_MATCH: "all_is_re",
|
|
66
68
|
TokenType.TO_INT: "to_int",
|
|
67
69
|
TokenType.TO_INT_LIST: "to_int",
|
|
68
70
|
TokenType.TO_FLOAT: "to_float",
|
|
@@ -329,7 +329,7 @@ def analyze_regex_expr(
|
|
|
329
329
|
case (
|
|
330
330
|
TokenType.EXPR_REGEX_SUB
|
|
331
331
|
| TokenType.EXPR_LIST_REGEX_SUB
|
|
332
|
-
| TokenType.
|
|
332
|
+
| TokenType.IS_STRING_REGEX_MATCH
|
|
333
333
|
):
|
|
334
334
|
result = analyze_re_expression(
|
|
335
335
|
re_expr.kwargs["pattern"], allow_empty_groups=True
|
|
@@ -153,7 +153,9 @@ class TokenType(IntEnum):
|
|
|
153
153
|
IS_CONTAINS = auto()
|
|
154
154
|
IS_CSS = auto()
|
|
155
155
|
IS_XPATH = auto()
|
|
156
|
-
|
|
156
|
+
IS_STRING_REGEX_MATCH = auto()
|
|
157
|
+
ANY_LIST_STRING_REGEX_MATCH = auto()
|
|
158
|
+
ALL_LIST_STRING_REGEX_MATCH = auto()
|
|
157
159
|
|
|
158
160
|
# NUMERIC
|
|
159
161
|
TO_INT = auto()
|
|
@@ -186,7 +188,7 @@ class TokenType(IntEnum):
|
|
|
186
188
|
TokenType.IS_EQUAL,
|
|
187
189
|
TokenType.IS_NOT_EQUAL,
|
|
188
190
|
TokenType.IS_CONTAINS,
|
|
189
|
-
TokenType.
|
|
191
|
+
TokenType.IS_STRING_REGEX_MATCH,
|
|
190
192
|
)
|
|
191
193
|
|
|
192
194
|
@classmethod
|
|
@@ -196,5 +198,5 @@ class TokenType(IntEnum):
|
|
|
196
198
|
TokenType.EXPR_REGEX_ALL,
|
|
197
199
|
TokenType.EXPR_REGEX_SUB,
|
|
198
200
|
TokenType.EXPR_LIST_REGEX_SUB,
|
|
199
|
-
TokenType.
|
|
201
|
+
TokenType.IS_STRING_REGEX_MATCH,
|
|
200
202
|
)
|
|
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
|