1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use peg::parser;
use crate::{
Command, CssAttributeComparison, CssAttributeSelector, CssPseudoClass, CssSelector,
CssSelectorList, CssSelectorPath, CssSelectorStep, Pipeline, ValueSource,
};
#[cfg(test)]
mod tests;
fn build_css_path<'a>(
first: CssSelectorStep<'a>,
rest: Option<Vec<CssSelectorStep<'a>>>,
) -> Vec<CssSelectorStep<'a>> {
let mut result = vec![first];
if let Some(mut rest_content) = rest {
result.append(&mut rest_content);
}
return result;
}
parser! {
pub grammar grammar() for str {
rule whitespace()
= quiet!{[' ' | '\n' | '\t']+}
rule assign_marker()
= "↤"
/ "<="
rule iterate_marker()
= "↦"
/ "=>"
rule number() -> usize
= n:$(['0'..='9']+) { n.parse().unwrap() }
pub(crate) rule identifier() -> &'input str
= i:$(['a'..='z' | 'A'..='Z' | '0'..='9' | '-' | '_' ]+) { i }
rule css_attribute() -> CssAttributeSelector<'input>
= "[" a:(identifier()) "]" { CssAttributeSelector::<'input> { attribute: a, operator: CssAttributeComparison::Exist, value: None } }
/ "[" a:(identifier()) "=" v:(string_value()) "]" { CssAttributeSelector::<'input> { attribute: a, operator: CssAttributeComparison::EqualsExact, value: Some(v) } }
/ "[" a:(identifier()) "|=" v:(string_value()) "]" { CssAttributeSelector::<'input> { attribute: a, operator: CssAttributeComparison::EqualsTillHyphen, value: Some(v) } }
/ "[" a:(identifier()) "^=" v:(string_value()) "]" { CssAttributeSelector::<'input> { attribute: a, operator: CssAttributeComparison::Starts, value: Some(v) } }
/ "[" a:(identifier()) "$=" v:(string_value()) "]" { CssAttributeSelector::<'input> { attribute: a, operator: CssAttributeComparison::Ends, value: Some(v) } }
/ "[" a:(identifier()) "*=" v:(string_value()) "]" { CssAttributeSelector::<'input> { attribute: a, operator: CssAttributeComparison::CharacterContains, value: Some(v) } }
/ "[" a:(identifier()) "~=" v:(string_value()) "]" { CssAttributeSelector::<'input> { attribute: a, operator: CssAttributeComparison::TermContains, value: Some(v) } }
rule css_attributes() -> Vec<CssAttributeSelector<'input>>
= a:(css_attribute() ++ "") { a }
rule css_class() -> &'input str
= "." c:(identifier()) { c }
rule css_classes() -> Vec<&'input str>
= c:(css_class() ++ "") { c }
rule css_pseudo_class() -> CssPseudoClass
= ":first-child" { CssPseudoClass::FirstChild }
/ ":first-of-type" { CssPseudoClass::FirstOfType }
/ ":nth-child(" i:(number()) ")" { CssPseudoClass::NthChild(i) }
/ ":nth-of-type(" i:(number()) ")" { CssPseudoClass::NthOfType(i) }
/ ":last-child" { CssPseudoClass::LastChild }
/ ":last-of-type" { CssPseudoClass::LastOfType }
/ ":nth-last-child(" i:(number()) ")" { CssPseudoClass::NthLastChild(i) }
/ ":nth-last-of-type(" i:(number()) ")" { CssPseudoClass::NthLastOfType(i) }
rule css_pseudo_classes() -> Vec<CssPseudoClass>
= p:(css_pseudo_class() ++ "") { p }
rule css_id() -> &'input str
= "#" i:(identifier()) { i }
pub(crate) rule css_selector() -> CssSelector<'input>
= e:(identifier())i:(css_id())?c:(css_classes()?)p:(css_pseudo_classes())?a:(css_attributes()?) { CssSelector{element:Some(e), id: i, classes: c.unwrap_or(vec![]), pseudo_classes: p.unwrap_or(vec![]), attributes: a.unwrap_or(vec![])} }
/ i:(css_id())c:(css_classes())?p:(css_pseudo_classes())?a:(css_attributes()?) { CssSelector{element:None, id: Some(i), classes: c.unwrap_or(vec![]), pseudo_classes: p.unwrap_or(vec![]), attributes: a.unwrap_or(vec![])} }
/ c:(css_classes())p:(css_pseudo_classes())?a:(css_attributes()?) { CssSelector{element:None, id: None, classes: c, pseudo_classes: p.unwrap_or(vec![]), attributes: a.unwrap_or(vec![])} }
/ p:(css_pseudo_classes())a:(css_attributes())? { CssSelector{element:None, id: None, classes: vec![], pseudo_classes: p, attributes: a.unwrap_or(vec![])} }
/ a:(css_attributes()) { CssSelector{element:None, id: None, classes: vec![], pseudo_classes: vec![], attributes: a} }
rule css_selector_step() -> Vec<CssSelectorStep<'input>>
= " "? ">" " "? s:(css_selector()) l:(css_selector_step())? { build_css_path(CssSelectorStep::direct_child(s), l) }
/ " "? "~" " "? s:(css_selector()) l:(css_selector_step())? { build_css_path(CssSelectorStep::general_sibling(s), l) }
/ " "? "+" " "? s:(css_selector()) l:(css_selector_step())? { build_css_path(CssSelectorStep::adjacent_sibling(s), l) }
/ " " s:(css_selector()) l:(css_selector_step())? { build_css_path(CssSelectorStep::descendent(s), l) }
pub(crate) rule css_selector_path() -> CssSelectorPath<'input>
= whitespace()? f:(css_selector()) l:(css_selector_step())? whitespace()?{ CssSelectorPath::new(f, l.unwrap_or(vec![])) }
pub(crate) rule css_selector_list() -> CssSelectorList<'input>
= v:(css_selector_path() ++ ",") { CssSelectorList::new(v) }
pub(crate) rule string_value() -> &'input str
= "\"" whitespace()? s:$([^'"']+) "\"" { s }
/ "'" whitespace()? s:$([^'\'']+) "'" { s }
/ "?" whitespace()? s:$([^'?']+) "?" { s }
rule only_command() -> Command<'input>
= ("ONLY" / "SELECT") "{" whitespace()? oc:css_selector_list() whitespace()? "}" { Command::Only(oc) }
rule without_command() -> Command<'input>
= ("WITHOUT" / "FILTER") "{" whitespace()? oc:css_selector_list() whitespace()? "}" { Command::Without(oc) }
rule for_each_command() -> Command<'input>
= "FOR" "-EACH"? "{" whitespace()? oc:css_selector_list() whitespace()? iterate_marker() whitespace()? sp:pipeline() whitespace()? "}" { Command::ForEach(oc, sp) }
rule replace_command() -> Command<'input>
= ("REPLACE"/"MAP") "{" whitespace()? oc:css_selector_list() whitespace()? assign_marker() whitespace()? sp:element_creating_pipeline() whitespace()? "}" { Command::Replace(oc, sp)}
rule clear_attr_command() -> Command<'input>
= "CLEAR-ATTR{" whitespace()? a:identifier() whitespace()? "}" { Command::ClearAttribute(String::from(a)) }
rule clear_content_command() -> Command<'input>
= "CLEAR-CONTENT" { Command::ClearContent }
rule set_attr_command() -> Command<'input>
= "SET-ATTR{" whitespace()? a:identifier() whitespace()? assign_marker() whitespace()? v:string_value() "}" { Command::SetAttribute(String::from(a), ValueSource::StringValue(String::from(v))) }
rule set_text_content_command() -> Command<'input>
= "SET-TEXT-CONTENT{" whitespace()? (assign_marker() whitespace()?)? v:string_value() "}" { Command::SetTextContent(ValueSource::StringValue(String::from(v))) }
rule add_text_content_command() -> Command<'input>
= "ADD-TEXT-CONTENT{" whitespace()? (assign_marker() whitespace()?)? v:string_value() "}" { Command::AddTextContent(ValueSource::StringValue(String::from(v))) }
rule add_comment_command() -> Command<'input>
= "ADD-COMMENT{" whitespace()? (assign_marker() whitespace()?)? v:string_value() "}" { Command::AddComment(ValueSource::StringValue(String::from(v))) }
rule add_element_command() -> Command<'input>
= "ADD-ELEMENT{" whitespace()? (assign_marker() whitespace()?)? sp:element_creating_pipeline() whitespace()? "}" { Command::AddElement(sp) }
rule create_element_command() -> Command<'input>
= ("CREATE-ELEMENT"/"NEW") "{" whitespace()? n:identifier() whitespace()? "}" { Command::CreateElement(String::from(n))}
rule read_from_command() -> Command<'input>
= ("READ-FROM"/"SOURCE") "{" whitespace()? f:string_value() whitespace()? "}" { Command::ReadFrom(String::from(f)) }
pub(super) rule command() -> Command<'input>
= only_command()
/ without_command()
/ for_each_command()
/ clear_attr_command()
/ clear_content_command()
/ set_attr_command()
/ set_text_content_command()
/ add_text_content_command()
/ add_comment_command()
/ add_element_command()
/ replace_command()
rule element_source_command() -> Command<'input>
= create_element_command()
/ read_from_command()
rule element_manipulating_pipeline() -> Vec<Command<'input>>
= " | " p:(command() ** " | ") { p }
rule element_creating_pipeline() -> Pipeline<'input>
= s:element_source_command() p:element_manipulating_pipeline()? { Pipeline::new(s + p) }
pub rule pipeline() -> Pipeline<'input>
= p:(command() ** " | ") { Pipeline::new(p) }
}
}