1 | #include "html.h" |
2 | #include "css_selector.h" |
3 | #include "document.h" |
4 | |
5 | void litehtml::css_element_selector::parse( const tstring& txt ) |
6 | { |
7 | tstring::size_type el_end = txt.find_first_of(_t(".#[:" )); |
8 | m_tag = txt.substr(pos: 0, n: el_end); |
9 | litehtml::lcase(s&: m_tag); |
10 | m_attrs.clear(); |
11 | while(el_end != tstring::npos) |
12 | { |
13 | if(txt[el_end] == _t('.')) |
14 | { |
15 | css_attribute_selector attribute; |
16 | |
17 | tstring::size_type pos = txt.find_first_of(_t(".#[:" ), pos: el_end + 1); |
18 | attribute.val = txt.substr(pos: el_end + 1, n: pos - el_end - 1); |
19 | split_string( str: attribute.val, tokens&: attribute.class_val, _t(" " ) ); |
20 | attribute.condition = select_equal; |
21 | attribute.attribute = _t("class" ); |
22 | m_attrs.push_back(x: attribute); |
23 | el_end = pos; |
24 | } else if(txt[el_end] == _t(':')) |
25 | { |
26 | css_attribute_selector attribute; |
27 | |
28 | if(txt[el_end + 1] == _t(':')) |
29 | { |
30 | tstring::size_type pos = txt.find_first_of(_t(".#[:" ), pos: el_end + 2); |
31 | attribute.val = txt.substr(pos: el_end + 2, n: pos - el_end - 2); |
32 | attribute.condition = select_pseudo_element; |
33 | litehtml::lcase(s&: attribute.val); |
34 | attribute.attribute = _t("pseudo-el" ); |
35 | m_attrs.push_back(x: attribute); |
36 | el_end = pos; |
37 | } else |
38 | { |
39 | tstring::size_type pos = txt.find_first_of(_t(".#[:(" ), pos: el_end + 1); |
40 | if(pos != tstring::npos && txt.at(n: pos) == _t('(')) |
41 | { |
42 | pos = find_close_bracket(s: txt, off: pos); |
43 | if(pos != tstring::npos) |
44 | { |
45 | pos++; |
46 | } |
47 | } |
48 | if(pos != tstring::npos) |
49 | { |
50 | attribute.val = txt.substr(pos: el_end + 1, n: pos - el_end - 1); |
51 | } else |
52 | { |
53 | attribute.val = txt.substr(pos: el_end + 1); |
54 | } |
55 | litehtml::lcase(s&: attribute.val); |
56 | if(attribute.val == _t("after" ) || attribute.val == _t("before" )) |
57 | { |
58 | attribute.condition = select_pseudo_element; |
59 | } else |
60 | { |
61 | attribute.condition = select_pseudo_class; |
62 | } |
63 | attribute.attribute = _t("pseudo" ); |
64 | m_attrs.push_back(x: attribute); |
65 | el_end = pos; |
66 | } |
67 | } else if(txt[el_end] == _t('#')) |
68 | { |
69 | css_attribute_selector attribute; |
70 | |
71 | tstring::size_type pos = txt.find_first_of(_t(".#[:" ), pos: el_end + 1); |
72 | attribute.val = txt.substr(pos: el_end + 1, n: pos - el_end - 1); |
73 | attribute.condition = select_equal; |
74 | attribute.attribute = _t("id" ); |
75 | m_attrs.push_back(x: attribute); |
76 | el_end = pos; |
77 | } else if(txt[el_end] == _t('[')) |
78 | { |
79 | css_attribute_selector attribute; |
80 | |
81 | tstring::size_type pos = txt.find_first_of(_t("]~=|$*^" ), pos: el_end + 1); |
82 | tstring attr = txt.substr(pos: el_end + 1, n: pos - el_end - 1); |
83 | trim(s&: attr); |
84 | litehtml::lcase(s&: attr); |
85 | if(pos != tstring::npos) |
86 | { |
87 | if(txt[pos] == _t(']')) |
88 | { |
89 | attribute.condition = select_exists; |
90 | } else if(txt[pos] == _t('=')) |
91 | { |
92 | attribute.condition = select_equal; |
93 | pos++; |
94 | } else if(txt.substr(pos: pos, n: 2) == _t("~=" )) |
95 | { |
96 | attribute.condition = select_contain_str; |
97 | pos += 2; |
98 | } else if(txt.substr(pos: pos, n: 2) == _t("|=" )) |
99 | { |
100 | attribute.condition = select_start_str; |
101 | pos += 2; |
102 | } else if(txt.substr(pos: pos, n: 2) == _t("^=" )) |
103 | { |
104 | attribute.condition = select_start_str; |
105 | pos += 2; |
106 | } else if(txt.substr(pos: pos, n: 2) == _t("$=" )) |
107 | { |
108 | attribute.condition = select_end_str; |
109 | pos += 2; |
110 | } else if(txt.substr(pos: pos, n: 2) == _t("*=" )) |
111 | { |
112 | attribute.condition = select_contain_str; |
113 | pos += 2; |
114 | } else |
115 | { |
116 | attribute.condition = select_exists; |
117 | pos += 1; |
118 | } |
119 | pos = txt.find_first_not_of(_t(" \t" ), pos: pos); |
120 | if(pos != tstring::npos) |
121 | { |
122 | if(txt[pos] == _t('"')) |
123 | { |
124 | tstring::size_type pos2 = txt.find_first_of(_t('\"'), pos: pos + 1); |
125 | attribute.val = txt.substr(pos: pos + 1, n: pos2 == tstring::npos ? pos2 : (pos2 - pos - 1)); |
126 | pos = pos2 == tstring::npos ? pos2 : (pos2 + 1); |
127 | } else if(txt[pos] == _t(']')) |
128 | { |
129 | pos ++; |
130 | } else |
131 | { |
132 | tstring::size_type pos2 = txt.find_first_of(_t(']'), pos: pos + 1); |
133 | attribute.val = txt.substr(pos: pos, n: pos2 == tstring::npos ? pos2 : (pos2 - pos)); |
134 | trim(s&: attribute.val); |
135 | pos = pos2 == tstring::npos ? pos2 : (pos2 + 1); |
136 | } |
137 | } |
138 | } else |
139 | { |
140 | attribute.condition = select_exists; |
141 | } |
142 | attribute.attribute = attr; |
143 | m_attrs.push_back(x: attribute); |
144 | el_end = pos; |
145 | } else |
146 | { |
147 | el_end++; |
148 | } |
149 | el_end = txt.find_first_of(_t(".#[:" ), pos: el_end); |
150 | } |
151 | } |
152 | |
153 | |
154 | bool litehtml::css_selector::parse( const tstring& text ) |
155 | { |
156 | if(text.empty()) |
157 | { |
158 | return false; |
159 | } |
160 | string_vector tokens; |
161 | split_string(str: text, tokens, _t("" ), _t(" \t>+~" ), _t("([" )); |
162 | |
163 | if(tokens.empty()) |
164 | { |
165 | return false; |
166 | } |
167 | |
168 | tstring left; |
169 | tstring right = tokens.back(); |
170 | tchar_t combinator = 0; |
171 | |
172 | tokens.pop_back(); |
173 | while(!tokens.empty() && (tokens.back() == _t(" " ) || tokens.back() == _t("\t" ) || tokens.back() == _t("+" ) || tokens.back() == _t("~" ) || tokens.back() == _t(">" ))) |
174 | { |
175 | if(combinator == _t(' ') || combinator == 0) |
176 | { |
177 | combinator = tokens.back()[0]; |
178 | } |
179 | tokens.pop_back(); |
180 | } |
181 | |
182 | for(const auto & token : tokens) |
183 | { |
184 | left += token; |
185 | } |
186 | |
187 | trim(s&: left); |
188 | trim(s&: right); |
189 | |
190 | if(right.empty()) |
191 | { |
192 | return false; |
193 | } |
194 | |
195 | m_right.parse(txt: right); |
196 | |
197 | switch(combinator) |
198 | { |
199 | case _t('>'): |
200 | m_combinator = combinator_child; |
201 | break; |
202 | case _t('+'): |
203 | m_combinator = combinator_adjacent_sibling; |
204 | break; |
205 | case _t('~'): |
206 | m_combinator = combinator_general_sibling; |
207 | break; |
208 | default: |
209 | m_combinator = combinator_descendant; |
210 | break; |
211 | } |
212 | |
213 | m_left = nullptr; |
214 | |
215 | if(!left.empty()) |
216 | { |
217 | m_left = std::make_shared<css_selector>(args: media_query_list::ptr(nullptr), _t("" )); |
218 | if(!m_left->parse(text: left)) |
219 | { |
220 | return false; |
221 | } |
222 | } |
223 | |
224 | return true; |
225 | } |
226 | |
227 | void litehtml::css_selector::calc_specificity() |
228 | { |
229 | if(!m_right.m_tag.empty() && m_right.m_tag != _t("*" )) |
230 | { |
231 | m_specificity.d = 1; |
232 | } |
233 | for(const auto& attr : m_right.m_attrs) |
234 | { |
235 | if(attr.attribute == _t("id" )) |
236 | { |
237 | m_specificity.b++; |
238 | } else |
239 | { |
240 | if(attr.attribute == _t("class" )) |
241 | { |
242 | m_specificity.c += (int) attr.class_val.size(); |
243 | } else |
244 | { |
245 | m_specificity.c++; |
246 | } |
247 | } |
248 | } |
249 | if(m_left) |
250 | { |
251 | m_left->calc_specificity(); |
252 | m_specificity += m_left->m_specificity; |
253 | } |
254 | } |
255 | |
256 | void litehtml::css_selector::add_media_to_doc( document* doc ) const |
257 | { |
258 | if(m_media_query && doc) |
259 | { |
260 | doc->add_media_list(list: m_media_query); |
261 | } |
262 | } |
263 | |
264 | |