diff options
author | bptato <nincsnevem662@gmail.com> | 2021-12-18 16:03:24 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-12-18 16:03:47 +0100 |
commit | eddf377277f85b172df453b5c3d5763d3a4467c6 (patch) | |
tree | ceaa7b7faef7670704a11868659b87bedfae8d4c | |
parent | a125464839927fd04f761c530e90888e340758ef (diff) | |
download | chawan-eddf377277f85b172df453b5c3d5763d3a4467c6.tar.gz |
Refactor selector code, optimize style tags
-rw-r--r-- | src/css/cascade.nim | 152 | ||||
-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.nim | 4 | ||||
-rw-r--r-- | src/html/dom.nim | 11 | ||||
-rw-r--r-- | src/html/parser.nim | 30 | ||||
-rw-r--r-- | src/io/buffer.nim | 3 | ||||
-rw-r--r-- | src/main.nim | 2 |
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 |