about summary refs log blame commit diff stats
path: root/src/css/selector.nim
blob: a474bd8f784b6291e7f001040e58e31bd1c1a923 (plain) (tree)
1
2
3
4
5
6




                        
                  















































                                                                                  























































                                                                             


                                                                        































































                                                                                                
                 



























                                                                        
import unicode

import ../types/enums
import ../types/tagtypes

import ./cssparser

type
  SelectorType* = enum
    TYPE_SELECTOR, ID_SELECTOR, ATTR_SELECTOR, CLASS_SELECTOR,
    UNIVERSAL_SELECTOR, PSEUDO_SELECTOR, PSELEM_SELECTOR, FUNC_SELECTOR

  QueryMode* = enum
    QUERY_TYPE, QUERY_CLASS, QUERY_ATTR, QUERY_DELIM, QUERY_VALUE,
    QUERY_PSEUDO, QUERY_PSELEM

  SelectorParser = object
    selectors: seq[SelectorList]
    query: QueryMode
    negate: bool

  #TODO combinators
  Selector* = object
    case t*: SelectorType
    of TYPE_SELECTOR:
      tag*: TagType
    of ID_SELECTOR:
      id*: string
    of ATTR_SELECTOR:
      attr*: string
      value*: string
      rel*: char
    of CLASS_SELECTOR:
      class*: string
    of UNIVERSAL_SELECTOR: #TODO namespaces?
      discard
    of PSEUDO_SELECTOR:
      pseudo*: string
    of PSELEM_SELECTOR:
      elem*: string
    of FUNC_SELECTOR:
      name*: string
      selectors*: SelectorList

  SelectorList* = ref object
    sels*: seq[Selector]
    parent*: SelectorList

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)
proc `[]`*(sellist: SelectorList, i: int): Selector = sellist.sels[i]
proc len*(sellist: SelectorList): int = sellist.sels.len

func getSpecificity(sel: Selector): int =
  case sel.t
  of ID_SELECTOR:
    result += 1000000
  of CLASS_SELECTOR, ATTR_SELECTOR, PSEUDO_SELECTOR:
    result += 1000
  of TYPE_SELECTOR, PSELEM_SELECTOR:
    result += 1
  of FUNC_SELECTOR:
    case sel.name
    of "is":
      var best = 0
      for child in sel.selectors.sels:
        let s = getSpecificity(child)
        if s > best:
          best = s
      result += best
    of "not":
      for child in sel.selectors.sels:
        result += getSpecificity(child)
    else: discard
  of UNIVERSAL_SELECTOR:
    discard

func getSpecificity*(sels: SelectorList): int =
  for sel in sels.sels:
    result += getSpecificity(sel)

func optimizeSelectorList*(selectors: SelectorList): SelectorList =
  new(result)
  #pass 1: check for invalid sequences
  var i = 1
  while i < selectors.len:
    let sel = selectors[i]
    if sel.t == TYPE_SELECTOR or sel.t == UNIVERSAL_SELECTOR:
      return SelectorList()
    inc i

  #pass 2: move selectors in combination
  if selectors.len > 1:
    var i = 0
    var slow = SelectorList()
    if selectors[0].t == UNIVERSAL_SELECTOR:
      inc i

    while i < selectors.len:
      if selectors[i].t in {ATTR_SELECTOR, PSEUDO_SELECTOR, PSELEM_SELECTOR}:
        slow.add(selectors[i])
      else:
        result.add(selectors[i])
      inc i

    result.add(slow)
  else:
    result.add(selectors[0])

proc parseSelectorToken(state: var SelectorParser, csstoken: CSSToken) =
  case csstoken.tokenType
  of CSS_IDENT_TOKEN:
    case state.query
    of QUERY_CLASS:
      state.selectors[^1].add(Selector(t: CLASS_SELECTOR, class: $csstoken.value))
    of QUERY_TYPE:
      state.selectors[^1].add(Selector(t: TYPE_SELECTOR, tag: tagType($csstoken.value)))
    of QUERY_PSEUDO:
      state.selectors[^1].add(Selector(t: PSEUDO_SELECTOR, pseudo: $csstoken.value))
    of QUERY_PSELEM:
      state.selectors[^1].add(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))
  of CSS_COMMA_TOKEN:
    if state.selectors[^1].len > 0:
      state.selectors.add(SelectorList())
  of CSS_COLON_TOKEN:
    if state.query == QUERY_PSEUDO:
      state.query = QUERY_PSELEM
    else:
      state.query = QUERY_PSEUDO
  else: discard

proc parseSelectorSimpleBlock(state: var SelectorParser, cssblock: CSSSimpleBlock) =
  case cssblock.token.tokenType
  of CSS_LBRACKET_TOKEN:
    state.query = QUERY_ATTR
    for cval in cssblock.value:
      if cval of CSSToken:
        let csstoken = (CSSToken)cval
        case csstoken.tokenType
        of CSS_IDENT_TOKEN:
          case state.query
          of QUERY_ATTR:
            state.query = QUERY_DELIM
            state.selectors[^1].add(Selector(t: ATTR_SELECTOR, attr: $csstoken.value, rel: ' '))
          of QUERY_VALUE:
            state.selectors[^1].sels[^1].value = $csstoken.value
            break
          else: discard
        of CSS_STRING_TOKEN:
          case state.query
          of QUERY_VALUE:
            state.selectors[^1].sels[^1].value = $csstoken.value
            break
          else: discard
        of CSS_DELIM_TOKEN:
          case csstoken.rvalue
          of Rune('~'), Rune('|'), Rune('^'), Rune('$'), Rune('*'):
            if state.query == QUERY_DELIM:
              state.selectors[^1].sels[^1].rel = char(csstoken.rvalue)
          of Rune('='):
            if state.query == QUERY_DELIM:
              state.query = QUERY_VALUE
          else: discard
        else: discard
    state.query = QUERY_TYPE
  else: discard

proc parseSelectorFunction(state: var SelectorParser, cssfunction: CSSFunction) =
  case $cssfunction.name
  of "not", "is":
    if state.query != QUERY_PSEUDO:
      return
    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
  for cval in cssfunction.value:
    if cval of CSSToken:
      state.parseSelectorToken((CSSToken)cval)
    elif cval of CSSSimpleBlock:
      state.parseSelectorSimpleBlock((CSSSimpleBlock)cval)
    elif cval of CSSFunction:
      state.parseSelectorFunction((CSSFunction)cval)
  state.selectors[^1] = fun.selectors.parent

func parseSelectors*(cvals: seq[CSSComponentValue]): seq[SelectorList] =
  var state = SelectorParser()
  state.selectors.add(SelectorList())
  for cval in cvals:
    if cval of CSSToken:
      state.parseSelectorToken((CSSToken)cval)
    elif cval of CSSSimpleBlock:
      state.parseSelectorSimpleBlock((CSSSimpleBlock)cval)
    elif cval of CSSFunction:
      state.parseSelectorFunction((CSSFunction)cval)
  return state.selectors