diff options
author | bptato <nincsnevem662@gmail.com> | 2021-11-23 12:25:42 +0100 |
---|---|---|
committer | bptato <nincsnevem662@gmail.com> | 2021-11-23 12:25:42 +0100 |
commit | ff1b68086d699510dcdbea6051460926556bd401 (patch) | |
tree | f4d7419534fbf7c960b704985ad27cb2e807a7c4 /src/css | |
parent | fae6b15f8fe7d59caa61b2295e6b71d89b70a795 (diff) | |
download | chawan-ff1b68086d699510dcdbea6051460926556bd401.tar.gz |
Support CSS descendant combinators
Diffstat (limited to 'src/css')
-rw-r--r-- | src/css/selector.nim | 94 | ||||
-rw-r--r-- | src/css/style.nim | 46 |
2 files changed, 108 insertions, 32 deletions
diff --git a/src/css/selector.nim b/src/css/selector.nim index 0ec1ac27..97c7d695 100644 --- a/src/css/selector.nim +++ b/src/css/selector.nim @@ -7,22 +7,26 @@ import css/parser type SelectorType* = enum TYPE_SELECTOR, ID_SELECTOR, ATTR_SELECTOR, CLASS_SELECTOR, - UNIVERSAL_SELECTOR, PSEUDO_SELECTOR, PSELEM_SELECTOR, FUNC_SELECTOR + UNIVERSAL_SELECTOR, PSEUDO_SELECTOR, PSELEM_SELECTOR, FUNC_SELECTOR, + COMBINATOR_SELECTOR QueryMode* = enum QUERY_TYPE, QUERY_CLASS, QUERY_ATTR, QUERY_DELIM, QUERY_VALUE, - QUERY_PSEUDO, QUERY_PSELEM + QUERY_PSEUDO, QUERY_PSELEM, QUERY_COMBINATOR PseudoElem* = enum PSEUDO_NONE, PSEUDO_BEFORE, PSEUDO_AFTER + CombinatorType* = enum + DESCENDANT_COMBINATOR + SelectorParser = object selectors: seq[SelectorList] query: QueryMode - negate: bool + combinator: Selector #TODO combinators - Selector* = object + Selector* = ref object of RootObj case t*: SelectorType of TYPE_SELECTOR: tag*: TagType @@ -42,7 +46,10 @@ type elem*: string of FUNC_SELECTOR: name*: string - selectors*: SelectorList + fsels*: SelectorList + of COMBINATOR_SELECTOR: + ct*: CombinatorType + csels*: seq[SelectorList] SelectorList* = ref object sels*: seq[Selector] @@ -54,6 +61,8 @@ proc setLen*(sellist: SelectorList, i: int) = sellist.sels.setLen(i) proc `[]`*(sellist: SelectorList, i: int): Selector = sellist.sels[i] proc len*(sellist: SelectorList): int = sellist.sels.len +func getSpecificity*(sels: SelectorList): int + func getSpecificity(sel: Selector): int = case sel.t of ID_SELECTOR: @@ -66,17 +75,21 @@ func getSpecificity(sel: Selector): int = case sel.name of "is": var best = 0 - for child in sel.selectors.sels: + for child in sel.fsels.sels: let s = getSpecificity(child) if s > best: best = s result += best of "not": - for child in sel.selectors.sels: - result += getSpecificity(child) + result += getSpecificity(sel.fsels) else: discard of UNIVERSAL_SELECTOR: discard + of COMBINATOR_SELECTOR: + case sel.ct + of DESCENDANT_COMBINATOR: + for child in sel.csels: + result += getSpecificity(child) func getSpecificity*(sels: SelectorList): int = for sel in sels.sels: @@ -110,28 +123,54 @@ func optimizeSelectorList*(selectors: SelectorList): SelectorList = else: result.add(selectors[0]) +proc addSelector(state: var SelectorParser, sel: Selector) = + if state.combinator != nil: + state.combinator.csels[^1].add(sel) + else: + state.selectors[^1].add(sel) + +proc addSelectorList(state: var SelectorParser) = + if state.combinator != nil: + state.selectors[^1].add(state.combinator) + state.combinator = nil + state.selectors.add(SelectorList()) + proc parseSelectorToken(state: var SelectorParser, csstoken: CSSToken) = + if state.query == QUERY_COMBINATOR: + if csstoken.tokenType in {CSS_IDENT_TOKEN, CSS_HASH_TOKEN, + CSS_COLON_TOKEN}: + if state.combinator == nil: + state.combinator = Selector(t: COMBINATOR_SELECTOR, ct: DESCENDANT_COMBINATOR) + state.combinator.csels.add(state.selectors[^1]) + if state.combinator.csels[^1].len > 0: + state.combinator.csels.add(SelectorList()) + state.selectors[^1] = SelectorList() + state.query = QUERY_TYPE + case csstoken.tokenType of CSS_IDENT_TOKEN: case state.query of QUERY_CLASS: - state.selectors[^1].add(Selector(t: CLASS_SELECTOR, class: $csstoken.value)) + state.addSelector(Selector(t: CLASS_SELECTOR, class: $csstoken.value)) of QUERY_TYPE: - state.selectors[^1].add(Selector(t: TYPE_SELECTOR, tag: tagType($csstoken.value))) + state.addSelector(Selector(t: TYPE_SELECTOR, tag: tagType($csstoken.value))) of QUERY_PSEUDO: - state.selectors[^1].add(Selector(t: PSEUDO_SELECTOR, pseudo: $csstoken.value)) + state.addSelector(Selector(t: PSEUDO_SELECTOR, pseudo: $csstoken.value)) of QUERY_PSELEM: - state.selectors[^1].add(Selector(t: PSELEM_SELECTOR, elem: $csstoken.value)) + state.addSelector(Selector(t: PSELEM_SELECTOR, elem: $csstoken.value)) else: discard state.query = QUERY_TYPE of CSS_DELIM_TOKEN: if csstoken.rvalue == Rune('.'): state.query = QUERY_CLASS of CSS_HASH_TOKEN: - state.selectors[^1].add(Selector(t: ID_SELECTOR, id: $csstoken.value)) + state.addSelector(Selector(t: ID_SELECTOR, id: $csstoken.value)) of CSS_COMMA_TOKEN: if state.selectors[^1].len > 0: - state.selectors.add(SelectorList()) + state.addSelectorList() + of CSS_WHITESPACE_TOKEN: + if state.selectors[^1].len > 0 or state.combinator != nil: + state.query = QUERY_COMBINATOR of CSS_COLON_TOKEN: if state.query == QUERY_PSEUDO: state.query = QUERY_PSELEM @@ -151,7 +190,7 @@ proc parseSelectorSimpleBlock(state: var SelectorParser, cssblock: CSSSimpleBloc case state.query of QUERY_ATTR: state.query = QUERY_DELIM - state.selectors[^1].add(Selector(t: ATTR_SELECTOR, attr: $csstoken.value, rel: ' ')) + state.addSelector(Selector(t: ATTR_SELECTOR, attr: $csstoken.value, rel: ' ')) of QUERY_VALUE: state.selectors[^1].sels[^1].value = $csstoken.value break @@ -183,26 +222,29 @@ proc parseSelectorFunction(state: var SelectorParser, cssfunction: CSSFunction) state.query = QUERY_TYPE else: return var fun = Selector(t: FUNC_SELECTOR, name: $cssfunction.name) - fun.selectors = SelectorList(parent: state.selectors[^1]) - state.selectors[^1].add(fun) - state.selectors[^1] = fun.selectors + fun.fsels = SelectorList(parent: state.selectors[^1]) + state.addSelector(fun) + state.selectors[^1] = fun.fsels for cval in cssfunction.value: if cval of CSSToken: - state.parseSelectorToken((CSSToken)cval) + state.parseSelectorToken(CSSToken(cval)) elif cval of CSSSimpleBlock: - state.parseSelectorSimpleBlock((CSSSimpleBlock)cval) + state.parseSelectorSimpleBlock(CSSSimpleBlock(cval)) elif cval of CSSFunction: - state.parseSelectorFunction((CSSFunction)cval) - state.selectors[^1] = fun.selectors.parent + state.parseSelectorFunction(CSSFunction(cval)) + state.selectors[^1] = fun.fsels.parent func parseSelectors*(cvals: seq[CSSComponentValue]): seq[SelectorList] = var state = SelectorParser() - state.selectors.add(SelectorList()) + state.addSelectorList() for cval in cvals: if cval of CSSToken: - state.parseSelectorToken((CSSToken)cval) + state.parseSelectorToken(CSSToken(cval)) elif cval of CSSSimpleBlock: - state.parseSelectorSimpleBlock((CSSSimpleBlock)cval) + state.parseSelectorSimpleBlock(CSSSimpleBlock(cval)) elif cval of CSSFunction: - state.parseSelectorFunction((CSSFunction)cval) + state.parseSelectorFunction(CSSFunction(cval)) + if state.combinator != nil: + state.selectors[^1].add(state.combinator) + state.combinator = nil return state.selectors diff --git a/src/css/style.nim b/src/css/style.nim index 22466d20..897c1652 100644 --- a/src/css/style.nim +++ b/src/css/style.nim @@ -50,6 +50,8 @@ func pseudoElemSelectorMatches(elem: Element, sel: Selector): SelectResult = of "before": return selectres(true, PSEUDO_AFTER) else: return selectres(false) +func selectorsMatch(elem: Element, selectors: SelectorList): SelectResult + func selectorMatches(elem: Element, sel: Selector): SelectResult = case sel.t of TYPE_SELECTOR: @@ -68,6 +70,29 @@ func selectorMatches(elem: Element, sel: Selector): SelectResult = return selectres(true) of FUNC_SELECTOR: return selectres(false) + of COMBINATOR_SELECTOR: + case sel.ct + of DESCENDANT_COMBINATOR: + #combinator without at least two members makes no sense + assert sel.csels.len > 1 + if elem.selectorsMatch(sel.csels[^1]).success: + var i = sel.csels.len - 2 + var e = elem.parentElement + var pseudo = PSEUDO_NONE + while e != nil and e != elem.ownerDocument.root and i >= 0: + let res = e.selectorsMatch(sel.csels[i]) + + if res.pseudo != PSEUDO_NONE: + if pseudo != PSEUDO_NONE: + return selectres(false) + pseudo = res.pseudo + + if res.success: + dec i + e = e.parentElement + return selectres(i == -1, pseudo) + else: + return selectres(false) func selectorsMatch(elem: Element, selectors: SelectorList): SelectResult = for sel in selectors.sels: @@ -99,10 +124,13 @@ func selectElems(document: Document, sel: Selector): seq[Element] = of FUNC_SELECTOR: case sel.name of "not": - return document.all_elements.filter((elem) => not selectorsMatch(elem, sel.selectors).psuccess) + return document.all_elements.filter((elem) => not selectorsMatch(elem, sel.fsels).psuccess) of "is", "where": - return document.all_elements.filter((elem) => selectorsMatch(elem, sel.selectors).psuccess) + return document.all_elements.filter((elem) => selectorsMatch(elem, sel.fsels).psuccess) return newSeq[Element]() + of COMBINATOR_SELECTOR: + #TODO + return document.all_elements.filter((elem) => selectorMatches(elem, sel)) func selectElems(document: Document, selectors: SelectorList): seq[Element] = assert(selectors.len > 0) @@ -114,9 +142,9 @@ func selectElems(document: Document, selectors: SelectorList): seq[Element] = if sellist[i].t == FUNC_SELECTOR: case sellist[i].name of "not": - result = result.filter((elem) => not selectorsMatch(elem, sellist[i].selectors).psuccess) + result = result.filter((elem) => not selectorsMatch(elem, sellist[i].fsels).psuccess) of "is", "where": - result = result.filter((elem) => selectorsMatch(elem, sellist[i].selectors).psuccess) + result = result.filter((elem) => selectorsMatch(elem, sellist[i].fsels).psuccess) else: discard else: result = result.filter((elem) => selectorMatches(elem, sellist[i]).psuccess) @@ -148,6 +176,12 @@ type normal: seq[tuple[e:Element,d:CSSDeclaration,p:PseudoElem]] important: seq[tuple[e:Element,d:CSSDeclaration,p:PseudoElem]] +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): array[low(PseudoElem)..high(PseudoElem), seq[CSSSimpleBlock]] = var tosorts: array[low(PseudoElem)..high(PseudoElem), seq[tuple[s:int,b:CSSSimpleBlock]]] @@ -211,7 +245,7 @@ proc applyAuthorRules*(document: Document): ApplyResult = stack.add(document.body) if rules_head.len > 0: - let parsed = parseCSS(newStringStream(rules_head)).value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) + let parsed = newStringStream(rules_head).parseStylesheet() embedded_rules.add(parsed) while stack.len > 0: @@ -224,7 +258,7 @@ proc applyAuthorRules*(document: Document): ApplyResult = rules_local &= Text(ct).data if rules_local.len > 0: - let parsed = parseCSS(newStringStream(rules_local)).value.map((x) => (sels: parseSelectors(x.prelude), oblock: x.oblock)) + let parsed = newStringStream(rules_local).parseStylesheet() embedded_rules.add(parsed) if not elem.cssapplied: |