import tables
import strutils
import css/cssparser
import css/selectorparser
import css/stylednode
import html/dom
import html/tags
func attrSelectorMatches(elem: Element, sel: Selector): bool =
case sel.rel
of ' ': return sel.attr in elem.attributes
of '=': return elem.attr(sel.attr) == sel.value
of '~': return sel.value in elem.attr(sel.attr).split(Whitespace)
of '|':
let val = elem.attr(sel.attr)
return val == sel.value or sel.value.startsWith(val & '-')
of '^': return elem.attr(sel.attr).startsWith(sel.value)
of '$': return elem.attr(sel.attr).endsWith(sel.value)
of '*': return elem.attr(sel.attr).contains(sel.value)
else: return false
func pseudoSelectorMatches[T: Element|StyledNode](elem: T, sel: Selector, felem: T): bool =
let selem = elem
when elem is StyledNode:
let elem = Element(elem.node)
case sel.pseudo
of PSEUDO_FIRST_CHILD: return elem.parentNode.firstElementChild == elem
of PSEUDO_LAST_CHILD: return elem.parentNode.lastElementChild == elem
of PSEUDO_ONLY_CHILD: return elem.parentNode.firstElementChild == elem and elem.parentNode.lastElementChild == elem
of PSEUDO_HOVER:
when selem is StyledNode: felem.addDependency(selem, DEPEND_HOVER)
return elem.hover
of PSEUDO_ROOT: return elem == elem.document.html
of PSEUDO_NTH_CHILD:
let n = int64(sel.pseudonum - 1)
var i = 0
for child in elem.parentNode.children:
if i == n:
return child == elem
inc i
return false
of PSEUDO_CHECKED:
when selem is StyledNode: felem.addDependency(selem, DEPEND_CHECKED)
if elem.tagType == TAG_INPUT:
return HTMLInputElement(elem).checked
elif elem.tagType == TAG_OPTION:
return HTMLOptionElement(elem).selected
return false
func selectorsMatch*[T: Element|StyledNode](elem: T, selectors: SelectorList, felem: T = nil): bool
func funcSelectorMatches[T: Element|StyledNode](elem: T, sel: Selector, felem: T): bool =
case sel.name
of "not":
for slist in sel.fsels:
if elem.selectorsMatch(slist, felem):
return false
return true
of "is", "where":
for slist in sel.fsels:
if elem.selectorsMatch(slist, felem):
return true
return false
else: discard
func combinatorSelectorMatches[T: Element|StyledNode](elem: T, sel: Selector, felem: T): bool =
let selem = elem
#combinator without at least two members makes no sense
assert sel.csels.len > 1
if selem.selectorsMatch(sel.csels[^1], felem):
var i = sel.csels.len - 2
case sel.ct
of DESCENDANT_COMBINATOR:
when selem is StyledNode:
var e = elem.parent
else:
var e = elem.parentElement
while e != nil and i >= 0:
if e.selectorsMatch(sel.csels[i], felem):
dec i
when elem is StyledNode:
e = e.parent
else:
e = e.parentElement
of CHILD_COMBINATOR:
when elem is StyledNode:
var e = elem.parent
else:
var e = elem.parentElement
while e != nil and i >= 0:
if not e.selectorsMatch(sel.csels[i], felem):
return false
dec i
when elem is StyledNode:
e = e.parent
else:
e = e.parentElement
of NEXT_SIBLING_COMBINATOR:
var found = false
when elem is StyledNode:
var parent = elem.parent
else:
var parent = elem.parentElement
for child in parent.children_rev:
when elem is StyledNode:
if child.t != STYLED_ELEMENT or child.node == nil: continue
if found:
if not child.selectorsMatch(sel.csels[i], felem):
return false
dec i
if i < 0:
return true
if child == elem:
found = true
of SUBSEQ_SIBLING_COMBINATOR:
var found = false
when selem is StyledNode:
var parent = selem.parent
else:
var parent = elem.parentElement
for child in parent.children_rev:
when selem is StyledNode:
if child.t != STYLED_ELEMENT or child.node == nil: continue
if found:
if child.selectorsMatch(sel.csels[i], felem):
dec i
if i < 0:
return true
if child == selem:
found = true
return i == -1
return false
func selectorMatches[T: Element|StyledNode](elem: T, sel: Selector, felem: T): bool =
let selem = elem
when elem is StyledNode:
let elem = Element(selem.node)
case sel.t
of TYPE_SELECTOR:
return elem.tagType == sel.tag
of CLASS_SELECTOR:
return sel.class in elem.classList
of ID_SELECTOR:
return sel.id == elem.id
of ATTR_SELECTOR:
return elem.attrSelectorMatches(sel)
of PSEUDO_SELECTOR:
return pseudoSelectorMatches(selem, sel, felem)
of PSELEM_SELECTOR:
return true
of UNIVERSAL_SELECTOR:
return true
of FUNC_SELECTOR:
return funcSelectorMatches(selem, sel, felem)
of COMBINATOR_SELECTOR:
return combinatorSelectorMatches(selem, sel, felem)
# WARNING for StyledNode, this has the side effect of modifying depends.
#TODO make that an explicit flag or something, also get rid of the Element case
func selectorsMatch*[T: Element|StyledNode](elem: T, selectors: SelectorList, felem: T = nil): bool =
let felem = if felem != nil:
felem
else:
elem
for sel in selectors.sels:
if not selectorMatches(elem, sel, felem):
return false
return true
#TODO idk, it's not like we have JS anyways
#func selectElems[T: Element|StyledNode](element: T, sel: Selector, felem: T): seq[T] =
# case sel.t
# of TYPE_SELECTOR:
# return element.filterDescendants((elem) => elem.tagType == sel.tag)
# of ID_SELECTOR:
# return element.filterDescendants((elem) => elem.id == sel.id)
# of CLASS_SELECTOR:
# return element.filterDescendants((elem) => sel.class in elem.classList)
# of UNIVERSAL_SELECTOR:
# return element.all_descendants
# of ATTR_SELECTOR:
# return element.filterDescendants((elem) => attrSelectorMatches(elem, sel))
# of PSEUDO_SELECTOR:
# return element.filterDescendants((elem) => pseudoSelectorMatches(elem, sel, felem))
# of PSELEM_SELECTOR:
# return element.all_descendants
# of FUNC_SELECTOR:
# return element.filterDescendants((elem) => selectorMatches(elem, sel))
# of COMBINATOR_SELECTOR:
# return element.filterDescendants((elem) => selectorMatches(elem, sel))
#
#func selectElems(element: Element, selectors: SelectorList): seq[Element] =
# assert(selectors.len > 0)
# let sellist = optimizeSelectorList(selectors)
# result = element.selectElems(selectors[0], element)
# var i = 1
#
# while i < sellist.len:
# result = result.filter((elem) => selectorMatches(elem, sellist[i], elem))
# inc i
#
#proc querySelectorAll*(document: Document, q: string): seq[Element] =
# let ss = newStringStream(q)
# let cvals = parseListOfComponentValues(ss)
# let selectors = parseSelectors(cvals)
#
# if document.html != nil:
# for sel in selectors:
# result.add(document.html.selectElems(sel))
#
#proc querySelector*(document: Document, q: string): Element =
# let elems = document.querySelectorAll(q)
# if elems.len > 0:
# return elems[0]
# return nil
#
#proc querySelectorAll*(element: Element, q: string): seq[Element] =
# let ss = newStringStream(q)
# let cvals = parseListOfComponentValues(ss)
# let selectors = parseSelectors(cvals)
#
# for sel in selectors:
# result.add(element.selectElems(sel))
#
#proc querySelector*(element: Element, q: string): Element =
# let elems = element.querySelectorAll(q)
# if elems.len > 0:
# return elems[0]
# return nil