about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--res/ua.css14
-rw-r--r--src/css/match.nim51
-rw-r--r--src/css/selectorparser.nim49
-rw-r--r--src/utils/twtstr.nim14
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))