diff options
author | bptato <nincsnevem662@gmail.com> | 2023-12-11 10:45:41 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2023-12-11 10:45:41 +0100 |
commit | d2451e51ede8a733c22fbe0b285ccb341e272b16 (patch) | |
tree | 1b118439904ec2591655968a7ce4912e3bea6ffb | |
parent | 6b9db7e8d77c3ce68558f45f9162121a13a96a2b (diff) | |
download | chawan-d2451e51ede8a733c22fbe0b285ccb341e272b16.tar.gz |
css: add case-insensitive matching
Also case-sensitive, but for now that is the same as normal matching...
-rw-r--r-- | res/ua.css | 14 | ||||
-rw-r--r-- | src/css/match.nim | 51 | ||||
-rw-r--r-- | src/css/selectorparser.nim | 49 | ||||
-rw-r--r-- | src/utils/twtstr.nim | 14 |
4 files changed, 103 insertions, 25 deletions
diff --git a/res/ua.css b/res/ua.css index 207968b6..71b3273b 100644 --- a/res/ua.css +++ b/res/ua.css @@ -113,7 +113,7 @@ input { color: red; } -input[type="hidden"] { +input[type="hidden" i] { display: none; } @@ -129,28 +129,28 @@ input::after { color: initial; } -input[type="radio"]::before { +input[type="radio" i]::before { content: '('; text-decoration: none; color: initial; } -input[type="radio"]::after { +input[type="radio" i]::after { content: ')'; text-decoration: none; color: initial; } -input:is([type="text"], [type="password"], [type="search"], [type="file"], - [type="url"], [type="email"], [type="tel"]) { +input:is([type="text" i], [type="password" i], [type="search" i], [type="file" i], + [type="url" i], [type="email" i], [type="tel" i]) { text-decoration: underline; } -input:is([type="submit"], [type="button"], [type="reset"])::before { +input:is([type="submit" i], [type="button" i], [type="reset" i])::before { color: red; } -input:is([type="submit"], [type="button"], [type="reset"])::after { +input:is([type="submit" i], [type="button" i], [type="reset" i])::after { color: red; } diff --git a/src/css/match.nim b/src/css/match.nim index 12a9fa06..1472f89b 100644 --- a/src/css/match.nim +++ b/src/css/match.nim @@ -7,20 +7,57 @@ import css/cssparser import css/selectorparser import css/stylednode import html/dom +import utils/twtstr import chame/tags +#TODO FLAG_NONE should match insensitively for certain properties func attrSelectorMatches(elem: Element, sel: Selector): bool = - case sel.rel + case sel.rel.t of RELATION_EXISTS: return elem.attrb(sel.attr) - of RELATION_EQUALS: return elem.attr(sel.attr) == sel.value - of RELATION_TOKEN: return sel.value in elem.attr(sel.attr).split(Whitespace) + of RELATION_EQUALS: + case sel.rel.flag + of FLAG_NONE: return elem.attr(sel.attr) == sel.value + of FLAG_I: return elem.attr(sel.attr).equalsIgnoreCase(sel.value) + of FLAG_S: return elem.attr(sel.attr) == sel.value + of RELATION_TOKEN: + let val = elem.attr(sel.attr) + case sel.rel.flag + of FLAG_NONE: return sel.value in val.split(AsciiWhitespace) + of FLAG_I: + let val = val.toLowerAscii() + let selval = sel.value.toLowerAscii() + return selval in val.split(AsciiWhitespace) + of FLAG_S: return sel.value in val.split(AsciiWhitespace) of RELATION_BEGIN_DASH: let val = elem.attr(sel.attr) - return val == sel.value or sel.value.startsWith(val & '-') - of RELATION_STARTS_WITH: return elem.attr(sel.attr).startsWith(sel.value) - of RELATION_ENDS_WITH: return elem.attr(sel.attr).endsWith(sel.value) - of RELATION_CONTAINS: return elem.attr(sel.attr).contains(sel.value) + case sel.rel.flag + of FLAG_NONE: return val == sel.value or sel.value.startsWith(val & '-') + of FLAG_I: + return val.equalsIgnoreCase(sel.value) or + sel.value.startsWithIgnoreCase(val & '-') + of FLAG_S: return val == sel.value or sel.value.startsWith(val & '-') + of RELATION_STARTS_WITH: + let val = elem.attr(sel.attr) + case sel.rel.flag + of FLAG_NONE: return val.startsWith(sel.value) + of FLAG_I: return val.startsWithIgnoreCase(sel.value) + of FLAG_S: return val.startsWith(sel.value) + of RELATION_ENDS_WITH: + let val = elem.attr(sel.attr) + case sel.rel.flag + of FLAG_NONE: return val.endsWith(sel.value) + of FLAG_I: return val.endsWithIgnoreCase(sel.value) + of FLAG_S: return val.endsWith(sel.value) + of RELATION_CONTAINS: + let val = elem.attr(sel.attr) + case sel.rel.flag + of FLAG_NONE: return val.contains(sel.value) + of FLAG_I: + let val = val.toLowerAscii() + let selval = sel.value.toLowerAscii() + return val.contains(selval) + of FLAG_S: return val.contains(sel.value) func selectorsMatch*[T: Element|StyledNode](elem: T, cxsel: ComplexSelector, felem: T = nil): bool diff --git a/src/css/selectorparser.nim b/src/css/selectorparser.nim index ba5c8eb1..0e892ad6 100644 --- a/src/css/selectorparser.nim +++ b/src/css/selectorparser.nim @@ -33,10 +33,17 @@ type at: int failed: bool - RelationType* = enum + RelationType* {.size: sizeof(int) div 2.} = enum RELATION_EXISTS, RELATION_EQUALS, RELATION_TOKEN, RELATION_BEGIN_DASH, RELATION_STARTS_WITH, RELATION_ENDS_WITH, RELATION_CONTAINS + RelationFlag* {.size: sizeof(int) div 2.} = enum + FLAG_NONE, FLAG_I, FLAG_S + + SelectorRelation* = object + t*: RelationType + flag*: RelationFlag + Selector* = ref object # Simple selector case t*: SelectorType of TYPE_SELECTOR: @@ -48,7 +55,7 @@ type of ATTR_SELECTOR: attr*: string value*: string - rel*: RelationType + rel*: SelectorRelation of CLASS_SELECTOR: class*: string of UNIVERSAL_SELECTOR: #TODO namespaces? @@ -108,7 +115,7 @@ func `$`*(sel: Selector): string = of ID_SELECTOR: return '#' & sel.id of ATTR_SELECTOR: - let rel = case sel.rel + let rel = case sel.rel.t of RELATION_EXISTS: "" of RELATION_EQUALS: "=" of RELATION_TOKEN: "~=" @@ -116,7 +123,11 @@ func `$`*(sel: Selector): string = of RELATION_STARTS_WITH: "^=" of RELATION_ENDS_WITH: "$=" of RELATION_CONTAINS: "*=" - return '[' & sel.attr & rel & sel.value & ']' + let flag = case sel.rel.flag + of FLAG_NONE: "" + of FLAG_I: " i" + of FLAG_S: " s" + return '[' & sel.attr & rel & sel.value & flag & ']' of CLASS_SELECTOR: return '.' & sel.class of UNIVERSAL_SELECTOR: @@ -337,10 +348,14 @@ proc parseAttributeSelector(state: var SelectorParser, if attr.tokenType != CSS_IDENT_TOKEN: fail state2.skipWhitespace() if not state2.has(): - return Selector(t: ATTR_SELECTOR, attr: attr.value, rel: RELATION_EXISTS) - let delim0 = get_tok state2.consume() - if delim0.tokenType != CSS_DELIM_TOKEN: fail - let rel = case delim0.cvalue + return Selector( + t: ATTR_SELECTOR, + attr: attr.value, + rel: SelectorRelation(t: RELATION_EXISTS) + ) + let delim = get_tok state2.consume() + if delim.tokenType != CSS_DELIM_TOKEN: fail + let rel = case delim.cvalue of '~': RELATION_TOKEN of '|': RELATION_BEGIN_DASH of '^': RELATION_STARTS_WITH @@ -349,17 +364,29 @@ proc parseAttributeSelector(state: var SelectorParser, of '=': RELATION_EQUALS else: fail if rel != RELATION_EQUALS: - let delim1 = get_tok state2.consume() - if delim1.tokenType != CSS_DELIM_TOKEN or delim1.cvalue != '=': fail + let delim = get_tok state2.consume() + if delim.tokenType != CSS_DELIM_TOKEN or delim.cvalue != '=': fail state2.skipWhitespace() if not state2.has(): fail let value = get_tok state2.consume() if value.tokenType notin {CSS_IDENT_TOKEN, CSS_STRING_TOKEN}: fail + state2.skipWhitespace() + var flag = FLAG_NONE + if state2.has(): + let delim = get_tok state2.consume() + if delim.tokenType != CSS_IDENT_TOKEN: fail + if delim.value.equalsIgnoreCase("i"): + flag = FLAG_I + elif delim.value.equalsIgnoreCase("s"): + flag = FLAG_S return Selector( t: ATTR_SELECTOR, attr: attr.value, value: value.value, - rel: rel + rel: SelectorRelation( + t: rel, + flag: flag + ) ) proc parseClassSelector(state: var SelectorParser): Selector = diff --git a/src/utils/twtstr.nim b/src/utils/twtstr.nim index 28a91558..13a432bf 100644 --- a/src/utils/twtstr.nim +++ b/src/utils/twtstr.nim @@ -177,6 +177,20 @@ func toHexLower*(u: uint16): string = func equalsIgnoreCase*(s1, s2: string): bool {.inline.} = return s1.cmpIgnoreCase(s2) == 0 +func startsWithIgnoreCase*(s1, s2: string): bool = + if s1.len < s2.len: return false + for i in 0 ..< s2.len: + if s1[i].toLowerAscii() != s2[i].toLowerAscii(): + return false + return true + +func endsWithIgnoreCase*(s1, s2: string): bool = + if s1.len < s2.len: return false + for i in countdown(s2.high, 0): + if s1[i].toLowerAscii() != s2[i].toLowerAscii(): + return false + return true + func isDigitAscii*(r: Rune): bool = return int(r) < 256 and isDigit(char(r)) |