39 return c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r';
42 std::string
trim(
const std::string &s) {
44 while (start < s.size() &&
isWhitespace(s[start])) ++start;
45 size_t end = s.size();
47 return s.substr(start, end - start);
56 size_t bang = value.rfind(
'!');
57 if (bang == std::string::npos)
return false;
59 size_t start = value.find_first_not_of(
" \t\n\r", bang + 1);
60 size_t end = value.find_last_not_of(
" \t\n\r");
61 if (start == std::string::npos || end == std::string::npos || start > end)
return false;
63 std::string tail = value.substr(start, end - start + 1);
64 std::transform(tail.begin(), tail.end(), tail.begin(),
65 [](
unsigned char c) { return std::tolower(c); });
66 if (tail !=
"important")
return false;
69 while (valEnd > 0 &&
isWhitespace(value[valEnd - 1])) --valEnd;
101 std::vector<libhtmlpp::AttributeCondition> out;
103 while (pos < sel.size()) {
104 if (sel[pos] !=
'[') { ++pos;
continue; }
105 size_t close = sel.find(
']', pos + 1);
106 if (close == std::string::npos)
break;
107 std::string inner =
trim(sel.substr(pos + 1, close - pos - 1));
109 if (inner.empty())
continue;
112 size_t eq = inner.find(
'=');
113 if (eq == std::string::npos) {
117 if (eq > 0 && std::string(
"~|^$*").find(inner[eq - 1]) != std::string::npos)
119 cond.
op = inner.substr(opStart, eq - opStart + 1);
120 cond.
name =
trim(inner.substr(0, opStart));
121 std::string value =
trim(inner.substr(eq + 1));
122 if (value.size() >= 2 &&
123 ((value.front() ==
'"' && value.back() ==
'"') ||
124 (value.front() ==
'\'' && value.back() ==
'\'')))
125 value = value.substr(1, value.size() - 2);
128 out.push_back(std::move(cond));
139 if (op.empty())
return true;
140 if (op ==
"=")
return actual == expected;
142 std::istringstream iss(actual);
144 while (iss >> tok)
if (tok == expected)
return true;
147 if (op ==
"^=")
return !expected.empty() && actual.compare(0, expected.size(), expected) == 0;
148 if (op ==
"$=")
return !expected.empty() && expected.size() <= actual.size() &&
149 actual.compare(actual.size() - expected.size(), expected.size(), expected) == 0;
150 if (op ==
"*=")
return !expected.empty() && actual.find(expected) != std::string::npos;
151 if (op ==
"|=")
return actual == expected || actual.compare(0, expected.size() + 1, expected +
"-") == 0;
162 const std::map<std::string,std::string> *targetAttrs)
164 if (conditions.empty())
return true;
165 if (!targetAttrs)
return true;
166 for (
const auto &cond : conditions) {
167 auto it = targetAttrs->find(cond.name);
168 if (it == targetAttrs->end())
return false;
177 while (pos < matchSel.size()) {
178 char c = matchSel[pos];
179 if (c ==
'.' || c ==
'#') {
180 size_t next = matchSel.find_first_of(
".#", pos + 1);
181 size_t tokenEnd = (next == std::string::npos) ? matchSel.size() : next;
182 std::string token = matchSel.substr(pos + 1, tokenEnd - (pos + 1));
183 if (c ==
'.') result.
classes.push_back(token);
184 else result.
id = token;
187 size_t next = matchSel.find_first_of(
".#", pos);
188 size_t tokenEnd = (next == std::string::npos) ? matchSel.size() : next;
189 result.
tag = matchSel.substr(pos, tokenEnd - pos);
193 std::transform(result.
tag.begin(), result.
tag.end(), result.
tag.begin(),
194 [](
unsigned char c) { return std::tolower(c); });
214 const std::vector<std::string> &classes,
const std::string &
id)
216 bool tagOk = compound.
tag.empty() || compound.
tag == tag;
217 bool idOk = compound.
id.empty() || compound.
id == id;
218 bool classesOk =
true;
219 for (
const auto &cls : compound.
classes) {
220 if (std::find(classes.begin(), classes.end(), cls) == classes.end()) {
225 return tagOk && idOk && classesOk;
249 const std::string &tag,
const std::vector<std::string> &classes,
250 const std::string &
id,
251 const std::map<std::string,std::string> *targetAttrs =
nullptr)
253 bool bareTagAfterCombinator = hadCombinator &&
254 compound.
classes.empty() && compound.
id.empty() &&
263 bool specifiedSomething = !compound.
tag.empty() || !compound.
classes.empty() ||
264 !compound.
id.empty() ||
266 if (bareTagAfterCombinator || !specifiedSomething)
return false;
282 std::vector<std::string> parts;
284 for (
char c : selector) {
285 if (c ==
' ' || c ==
'\t' || c ==
'\n' || c ==
'\r' ||
286 c ==
'>' || c ==
'+' || c ==
'~') {
287 if (!cur.empty()) { parts.push_back(cur); cur.clear(); }
292 if (!cur.empty()) parts.push_back(cur);
326 const std::vector<libhtmlpp::AncestorFrame> &ancestors,
327 bool *usedUnverifiableAncestor =
nullptr)
330 for (
const auto &compound : ancestorCompounds) {
331 bool specifiedSomething = !compound.tag.empty() || !compound.classes.empty() ||
332 !compound.id.empty() || !compound.attrConditions.empty();
333 if (!specifiedSomething) {
334 if (usedUnverifiableAncestor) *usedUnverifiableAncestor =
true;
339 while (idx < ancestors.size()) {
349 if (!found)
return false;
362 return matchSel.find(
':') != std::string::npos ||
385 if (matchSel.find(
'[') == std::string::npos)
return matchSel;
387 out.reserve(matchSel.size());
389 while (pos < matchSel.size()) {
390 if (matchSel[pos] ==
'[') {
391 size_t close = matchSel.find(
']', pos + 1);
392 if (close == std::string::npos) { out += matchSel.substr(pos);
break; }
395 out += matchSel[pos];
407 for (
size_t i = openPos; i < s.size(); ++i) {
408 if (s[i] ==
'(') ++depth;
409 else if (s[i] ==
')') {
411 if (depth == 0)
return i;
414 return std::string::npos;
440 std::array<int,3> spec{0, 0, 0};
442 auto skipIdent = [&](
size_t p) {
443 while (p < selector.size() &&
444 (std::isalnum(
static_cast<unsigned char>(selector[p])) ||
445 selector[p] ==
'-' || selector[p] ==
'_' || selector[p] ==
'\\')) {
450 while (pos < selector.size()) {
451 char c = selector[pos];
454 pos = skipIdent(pos + 1);
455 }
else if (c ==
'.') {
457 pos = skipIdent(pos + 1);
458 }
else if (c ==
'[') {
460 size_t close = selector.find(
']', pos + 1);
461 pos = (close == std::string::npos) ? selector.size() : close + 1;
462 }
else if (c ==
':') {
463 bool pseudoElement = pos + 1 < selector.size() && selector[pos + 1] ==
':';
464 ++spec[pseudoElement ? 2 : 1];
465 pos = skipIdent(pos + (pseudoElement ? 2 : 1));
466 if (pos < selector.size() && selector[pos] ==
'(') {
468 pos = (close == std::string::npos) ? selector.size() : close + 1;
470 }
else if (c ==
'*') {
472 }
else if (std::isalpha(
static_cast<unsigned char>(c)) || c ==
'_') {
473 size_t next = skipIdent(pos);
474 if (next > pos) ++spec[2];
487 void splitVarArgs(
const std::string &inner, std::string &name, std::string &fallback) {
489 size_t commaPos = std::string::npos;
490 for (
size_t i = 0; i < inner.size(); ++i) {
491 if (inner[i] ==
'(') ++depth;
492 else if (inner[i] ==
')') --depth;
493 else if (inner[i] ==
',' && depth == 0) { commaPos = i;
break; }
495 std::string rawName = (commaPos == std::string::npos) ? inner : inner.substr(0, commaPos);
496 std::string rawFallback = (commaPos == std::string::npos) ?
"" : inner.substr(commaPos + 1);
497 name =
trim(rawName);
498 fallback =
trim(rawFallback);
513 const std::map<std::string,std::string> &customProperties,
514 std::set<std::string> &resolving,
517 if (value.find(
"var(") == std::string::npos)
return value;
520 out.reserve(value.size());
522 while (pos < value.size()) {
523 if (value.compare(pos, 4,
"var(") == 0) {
524 size_t openParen = pos + 3;
526 if (closeParen == std::string::npos) {
529 out += value.substr(pos);
532 std::string inner = value.substr(openParen + 1, closeParen - openParen - 1);
533 std::string name, fallback;
536 std::string substitution;
537 if (resolving.count(name)) {
539 sawUnresolved =
true;
541 auto it = customProperties.find(name);
542 if (it != customProperties.end()) {
543 resolving.insert(name);
544 substitution =
substituteVars(it->second, customProperties, resolving, sawUnresolved);
545 resolving.erase(name);
546 }
else if (!fallback.empty()) {
547 substitution =
substituteVars(fallback, customProperties, resolving, sawUnresolved);
550 sawUnresolved =
true;
554 pos = closeParen + 1;
570 : _Name(name), _Value(value) {}
573 : _Name(prop._Name), _Value(prop._Value) {}
580 _Value = prop._Value;
596 : _Properties(decl._Properties) {}
602 _Properties = decl._Properties;
608 std::string tname = trim(name);
609 std::string tvalue = trim(value);
611 if (tname.empty())
return;
613 for (
auto &prop : _Properties) {
614 if (prop.getName() == tname) {
615 prop.setValue(tvalue);
619 _Properties.emplace_back(tname, tvalue);
623 std::string tname = trim(name);
625 std::remove_if(_Properties.begin(), _Properties.end(),
626 [&tname](
const CSSProperty &p) { return p.getName() == tname; }),
632 std::string tname = trim(name);
633 for (
const auto &prop : _Properties) {
634 if (prop.getName() == tname)
return ∝
645 for (
size_t i = 0; i < _Properties.size(); ++i) {
646 result += _Properties[i].getName();
648 result += _Properties[i].getValue();
650 if (i + 1 < _Properties.size()) result +=
" ";
659 size_t len = input.size();
663 while (pos < len && isWhitespace(input[pos])) ++pos;
664 if (pos >= len)
break;
667 size_t colon = input.find(
':', pos);
668 if (colon == std::string::npos)
break;
670 std::string propName = trim(input.substr(pos, colon - pos));
676 size_t valueStart = pos;
678 bool inSingleQuote =
false;
679 bool inDoubleQuote =
false;
685 if (c ==
'\'') inSingleQuote =
false;
690 if (c ==
'"') inDoubleQuote =
false;
695 if (c ==
'\'') { inSingleQuote =
true; ++pos;
continue; }
696 if (c ==
'"') { inDoubleQuote =
true; ++pos;
continue; }
697 if (c ==
'(') { ++parenDepth; ++pos;
continue; }
698 if (c ==
')') {
if (parenDepth > 0) --parenDepth; ++pos;
continue; }
700 if (c ==
';' && parenDepth == 0) {
706 std::string propValue = trim(input.substr(valueStart, pos - valueStart));
708 if (!propName.empty()) {
709 addProperty(propName, propValue);
712 if (pos < len && input[pos] ==
';') ++pos;
725 : _Selector(trim(selector)) {}
728 : _Selector(rule._Selector), _Declaration(rule._Declaration) {}
734 _Selector = rule._Selector;
735 _Declaration = rule._Declaration;
749 result += _Selector +
" {\n";
750 for (
const auto &prop : _Declaration.getProperties()) {
751 result +=
" " + prop.getName() +
": " + prop.getValue() +
";\n";
755 result += _Selector +
"{";
767 : _Rules(sheet._Rules) {}
772 if (
this != &sheet) {
773 _Rules = sheet._Rules;
774 _compoundCacheValid =
false;
779void libhtmlpp::CSSStyleSheet::_skipWhitespace(
const std::string &input,
size_t &pos)
const {
780 while (pos < input.size() && isWhitespace(input[pos])) ++pos;
783void libhtmlpp::CSSStyleSheet::_skipComment(
const std::string &input,
size_t &pos)
const {
784 if (pos + 1 < input.size() && input[pos] ==
'/' && input[pos + 1] ==
'*') {
786 while (pos + 1 < input.size()) {
787 if (input[pos] ==
'*' && input[pos + 1] ==
'/') {
799 _compoundCacheValid =
false;
802 size_t len = input.size();
805 _skipWhitespace(input, pos);
806 if (pos >= len)
break;
809 if (pos + 1 < len && input[pos] ==
'/' && input[pos + 1] ==
'*') {
810 _skipComment(input, pos);
815 if (input[pos] ==
'@') {
816 size_t atStart = pos;
820 size_t semiPos = input.find(
';', pos);
821 size_t bracePos = input.find(
'{', pos);
823 if (bracePos != std::string::npos && (semiPos == std::string::npos || bracePos < semiPos)) {
825 std::string atSelector = trim(input.substr(atStart, bracePos - atStart));
829 size_t blockStart = pos;
830 while (pos < len && depth > 0) {
831 if (input[pos] ==
'{') ++depth;
832 else if (input[pos] ==
'}') --depth;
833 if (depth > 0) ++pos;
836 std::string blockContent = input.substr(blockStart, pos - blockStart);
837 pos = (pos < len) ? pos + 1 : pos;
841 nested.
parse(blockContent);
844 for (
const auto &nestedRule : nested.
getRules()) {
846 rule.
setSelector(atSelector +
" " + nestedRule.getSelector());
847 for (
const auto &prop : nestedRule.getDeclaration().getProperties()) {
850 _Rules.push_back(rule);
854 if (nested.
getRuleCount() == 0 && !blockContent.empty()) {
857 _Rules.push_back(rule);
859 }
else if (semiPos != std::string::npos) {
861 std::string atRule = trim(input.substr(atStart, semiPos - atStart));
863 _Rules.push_back(rule);
873 size_t braceOpen = input.find(
'{', pos);
874 if (braceOpen == std::string::npos)
break;
876 std::string selector = trim(input.substr(pos, braceOpen - pos));
881 size_t declStart = pos;
882 while (pos < len && depth > 0) {
883 if (pos + 1 < len && input[pos] ==
'/' && input[pos + 1] ==
'*') {
884 _skipComment(input, pos);
887 if (input[pos] ==
'{') ++depth;
888 else if (input[pos] ==
'}') --depth;
889 if (depth > 0) ++pos;
892 std::string declarations = input.substr(declStart, pos - declStart);
893 pos = (pos < len) ? pos + 1 : pos;
895 if (!selector.empty()) {
898 _Rules.push_back(rule);
904 _Rules.push_back(rule);
905 _compoundCacheValid =
false;
909 if (index < _Rules.size()) {
910 _Rules.erase(_Rules.begin() +
static_cast<std::ptrdiff_t
>(index));
911 _compoundCacheValid =
false;
916 if (index < _Rules.size())
return &_Rules[index];
921 return _Rules.size();
930 for (
size_t i = 0; i < _Rules.size(); ++i) {
931 result += _Rules[i].
serialize(formatted);
932 if (formatted) result +=
"\n";
933 if (i + 1 < _Rules.size() && formatted) result +=
"\n";
940 _compoundCacheValid =
false;
950 const std::string &tag,
951 const std::vector<std::string> &classes,
952 const std::string &
id,
953 const std::vector<AncestorFrame> *ancestors)
955 std::string tagLower = tag;
956 std::transform(tagLower.begin(), tagLower.end(), tagLower.begin(),
957 [](
unsigned char c) { return std::tolower(c); });
959 std::istringstream selStream(selector);
960 std::string singleSel;
961 while (std::getline(selStream, singleSel,
',')) {
962 size_t start = singleSel.find_first_not_of(
" \t\n\r");
963 if (start == std::string::npos)
continue;
964 size_t end = singleSel.find_last_not_of(
" \t\n\r");
965 singleSel = singleSel.substr(start, end - start + 1);
967 std::vector<std::string> chain = splitCombinatorChain(singleSel);
968 if (chain.empty())
continue;
969 bool hadCombinator = chain.size() > 1;
971 std::string matchSel = stripAttributeSelectors(chain.back());
972 if (hasUnsupportedSelectorSyntax(matchSel))
continue;
974 CompoundParts compound = parseCompoundSelector(matchSel);
975 compound.attrConditions = parseAttributeConditions(chain.back());
976 if (!compoundMatches(compound, hadCombinator, tagLower, classes,
id))
continue;
978 if (ancestors && chain.size() > 1) {
979 std::vector<CompoundParts> ancestorCompounds;
980 for (
size_t i = 0; i + 1 < chain.size(); ++i) {
981 std::string aSel = stripAttributeSelectors(chain[i]);
982 if (hasUnsupportedSelectorSyntax(aSel)) {
983 ancestorCompounds.push_back({});
985 CompoundParts aCompound = parseCompoundSelector(aSel);
986 aCompound.attrConditions = parseAttributeConditions(chain[i]);
987 ancestorCompounds.push_back(std::move(aCompound));
990 if (!ancestorChainSatisfies(ancestorCompounds, *ancestors))
continue;
1002void libhtmlpp::CSSStyleSheet::_rebuildCompoundCache()
const {
1003 _compoundCache.clear();
1004 _compoundCache.reserve(_Rules.size());
1006 for (
const auto &rule : _Rules) {
1007 std::vector<_CompoundCacheBranch> branches;
1008 const std::string &sel = rule.getSelector();
1011 bool isAtRule = sel[0] ==
'@';
1012 std::string atWrapper;
1013 std::string innerSel;
1014 bool atRuleUnparseable =
false;
1017 size_t parenDepth = 0;
1018 size_t splitPos = std::string::npos;
1019 for (
size_t i = 0; i < sel.size(); ++i) {
1020 if (sel[i] ==
'(') ++parenDepth;
1021 else if (sel[i] ==
')') {
1022 if (parenDepth > 0) --parenDepth;
1023 if (parenDepth == 0) {
1034 size_t next = sel.find_first_not_of(
" \t\n\r", splitPos);
1035 if (next == std::string::npos)
break;
1036 size_t kwEnd = next;
1037 while (kwEnd < sel.size() &&
1038 std::isalpha(
static_cast<unsigned char>(sel[kwEnd]))) {
1041 std::string kw = sel.substr(next, kwEnd - next);
1042 std::transform(kw.begin(), kw.end(), kw.begin(),
1043 [](
unsigned char c) { return std::tolower(c); });
1044 if (kw ==
"and" || kw ==
"or" || kw ==
"not") {
1045 size_t afterKw = sel.find_first_not_of(
" \t\n\r", kwEnd);
1046 if (afterKw != std::string::npos && sel[afterKw] ==
'(') {
1055 if (splitPos != std::string::npos && splitPos < sel.size()) {
1056 atWrapper = sel.substr(0, splitPos);
1057 size_t innerStart = sel.find_first_not_of(
" \t\n\r", splitPos);
1058 if (innerStart != std::string::npos) innerSel = sel.substr(innerStart);
1060 if (innerSel.empty()) atRuleUnparseable =
true;
1063 if (!atRuleUnparseable) {
1064 const std::string &matchTarget = isAtRule ? innerSel : sel;
1066 std::istringstream selStream(matchTarget);
1067 std::string singleSel;
1068 while (std::getline(selStream, singleSel,
',')) {
1069 size_t start = singleSel.find_first_not_of(
" \t\n\r");
1070 if (start == std::string::npos)
continue;
1071 size_t end = singleSel.find_last_not_of(
" \t\n\r");
1072 singleSel = singleSel.substr(start, end - start + 1);
1074 _CompoundCacheBranch branch;
1075 branch.isAtRule = isAtRule;
1076 branch.atWrapper = atWrapper;
1077 branch.trimmedSelector = singleSel;
1081 bool hadCombinator = chain.size() > 1;
1082 std::string matchSel = chain.empty() ? std::string()
1088 branch.skip =
false;
1089 branch.tag = compound.tag;
1090 branch.classes = compound.classes;
1091 branch.id = compound.id;
1093 branch.hadCombinator = hadCombinator;
1094 for (
size_t i = 0; i + 1 < chain.size(); ++i) {
1097 branch.ancestorCompounds.push_back({});
1100 branch.ancestorCompounds.push_back(
1101 {aCompound.tag, aCompound.classes, aCompound.id,
1106 branches.push_back(std::move(branch));
1111 _compoundCache.push_back(std::move(branches));
1114 _compoundCacheValid =
true;
1118 const std::string &tag,
1119 const std::string &cssClass,
1120 const std::string &
id,
1121 std::map<std::string,std::string> &props,
1122 std::string &mediaRules,
1123 std::set<std::string> &seenMediaBlocks,
1124 const std::vector<AncestorFrame> *ancestors,
1125 const std::map<std::string,std::string> *targetAttributes)
const
1127 std::string tagLower = tag;
1128 std::transform(tagLower.begin(), tagLower.end(), tagLower.begin(),
1129 [](
unsigned char c) { return std::tolower(c); });
1131 std::vector<std::string> classes;
1132 if (!cssClass.empty()) {
1133 std::istringstream iss(cssClass);
1135 while (iss >> cls) classes.push_back(cls);
1142 std::set<std::string> inlineKeys;
1143 for (
const auto &kv : props) inlineKeys.insert(kv.first);
1144 std::set<std::string> importantKeys;
1154 std::map<std::string, std::array<int,3>> plainSpecOf;
1155 std::map<std::string, std::array<int,3>> importantSpecOf;
1157 if (!_compoundCacheValid) _rebuildCompoundCache();
1159 for (
size_t ri = 0; ri < _Rules.size(); ++ri) {
1160 const CSSRule &rule = _Rules[ri];
1161 const auto &branches = _compoundCache[ri];
1163 for (
const auto &branch : branches) {
1164 if (branch.skip)
continue;
1166 CompoundParts compound;
1167 compound.tag = branch.tag;
1168 compound.classes = branch.classes;
1169 compound.id = branch.id;
1170 compound.attrConditions = branch.attrConditions;
1171 if (!compoundMatches(compound, branch.hadCombinator, tagLower, classes,
id, targetAttributes))
1174 bool usedUnverifiableAncestor =
false;
1175 if (ancestors && !branch.ancestorCompounds.empty()) {
1176 std::vector<CompoundParts> ancestorCompounds;
1177 for (
const auto &ac : branch.ancestorCompounds) {
1178 ancestorCompounds.push_back({ac.tag, ac.classes, ac.id, ac.attrConditions});
1180 if (!ancestorChainSatisfies(ancestorCompounds, *ancestors, &usedUnverifiableAncestor))
1184 if (branch.isAtRule) {
1190 std::string block = branch.atWrapper +
" { " + branch.trimmedSelector +
" { ";
1192 block += prop.getName() +
": " + prop.getValue() +
"; ";
1195 if (seenMediaBlocks.insert(block).second) {
1196 mediaRules += block;
1200 std::string value = prop.getValue();
1201 bool isImportant = stripImportant(value);
1202 const std::string &key = prop.getName();
1206 if (inlineKeys.count(key) && !isImportant)
continue;
1207 if (importantKeys.count(key) && !isImportant)
continue;
1214 auto &specOf = isImportant ? importantSpecOf : plainSpecOf;
1215 auto specIt = specOf.find(key);
1216 if (specIt != specOf.end() && specIt->second > branch.specificity)
1229 if (usedUnverifiableAncestor &&
1230 ((key ==
"display" && value ==
"none") ||
1231 (key ==
"visibility" && value ==
"hidden")))
1235 specOf[key] = branch.specificity;
1236 if (isImportant) importantKeys.insert(key);
1244 const std::map<std::string,std::string> &customProperties,
1247 std::set<std::string> resolving;
1248 bool sawUnresolved =
false;
1249 std::string result = substituteVars(value, customProperties, resolving, sawUnresolved);
1250 if (unresolved) *unresolved = sawUnresolved;
void addProperty(const std::string &name, const std::string &value)
void removeProperty(const std::string &name)
const std::vector< CSSProperty > & getProperties() const
std::string serialize() const
const CSSProperty * getProperty(const std::string &name) const
void parse(const std::string &input)
CSSDeclaration & operator=(const CSSDeclaration &decl)
void setName(const std::string &name)
const std::string & getName() const
void setValue(const std::string &value)
CSSProperty & operator=(const CSSProperty &prop)
const std::string & getValue() const
CSSDeclaration & getDeclaration()
std::string serialize(bool formatted=false) const
void setSelector(const std::string &selector)
CSSRule & operator=(const CSSRule &rule)
const std::string & getSelector() const
void parse(const std::string &input)
const std::vector< CSSRule > & getRules() const
void collectApproximateMatches(const std::string &tag, const std::string &cssClass, const std::string &id, std::map< std::string, std::string > &props, std::string &mediaRules, std::set< std::string > &seenMediaBlocks, const std::vector< AncestorFrame > *ancestors=nullptr, const std::map< std::string, std::string > *targetAttributes=nullptr) const
Runs every rule in this sheet through approximateSelectorMatch against the element identified by tag/...
CSSStyleSheet & operator=(const CSSStyleSheet &sheet)
static CSSDeclaration parseInlineStyle(const std::string &style)
std::string serialize(bool formatted=false) const
const CSSRule * getRule(size_t index) const
static bool approximateSelectorMatch(const std::string &selector, const std::string &tag, const std::vector< std::string > &classes, const std::string &id, const std::vector< AncestorFrame > *ancestors=nullptr)
Conservative, NOT spec-complete selector match: selector (a single selector, or a comma-separated lis...
void addRule(const CSSRule &rule)
void removeRule(size_t index)
size_t getRuleCount() const
size_t findMatchingParen(const std::string &s, size_t openPos)
std::vector< libhtmlpp::AttributeCondition > parseAttributeConditions(const std::string &sel)
bool isWhitespace(char c)
std::string stripAttributeSelectors(const std::string &matchSel)
void splitVarArgs(const std::string &inner, std::string &name, std::string &fallback)
bool attributeValueMatches(const std::string &op, const std::string &expected, const std::string &actual)
CompoundParts parseCompoundSelector(const std::string &matchSel)
bool compoundMatches(const CompoundParts &compound, bool hadCombinator, const std::string &tag, const std::vector< std::string > &classes, const std::string &id, const std::map< std::string, std::string > *targetAttrs=nullptr)
bool attributeConditionsSatisfied(const std::vector< libhtmlpp::AttributeCondition > &conditions, const std::map< std::string, std::string > *targetAttrs)
bool stripImportant(std::string &value)
std::string substituteVars(const std::string &value, const std::map< std::string, std::string > &customProperties, std::set< std::string > &resolving, bool &sawUnresolved)
std::array< int, 3 > computeSpecificity(const std::string &selector)
std::string trim(const std::string &s)
bool hasUnsupportedSelectorSyntax(const std::string &matchSel)
bool ancestorChainSatisfies(const std::vector< CompoundParts > &ancestorCompounds, const std::vector< libhtmlpp::AncestorFrame > &ancestors, bool *usedUnverifiableAncestor=nullptr)
std::vector< std::string > splitCombinatorChain(const std::string &selector)
bool compoundPartsMatch(const CompoundParts &compound, const std::string &tag, const std::vector< std::string > &classes, const std::string &id)
std::string resolveCSSVariables(const std::string &value, const std::map< std::string, std::string > &customProperties, bool *unresolved=nullptr)
Resolves every var(--name) / var(--name, fallback) reference in value using customProperties (custom-...
std::vector< libhtmlpp::AttributeCondition > attrConditions
std::vector< std::string > classes
One ancestor of the element being matched, for the optional ancestor chain approximateSelectorMatch/c...
std::vector< std::string > classes
std::map< std::string, std::string > attributes
One "[name]"/"[name=value]"/"[name~=value]"/etc.