1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010-2021 Winch Gate Property Limited
4 // This source file has been modified by the following contributors:
5 // Copyright (C) 2020 Jan BOON (Kaetemi) <jan.boon@kaetemi.be>
7 // This program is free software: you can redistribute it and/or modify
8 // it under the terms of the GNU Affero General Public License as
9 // published by the Free Software Foundation, either version 3 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful,
13 // but WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 // GNU Affero General Public License for more details.
17 // You should have received a copy of the GNU Affero General Public License
18 // along with this program. If not, see <http://www.gnu.org/licenses/>.
23 #include "nel/misc/types_nl.h"
24 #include "nel/gui/css_selector.h"
25 #include "nel/gui/html_element.h"
27 using namespace NLMISC
;
35 CCssSelector::CCssSelector(std::string elm
, std::string id
, std::string cls
, char comb
)
36 : Element(elm
), Id(id
), Class(), Attr(), PseudoClass(), Combinator(comb
)
44 uint32
CCssSelector::specificity() const
48 if (!Element
.empty() && Element
!= "*") ret
+= 0x000001;
49 // Pseudo Element is added in CCssStyle
50 //if (!PseudoElement.empty()) ret += 0x000001;
52 if (!Class
.empty()) ret
+= 0x000100 * Class
.size();
53 if (!Attr
.empty()) ret
+= 0x000100 * Attr
.size();
54 // TODO: has different cases
55 if (!PseudoClass
.empty()) ret
+= 0x000100 * PseudoClass
.size();
57 if (!Id
.empty()) ret
+= 0x010000;
62 void CCssSelector::setClass(const std::string
&cls
)
64 std::vector
<std::string
> parts
;
65 NLMISC::splitString(toLowerAscii(cls
), ".", parts
);
67 for(uint i
= 0; i
< parts
.size(); i
++)
69 std::string cname
= trim(parts
[i
]);
72 Class
.push_back(cname
);
77 void CCssSelector::addAttribute(const std::string
&key
, const std::string
&val
, char op
, bool cs
)
81 // case sensitive match
82 Attr
.push_back(SAttribute(key
, val
, op
, cs
));
86 Attr
.push_back(SAttribute(key
, toLowerAscii(val
), op
, cs
));
90 void CCssSelector::addPseudoClass(const std::string
&key
)
92 if (key
.empty()) return;
94 PseudoClass
.push_back(key
);
97 bool CCssSelector::match(const CHtmlElement
&elm
) const
99 if (!Element
.empty() && Element
!= "*" && Element
!= elm
.Value
)
104 if (!Id
.empty() && Id
!= elm
.getAttribute("id"))
109 if (!Class
.empty() && !matchClass(elm
))
114 if (!Attr
.empty() && !matchAttributes(elm
))
119 if (!PseudoClass
.empty() && !matchPseudoClass(elm
))
127 bool CCssSelector::matchClass(const CHtmlElement
&elm
) const
129 // make sure all class names we have, other has as well
130 for(uint i
= 0; i
< Class
.size(); ++i
)
132 if (!elm
.hasClass(Class
[i
]))
141 bool CCssSelector::matchAttributes(const CHtmlElement
&elm
) const
143 // TODO: refactor into matchAttributeSelector
144 for(uint i
= 0; i
< Attr
.size(); ++i
)
146 if (!elm
.hasAttribute(Attr
[i
].key
)) return false;
148 std::string value
= elm
.getAttribute(Attr
[i
].key
);
149 // case-insensitive compare, Attr.value is already lowercased
150 if (!Attr
[i
].caseSensitive
)
152 value
= toLowerAscii(value
);
158 if (Attr
[i
].value
!= value
) return false;
162 // exact match to any of whitespace separated words from element attribute
163 if (Attr
[i
].value
.empty()) return false;
165 std::vector
<std::string
> parts
;
166 NLMISC::splitString(value
, " ", parts
);
168 for(uint j
= 0; j
< parts
.size(); j
++)
170 if (Attr
[i
].value
== parts
[j
])
176 if (!found
) return false;
180 // exact value, or start with val+'-'
181 if (value
!= Attr
[i
].value
&& value
.find(Attr
[i
].value
+ "-") == std::string::npos
) return false;
184 // prefix, starts with
185 if (Attr
[i
].value
.empty()) return false;
186 if (value
.find(Attr
[i
].value
) != 0) return false;
190 if (Attr
[i
].value
.empty() || value
.size() < Attr
[i
].value
.size()) return false;
191 if (Attr
[i
].value
== value
.substr(value
.size() - Attr
[i
].value
.size())) return false;
194 if (Attr
[i
].value
.empty()) return false;
195 if (value
.find(Attr
[i
].value
) == std::string::npos
) return false;
198 // contains key, ignore value
201 // unknown comparison
209 bool CCssSelector::matchPseudoClass(const CHtmlElement
&elm
) const
211 for(uint i
= 0; i
< PseudoClass
.size(); i
++)
213 if (PseudoClass
[i
] == "root")
215 // ':root' is just 'html' with higher specificity
216 if (elm
.Value
!= "html") return false;
218 else if (PseudoClass
[i
] == "only-child")
220 if (elm
.parent
&& !elm
.parent
->Children
.empty()) return false;
224 if (PseudoClass
[i
] == "first-child")
226 if (elm
.previousSibling
) return false;
228 else if (PseudoClass
[i
] == "last-child")
230 if (elm
.nextSibling
) return false;
232 else if (PseudoClass
[i
].find("nth-child(") != std::string::npos
)
235 // TODO: there might be multiple :nth-child() on single selector, so current can't cache it
236 parseNth(PseudoClass
[i
], a
, b
);
238 // 1st child should be '1' and not '0'
239 if (!matchNth(elm
.childIndex
+1, a
, b
)) return false;
241 else if (startsWith(PseudoClass
[i
], "lang("))
243 std::string lang
= PseudoClass
[i
].substr(5, PseudoClass
[i
].size() - 6);
244 if (lang
.empty() || !matchLang(elm
, lang
)) return false;
256 void CCssSelector::parseNth(const std::string
&pseudo
, sint
&a
, sint
&b
) const
261 std::string::size_type start
= pseudo
.find_first_of("(") + 1;
262 std::string::size_type end
= pseudo
.find_first_of(")");
264 if (start
== std::string::npos
) return;
266 std::string expr
= toLowerAscii(pseudo
.substr(start
, end
- start
));
268 if (expr
.empty()) return;
276 else if (expr
== "odd")
285 std::string::size_type pos
;
288 pos
= expr
.find_first_of("n", start
);
289 if (pos
== std::string::npos
)
298 else if (expr
[0] == '-' && pos
== 1)
305 fromString(expr
.substr(start
, pos
- start
), a
);
309 pos
= expr
.find_first_of("+-", start
);
310 if (pos
!= std::string::npos
&& (expr
[pos
] == '+' || expr
[pos
] == '-'))
312 // copy with sign char
313 fromString(expr
.substr(pos
, end
- pos
), b
);
318 bool CCssSelector::matchNth(sint childNr
, sint a
, sint b
) const
326 return childNr
>= b
&& (childNr
- b
) % a
== 0;
330 // a is negative from '-An+B'
331 return childNr
<= b
&& (b
- childNr
) % (-a
) == 0;
335 bool CCssSelector::matchLang(const CHtmlElement
&elm
, const std::string
&pseudo
) const
337 // TODO: does not support comma separated, or escaped/quoted/wildcard tags
338 std::string lang
= toLowerAscii(elm
.getInheritedLanguage());
339 if (lang
.empty() || pseudo
.empty())
342 // lang = 'en', pseudo = 'en-US'
343 if (lang
.size() < pseudo
.size())
346 std::string selector
= toLowerAscii(pseudo
);
347 bool selectorHasRegion
= selector
.find("-") != std::string::npos
;
348 bool langHasRegion
= lang
.find("-") != std::string::npos
;
350 // both are 'en', or 'en-US' type
351 if (langHasRegion
== selectorHasRegion
)
352 return lang
== selector
;
354 // lang = 'en-US', selector = 'en'
355 return lang
[selector
.size()] == '-' && startsWith(lang
, selector
);
358 std::string
CCssSelector::toString() const
365 for(uint i
= 0; i
<Class
.size(); i
++)
366 ret
+= "." + Class
[i
];
370 for(uint i
= 0; i
<Attr
.size(); ++i
)
372 ret
+= "[" + Attr
[i
].key
;
373 if (Attr
[i
].op
!= ' ')
375 ret
+= Attr
[i
].op
+ Attr
[i
].value
;
380 if (!PseudoClass
.empty())
382 for(uint i
= 0; i
<PseudoClass
.size(); ++i
)
384 ret
+= ":" + PseudoClass
[i
];
387 if (Combinator
!= '\0')
392 // ret += ":" + PseudoClass;