about summary refs log blame commit diff stats
path: root/src/css/sheet.nim
blob: be8ac80cab32d68bc3aa9ef824367f133b84d73b (plain) (tree)
1
2
3
4
5
6
7
8
9
10
              
             
              
 
                     

                         
                

    
                                      
 
                                         
                            
                               
 
                                                
                            
 

                                                     
 

































                                                                                              


















































                                                                                         
















                                                                                                       






































                                                                       
 


                                                                              
                                                                     

                                         
                                                                 

                     






                                                                  
                                               




                                                   
                                                        

                           

                                                 


                                       

                                                     
           


                                                 
import streams
import tables
import unicode

import css/mediaquery
import css/cssparser
import css/selectorparser
import html/tags

type
  CSSRuleBase* = ref object of RootObj

  CSSRuleDef* = ref object of CSSRuleBase
    sels*: seq[SelectorList]
    decls*: seq[CSSDeclaration]

  CSSConditionalDef* = ref object of CSSRuleBase
    children*: CSSStylesheet

  CSSMediaQueryDef* = ref object of CSSConditionalDef
    query*: MediaQueryList

  CSSStylesheet* = ref object
    mq_list*: seq[CSSMediaQueryDef]
    tag_table*: array[TagType, seq[CSSRuleDef]]
    id_table*: TableRef[string, seq[CSSRuleDef]]
    class_table*: TableRef[string, seq[CSSRuleDef]]
    general_list*: seq[CSSRuleDef]
    len*: int

type SelectorHashes = object
  tag: TagType
  id: string
  class: string

func newStylesheet*(cap: int): CSSStylesheet =
  new(result)
  let bucketsize = cap div 2
  result.id_table = newTable[string, seq[CSSRuleDef]](bucketsize)
  result.class_table = newTable[string, seq[CSSRuleDef]](bucketsize)
  result.general_list = newSeqOfCap[CSSRuleDef](bucketsize)

proc getSelectorIds(hashes: var SelectorHashes, sel: Selector): bool {.inline.} =
  case sel.t
  of TYPE_SELECTOR:
    hashes.tag = sel.tag
    return true
  of CLASS_SELECTOR:
    hashes.class = sel.class
    return true
  of ID_SELECTOR:
    hashes.id = sel.id
    return true
  of ATTR_SELECTOR, PSEUDO_SELECTOR, PSELEM_SELECTOR, UNIVERSAL_SELECTOR, COMBINATOR_SELECTOR:
    discard
  of FUNC_SELECTOR:
    if sel.name == "is":
      # Basically just hash whatever the selectors have in common:
      #1. get the hashable values of selector 1
      #2. for every other selector x:
      #3.   get hashable values of selector x
      #4.   store hashable values of selector x that aren't stored yet
      #5.   for every hashable value of selector 1 that doesn't match selector x
      #6.     cancel hashable value
      var cancel_tag = false
      var cancel_id = false
      var cancel_class = false
      var i = 0
      if i < sel.fsels.len:
        let list = sel.fsels[i]
        for sel in list.sels:
          if hashes.getSelectorIds(sel):
            break
        inc i

      while i < sel.fsels.len:
        let list = sel.fsels[i]
        var nhashes: SelectorHashes
        for sel in list.sels:
          if nhashes.getSelectorIds(sel):
            break
        if hashes.tag == TAG_UNKNOWN:
          hashes.tag = nhashes.tag
        elif not cancel_tag and nhashes.tag != TAG_UNKNOWN and nhashes.tag != hashes.tag:
          cancel_tag = true

        if hashes.id == "":
          hashes.id = nhashes.id
        elif not cancel_id and nhashes.id != "" and nhashes.id != hashes.id:
          cancel_id = true

        if hashes.class == "":
          hashes.class = nhashes.class
        elif not cancel_class and nhashes.class != "" and nhashes.class != hashes.class:
          cancel_class = true

        inc i

      if cancel_tag:
        hashes.tag = TAG_UNKNOWN
      if cancel_id:
        hashes.id = ""
      if cancel_class:
        hashes.class = ""

      if hashes.tag != TAG_UNKNOWN or hashes.id != "" or hashes.class != "":
        return true

iterator gen_rules*(sheet: CSSStylesheet, tag: TagType, id: string, classes: seq[string]): CSSRuleDef =
  for rule in sheet.tag_table[tag]:
    yield rule
  if id != "":
    if sheet.id_table.hasKey(id):
      for rule in sheet.id_table[id]:
        yield rule
  if classes.len > 0:
    for class in classes:
      if sheet.class_table.hasKey(class):
        for rule in sheet.class_table[class]:
          yield rule
  for rule in sheet.general_list:
    yield rule

proc add(sheet: var CSSStylesheet, rule: CSSRuleDef) =
  var hashes: SelectorHashes
  for list in rule.sels:
    for sel in list.sels:
      if hashes.getSelectorIds(sel):
        break

    if hashes.tag != TAG_UNKNOWN:
      sheet.tag_table[hashes.tag].add(rule)
    elif hashes.id != "":
      if hashes.id notin sheet.id_table:
        sheet.id_table[hashes.id] = newSeq[CSSRuleDef]()
      sheet.id_table[hashes.id].add(rule)
    elif hashes.class != "":
      if hashes.class notin sheet.class_table:
        sheet.class_table[hashes.class] = newSeq[CSSRuleDef]()
      sheet.class_table[hashes.class].add(rule)
    else:
      sheet.general_list.add(rule)

proc add*(sheet: var CSSStylesheet, rule: CSSRuleBase) {.inline.} =
  if rule of CSSRuleDef:
    sheet.add(CSSRuleDef(rule))
  else:
    sheet.mq_list.add(CSSMediaQueryDef(rule))
  inc sheet.len

proc add*(sheet: var CSSStylesheet, sheet2: CSSStylesheet) {.inline.} =
  sheet.general_list.add(sheet2.general_list)
  for tag in TagType:
    sheet.tag_table[tag].add(sheet2.tag_table[tag])
  for key, value in sheet2.id_table.pairs:
    if key notin sheet.id_table:
      sheet.id_table[key] = newSeq[CSSRuleDef]()
    sheet.id_table[key].add(value)
  for key, value in sheet2.class_table.pairs:
    if key notin sheet.class_table:
      sheet.class_table[key] = newSeq[CSSRuleDef]()
    sheet.class_table[key].add(value)
  sheet.len += sheet2.len

proc getDeclarations(rule: CSSQualifiedRule): seq[CSSDeclaration] {.inline.} =
  rule.oblock.value.parseListOfDeclarations2()

proc addRule(stylesheet: var CSSStylesheet, rule: CSSQualifiedRule) =
  let sels = parseSelectors(rule.prelude)
  if sels.len > 1 or sels[^1].len > 0:
    let r = CSSRuleDef(sels: sels, decls: rule.getDeclarations())
    stylesheet.add(r)

proc addAtRule(stylesheet: var CSSStylesheet, atrule: CSSAtRule) =
  case $atrule.name
  of "media":
    let query = parseMediaQueryList(atrule.prelude)
    let rules = atrule.oblock.value.parseListOfRules()
    if rules.len > 0:
      var media = CSSMediaQueryDef()
      media.children = newStylesheet(rules.len)
      media.query = query
      for rule in rules:
        if rule of CSSAtRule:
          media.children.addAtRule(CSSAtRule(rule))
        else:
          media.children.addRule(CSSQualifiedRule(rule))
      stylesheet.add(media)
  else: discard #TODO

proc parseStylesheet*(s: Stream): CSSStylesheet =
  let css = parseCSS(s)
  result = newStylesheet(css.value.len)
  for v in css.value:
    if v of CSSAtRule: result.addAtRule(CSSAtRule(v))
    else: result.addRule(CSSQualifiedRule(v))
  s.close()

proc parseStylesheet*(s: string): CSSStylesheet =
  return newStringStream(s).parseStylesheet()