about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2021-12-18 16:03:24 +0100
committerbptato <nincsnevem662@gmail.com>2021-12-18 16:03:47 +0100
commiteddf377277f85b172df453b5c3d5763d3a4467c6 (patch)
treeceaa7b7faef7670704a11868659b87bedfae8d4c
parenta125464839927fd04f761c530e90888e340758ef (diff)
downloadchawan-eddf377277f85b172df453b5c3d5763d3a4467c6.tar.gz
Refactor selector code, optimize style tags
-rw-r--r--src/css/cascade.nim152
-rw-r--r--src/css/select.nim (renamed from src/css/style.nim)173
-rw-r--r--src/css/selparser.nim (renamed from src/css/selector.nim)11
-rw-r--r--src/css/values.nim4
-rw-r--r--src/html/dom.nim11
-rw-r--r--src/html/parser.nim30
-rw-r--r--src/io/buffer.nim3
-rw-r--r--src/main.nim2
8 files changed, 207 insertions, 179 deletions
diff --git a/src/css/cascade.nim b/src/css/cascade.nim
new file mode 100644
index 00000000..cd952487
--- /dev/null
+++ b/src/css/cascade.nim
@@ -0,0 +1,152 @@
+import streams
+import sequtils
+import sugar
+import algorithm
+
+import css/select
+import css/selparser
+import css/parser
+import css/values
+import html/dom
+import html/tags
+
+type
+  ApplyResult = object
+    normal: seq[CSSDeclaration]
+    important: seq[CSSDeclaration]
+  RuleList* = array[low(PseudoElem)..high(PseudoElem), seq[CSSSimpleBlock]]
+
+proc applyProperty(elem: Element, d: CSSDeclaration, pseudo: PseudoElem) =
+  var parent: CSSSpecifiedValues
+  if elem.parentElement != nil:
+    parent = elem.parentElement.css
+  else:
+    parent = rootProperties()
+
+  case pseudo
+  of PSEUDO_NONE:
+    elem.css.applyValue(parent, d)
+  of PSEUDO_BEFORE, PSEUDO_AFTER:
+    if elem.pseudo[pseudo] == nil:
+      elem.pseudo[pseudo] = elem.css.inheritProperties()
+    elem.pseudo[pseudo].applyValue(parent, d)
+
+  elem.cssapplied = true
+  elem.rendered = false
+
+func calcRules(elem: Element, rules: ParsedStylesheet): RuleList =
+  var tosorts: array[low(PseudoElem)..high(PseudoElem), seq[tuple[s:int,b:CSSSimpleBlock]]]
+  for rule in rules:
+    for sel in rule.sels:
+      let match = elem.selectorsMatch(sel)
+      if match.success:
+        let spec = getSpecificity(sel)
+        tosorts[match.pseudo].add((spec,rule.oblock))
+
+  for i in low(PseudoElem)..high(PseudoElem):
+    tosorts[i].sort((x, y) => cmp(x.s,y.s))
+    result[i] = tosorts[i].map((x) => x.b)
+
+proc applyItems(ares: var ApplyResult, decls: seq[CSSParsedItem]) =
+  for item in decls:
+    if item of CSSDeclaration:
+      let decl = CSSDeclaration(item)
+      if decl.important:
+        ares.important.add(decl)
+      else:
+        ares.normal.add(decl)
+
+proc applyRules(element: Element, ua, user, author: RuleList, pseudo: PseudoElem) =
+  var ares: ApplyResult
+
+  let rules_user_agent = ua[pseudo]
+  for rule in rules_user_agent:
+    let decls = parseCSSListOfDeclarations(rule.value)
+    ares.applyItems(decls)
+
+  let rules_user = user[pseudo]
+  for rule in rules_user:
+    let decls = parseCSSListOfDeclarations(rule.value)
+    ares.applyItems(decls)
+
+  let rules_author = author[pseudo]
+  for rule in rules_author:
+    let decls = parseCSSListOfDeclarations(rule.value)
+    ares.applyItems(decls)
+
+  if pseudo == PSEUDO_NONE:
+    let style = element.attr("style")
+    if style.len > 0:
+      let inline_rules = newStringStream(style).parseCSSListOfDeclarations()
+      ares.applyItems(inline_rules)
+
+  for rule in ares.normal:
+    element.applyProperty(rule, pseudo)
+
+  for rule in ares.important:
+    element.applyProperty(rule, pseudo)
+
+proc applyRules*(document: Document, ua, user: ParsedStylesheet) =
+  var stack: seq[Element]
+
+  var embedded_rules: seq[ParsedStylesheet]
+
+  stack.add(document.head)
+  var rules_head: ParsedStylesheet
+
+  for child in document.head.children:
+    if child.tagType == TAG_STYLE:
+      let style = HTMLStyleElement(child)
+      rules_head.add(style.stylesheet)
+
+  if rules_head.len > 0:
+    embedded_rules.add(rules_head)
+
+  stack.setLen(0)
+
+  stack.add(document.root)
+
+  document.root.css = rootProperties()
+
+  while stack.len > 0:
+    let elem = stack.pop()
+
+    var rules_local: ParsedStylesheet
+    for child in document.head.children:
+      if child.tagType == TAG_STYLE:
+        let style = HTMLStyleElement(child)
+        rules_local.add(style.stylesheet)
+
+    if rules_local.len > 0:
+      embedded_rules.add(rules_local)
+
+    if not elem.cssapplied:
+      if elem.parentElement != nil:
+        elem.css = elem.parentElement.css.inheritProperties()
+      else:
+        elem.css = rootProperties()
+
+      let uarules = calcRules(elem, ua)
+      let userrules = calcRules(elem, user)
+      let this_rules = embedded_rules.concat()
+      let authorrules = calcRules(elem, this_rules)
+
+      for pseudo in low(PseudoElem)..high(PseudoElem):
+        elem.applyRules(uarules, userrules, authorrules, pseudo)
+
+    var i = elem.children.len - 1
+    while i >= 0:
+      let child = elem.children[i]
+      stack.add(child)
+      dec i
+
+    if rules_local.len > 0:
+      discard embedded_rules.pop()
+
+proc applyStylesheets*(document: Document, uass, userss: ParsedStylesheet) =
+  document.applyRules(uass, userss)
+
+proc refreshStyle*(elem: Element) =
+  elem.cssapplied = false
+  for child in elem.children:
+    child.refreshStyle()
diff --git a/src/css/style.nim b/src/css/select.nim
index 63bc48e1..1f43844f 100644
--- a/src/css/style.nim
+++ b/src/css/select.nim
@@ -1,22 +1,17 @@
 import unicode
-import strutils
 import tables
-import streams
+import strutils
 import sequtils
 import sugar
-import algorithm
+import streams
 
-import css/selector
+import css/selparser
 import css/parser
-import css/values
 import html/dom
-import html/tags
-
-#TODO case sensitivity
 
-type SelectResult = object
-  success: bool
-  pseudo: PseudoElem
+type SelectResult* = object
+  success*: bool
+  pseudo*: PseudoElem
 
 func selectres(s: bool, p: PseudoElem = PSEUDO_NONE): SelectResult =
   return SelectResult(success: s, pseudo: p)
@@ -52,7 +47,7 @@ func pseudoElemSelectorMatches(elem: Element, sel: Selector): SelectResult =
   of "after": return selectres(true, PSEUDO_AFTER)
   else: return selectres(false)
 
-func selectorsMatch(elem: Element, selectors: SelectorList): SelectResult
+func selectorsMatch*(elem: Element, selectors: SelectorList): SelectResult
 
 func funcSelectorMatches(elem: Element, sel: Selector): SelectResult =
   case sel.name
@@ -145,7 +140,7 @@ func selectorMatches(elem: Element, sel: Selector): SelectResult =
     else:
       return selectres(false)
 
-func selectorsMatch(elem: Element, selectors: SelectorList): SelectResult =
+func selectorsMatch*(elem: Element, selectors: SelectorList): SelectResult =
   for sel in selectors.sels:
     let res = selectorMatches(elem, sel)
     if not res.success:
@@ -195,155 +190,3 @@ proc querySelector*(document: Document, q: string): seq[Element] =
   for sel in selectors:
     result.add(document.selectElems(sel))
 
-proc applyProperty(elem: Element, d: CSSDeclaration, pseudo: PseudoElem) =
-  var parent: CSSSpecifiedValues
-  if elem.parentElement != nil:
-    parent = elem.parentElement.css
-  else:
-    parent = rootProperties()
-
-  case pseudo
-  of PSEUDO_NONE:
-    elem.css.applyValue(parent, d)
-  of PSEUDO_BEFORE, PSEUDO_AFTER:
-    if elem.pseudo[pseudo] == nil:
-      elem.pseudo[pseudo] = elem.css.inheritProperties()
-    elem.pseudo[pseudo].applyValue(parent, d)
-
-  elem.cssapplied = true
-  elem.rendered = false
-
-type
-  ParsedRule* = tuple[sels: seq[SelectorList], oblock: CSSSimpleBlock]
-  ParsedStylesheet* = seq[ParsedRule]
-  ApplyResult = object
-    normal: seq[CSSDeclaration]
-    important: seq[CSSDeclaration]
-  RuleList = array[low(PseudoElem)..high(PseudoElem), seq[CSSSimpleBlock]]
-
-proc parseStylesheet*(s: Stream): ParsedStylesheet =
-  for v in parseCSS(s).value:
-    let sels = parseSelectors(v.prelude)
-    if sels.len > 1 or sels[^1].len > 0:
-      result.add((sels: sels, oblock: v.oblock))
-
-func calcRules(elem: Element, rules: ParsedStylesheet): RuleList =
-  var tosorts: array[low(PseudoElem)..high(PseudoElem), seq[tuple[s:int,b:CSSSimpleBlock]]]
-  for rule in rules:
-    for sel in rule.sels:
-      let match = elem.selectorsMatch(sel)
-      if match.success:
-        let spec = getSpecificity(sel)
-        tosorts[match.pseudo].add((spec,rule.oblock))
-
-  for i in low(PseudoElem)..high(PseudoElem):
-    tosorts[i].sort((x, y) => cmp(x.s,y.s))
-    result[i] = tosorts[i].map((x) => x.b)
-
-proc applyItems(ares: var ApplyResult, decls: seq[CSSParsedItem]) =
-  for item in decls:
-    if item of CSSDeclaration:
-      let decl = CSSDeclaration(item)
-      if decl.important:
-        ares.important.add(decl)
-      else:
-        ares.normal.add(decl)
-
-proc applyRules(element: Element, ua, user, author: RuleList, pseudo: PseudoElem) =
-  var ares: ApplyResult
-
-  let rules_user_agent = ua[pseudo]
-  for rule in rules_user_agent:
-    let decls = parseCSSListOfDeclarations(rule.value)
-    ares.applyItems(decls)
-
-  let rules_user = user[pseudo]
-  for rule in rules_user:
-    let decls = parseCSSListOfDeclarations(rule.value)
-    ares.applyItems(decls)
-
-  let rules_author = author[pseudo]
-  for rule in rules_author:
-    let decls = parseCSSListOfDeclarations(rule.value)
-    ares.applyItems(decls)
-
-  if pseudo == PSEUDO_NONE:
-    let style = element.attr("style")
-    if style.len > 0:
-      let inline_rules = newStringStream(style).parseCSSListOfDeclarations()
-      ares.applyItems(inline_rules)
-
-  for rule in ares.normal:
-    element.applyProperty(rule, pseudo)
-
-  for rule in ares.important:
-    element.applyProperty(rule, pseudo)
-
-proc applyRules*(document: Document, ua, user: ParsedStylesheet) =
-  var stack: seq[Element]
-
-  var embedded_rules: seq[ParsedStylesheet]
-
-  stack.add(document.head)
-  var rules_head = ""
-
-  for child in document.head.children:
-    if child.tagType == TAG_STYLE:
-      for ct in child.childNodes:
-        if ct.nodeType == TEXT_NODE:
-          rules_head &= Text(ct).data
-
-  if rules_head.len > 0:
-    let parsed = newStringStream(rules_head).parseStylesheet()
-    embedded_rules.add(parsed)
-
-  stack.setLen(0)
-
-  stack.add(document.root)
-
-  document.root.css = rootProperties()
-
-  while stack.len > 0:
-    let elem = stack.pop()
-
-    var rules_local = ""
-    for child in elem.children:
-      if child.tagType == TAG_STYLE:
-        for ct in child.childNodes:
-          if ct.nodeType == TEXT_NODE:
-            rules_local &= Text(ct).data
-
-    if rules_local.len > 0:
-      let parsed = newStringStream(rules_local).parseStylesheet()
-      embedded_rules.add(parsed)
-
-    if not elem.cssapplied:
-      if elem.parentElement != nil:
-        elem.css = elem.parentElement.css.inheritProperties()
-      else:
-        elem.css = rootProperties()
-
-      let uarules = calcRules(elem, ua)
-      let userrules = calcRules(elem, user)
-      let this_rules = embedded_rules.concat()
-      let authorrules = calcRules(elem, this_rules)
-
-      for pseudo in low(PseudoElem)..high(PseudoElem):
-        elem.applyRules(uarules, userrules, authorrules, pseudo)
-
-    var i = elem.children.len - 1
-    while i >= 0:
-      let child = elem.children[i]
-      stack.add(child)
-      dec i
-
-    if rules_local.len > 0:
-      discard embedded_rules.pop()
-
-proc applyStylesheets*(document: Document, uass, userss: ParsedStylesheet) =
-  document.applyRules(uass, userss)
-
-proc refreshStyle*(elem: Element) =
-  elem.cssapplied = false
-  for child in elem.children:
-    child.refreshStyle()
diff --git a/src/css/selector.nim b/src/css/selparser.nim
index 67f53b42..0c1655aa 100644
--- a/src/css/selector.nim
+++ b/src/css/selparser.nim
@@ -1,4 +1,5 @@
 import unicode
+import streams
 
 import css/parser
 import html/tags
@@ -56,6 +57,9 @@ type
     sels*: seq[Selector]
     parent*: SelectorList
 
+  ParsedRule* = tuple[sels: seq[SelectorList], oblock: CSSSimpleBlock]
+  ParsedStylesheet* = seq[ParsedRule]
+
 proc add*(sellist: SelectorList, sel: Selector) = sellist.sels.add(sel)
 proc add*(sellist: SelectorList, sels: SelectorList) = sellist.sels.add(sels.sels)
 proc setLen*(sellist: SelectorList, i: int) = sellist.sels.setLen(i)
@@ -297,3 +301,10 @@ func parseSelectors*(cvals: seq[CSSComponentValue]): seq[SelectorList] =
     state.selectors[^1].add(state.combinator)
     state.combinator = nil
   return state.selectors
+
+#TODO this is pretty dumb
+proc parseStylesheet*(s: Stream): ParsedStylesheet =
+  for v in parseCSS(s).value:
+    let sels = parseSelectors(v.prelude)
+    if sels.len > 1 or sels[^1].len > 0:
+      result.add((sels, v.oblock))
diff --git a/src/css/values.nim b/src/css/values.nim
index 4771dfc5..0d6f2ae6 100644
--- a/src/css/values.nim
+++ b/src/css/values.nim
@@ -8,10 +8,10 @@ import strutils
 
 import utils/twtstr
 import css/parser
-import css/selector
+import css/selparser
 import types/color
 
-export selector.PseudoElem
+export selparser.PseudoElem
 
 type
   CSSUnit* = enum
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 02258296..f9a4cfea 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -4,6 +4,7 @@ import options
 import strutils
 
 import css/values
+import css/selparser
 import html/tags
 
 type
@@ -112,6 +113,14 @@ type
     value*: Option[int]
     ordinalvalue*: int
 
+  HTMLStyleElement* = ref object of HTMLElement
+    stylesheet*: ParsedStylesheet
+
+iterator textNodes*(node: Node): Text {.inline.} =
+  for node in node.childNodes:
+    if node.nodeType == TEXT_NODE:
+      yield Text(node)
+
 func firstChild(node: Node): Node =
   if node.childNodes.len == 0:
     return nil
@@ -275,6 +284,8 @@ func newHtmlElement*(tagType: TagType): HTMLElement =
     HTMLMenuElement(result).ordinalcounter = 1
   of TAG_LI:
     result = new(HTMLLIElement)
+  of TAG_STYLE:
+    result = new(HTMLStyleElement)
   else:
     result = new(HTMLElement)
 
diff --git a/src/html/parser.nim b/src/html/parser.nim
index 153df1c5..c3e12300 100644
--- a/src/html/parser.nim
+++ b/src/html/parser.nim
@@ -10,6 +10,7 @@ import utils/radixtree
 import html/dom
 import html/entity
 import html/tags
+import css/selparser
 
 type
   HTMLParseState = object
@@ -306,18 +307,27 @@ proc processDocumentStartElement(state: var HTMLParseState, element: Element, ta
     else: discard
 
 proc processDocumentEndElement(state: var HTMLParseState, tag: DOMParsedTag) =
-  if tag.tagid in VoidTagTypes:
-    return
-  if tag.tagid == TAG_HEAD:
-    processDocumentBody(state)
-    return
-  if tag.tagid == TAG_BODY:
-    return
-  if state.elementNode.nodeType == ELEMENT_NODE and tag.tagid != state.elementNode.tagType:
+  if tag.tagid != state.elementNode.tagType:
     if state.elementNode.tagType in SelfClosingTagTypes:
       processDocumentEndNode(state)
-  
-  processDocumentEndNode(state)
+      processDocumentEndNode(state)
+  else:
+    case tag.tagid
+    of VoidTagTypes:
+      return
+    of TAG_HEAD:
+      processDocumentBody(state)
+      return
+    of TAG_BODY:
+      return
+    of TAG_STYLE:
+      let style = HTMLStyleElement(state.elementNode)
+      var str = ""
+      for child in style.textNodes:
+        str &= child.data
+      style.stylesheet = newStringStream(str).parseStylesheet()
+    else: discard
+    processDocumentEndNode(state)
 
 proc processDocumentTag(state: var HTMLParseState, tag: DOMParsedTag) =
   if state.in_script:
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index d7a2b092..9edc0738 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -5,7 +5,8 @@ import unicode
 import streams
 
 import css/values
-import css/style
+import css/cascade
+import css/selparser
 import utils/twtstr
 import html/dom
 import html/tags
diff --git a/src/main.nim b/src/main.nim
index dc3db21c..06c6bdb5 100644
--- a/src/main.nim
+++ b/src/main.nim
@@ -1,4 +1,4 @@
-import httpClient
+import httpclient
 import uri
 import os
 import streams