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

    
                                      
 
                                         
                       

                                         


                                                                       
 
                                                
                            
 

                                                     
 
                             
                                  




                                              
            
                         

                            
            

              
             
 
                                                                     
                            
                       
                                                            

                                                              
                                                             

                                                     
   
 
                                                                    
 
                                                                         



                                  
                                                                         

                                  
                                                                      
            
            

                        
             

                            
          

                      


                                  
                

                                          

                                                                





                                                                               


                           
                          



                                                
                                   
                                    


                                                 
                                                                  
                        

                                
                                                               
                       

                                    
                                                                        
                          



                                                                     
           





                              

                             

                                                               
 
                                         

                   
                                                  
                               

                                




                                              
                                



                                            
                                   



                                                  
         
                                 
 
                                         
                                           




                                          









                                            




                                           
 
                                                                 
                                                             
                  









                                                           
                 

                                   
                         
      
                      
 
                                                              
                                           


                            



                                                      
                                                                   
                                         




                                                   
                                                        
                                  
                                         
 

                                                                           
                                                   

                      
                                   
         

                                        
import std/tables

import css/cssparser
import css/cssvalues
import css/mediaquery
import css/selectorparser
import html/catom
import utils/twtstr

type
  CSSRuleBase* = ref object of RootObj

  CSSRuleDef* = ref object of CSSRuleBase
    sels*: SelectorList
    normalVals*: seq[CSSComputedEntry]
    importantVals*: seq[CSSComputedEntry]
    # Absolute position in the stylesheet; used for sorting rules after
    # retrieval from the cache.
    idx: int

  CSSConditionalDef* = ref object of CSSRuleBase
    children*: CSSStylesheet

  CSSMediaQueryDef* = ref object of CSSConditionalDef
    query*: MediaQueryList

  CSSStylesheet* = ref object
    mqList*: seq[CSSMediaQueryDef]
    tagTable*: Table[CAtom, seq[CSSRuleDef]]
    idTable*: Table[CAtom, seq[CSSRuleDef]]
    classTable*: Table[CAtom, seq[CSSRuleDef]]
    attrTable*: Table[CAtom, seq[CSSRuleDef]]
    generalList*: seq[CSSRuleDef]
    len: int
    factory: CAtomFactory

type SelectorHashes = object
  tag: CAtom
  id: CAtom
  class: CAtom
  attr: CAtom

func newStylesheet*(cap: int; factory: CAtomFactory): CSSStylesheet =
  let bucketsize = cap div 2
  return CSSStylesheet(
    tagTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
    idTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
    classTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
    attrTable: initTable[CAtom, seq[CSSRuleDef]](bucketsize),
    generalList: newSeqOfCap[CSSRuleDef](bucketsize),
    factory: factory
  )

proc getSelectorIds(hashes: var SelectorHashes; sel: Selector): bool

proc getSelectorIds(hashes: var SelectorHashes; sels: CompoundSelector) =
  for sel in sels:
    if hashes.getSelectorIds(sel):
      break

proc getSelectorIds(hashes: var SelectorHashes; cxsel: ComplexSelector) =
  hashes.getSelectorIds(cxsel[^1])

proc getSelectorIds(hashes: var SelectorHashes; sel: Selector): bool =
  case sel.t
  of stType:
    hashes.tag = sel.tag
    return true
  of stClass:
    hashes.class = sel.class
    return true
  of stId:
    hashes.id = sel.id
    return true
  of stAttr:
    hashes.attr = sel.attr
  of stPseudoElement, stUniversal:
    return false
  of stPseudoClass:
    if sel.pseudo.t notin {pcIs, pcWhere}:
      return false
    # 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 cancelTag = false
    var cancelId = false
    var cancelClass = false
    var cancelAttr = false
    var i = 0
    if i < sel.pseudo.fsels.len:
      hashes.getSelectorIds(sel.pseudo.fsels[i])
      inc i
    while i < sel.pseudo.fsels.len:
      var nhashes = SelectorHashes()
      nhashes.getSelectorIds(sel.pseudo.fsels[i])
      if hashes.tag == CAtomNull:
        hashes.tag = nhashes.tag
      elif nhashes.tag != CAtomNull and nhashes.tag != hashes.tag:
        cancelTag = true
      if hashes.id == CAtomNull:
        hashes.id = nhashes.id
      elif nhashes.id != CAtomNull and nhashes.id != hashes.id:
        cancelId = true
      if hashes.class == CAtomNull:
        hashes.class = nhashes.class
      elif nhashes.class != CAtomNull and nhashes.class != hashes.class:
        cancelClass = true
      if hashes.attr == CAtomNull:
        hashes.attr = nhashes.attr
      elif nhashes.attr != CAtomNull and nhashes.attr != hashes.attr:
        cancelAttr = true
      inc i
    if cancelTag:
      hashes.tag = CAtomNull
    if cancelId:
      hashes.id = CAtomNull
    if cancelClass:
      hashes.class = CAtomNull
    if cancelAttr:
      hashes.attr = CAtomNull
    return hashes.tag != CAtomNull or hashes.id != CAtomNull or
      hashes.class != CAtomNull

proc ruleDefCmp*(a, b: CSSRuleDef): int =
  cmp(a.idx, b.idx)

proc add(sheet: CSSStylesheet; rule: CSSRuleDef) =
  var hashes = SelectorHashes()
  for cxsel in rule.sels:
    hashes.getSelectorIds(cxsel)
    if hashes.tag != CAtomNull:
      sheet.tagTable.withValue(hashes.tag, p):
        p[].add(rule)
      do:
        sheet.tagTable[hashes.tag] = @[rule]
    elif hashes.id != CAtomNull:
      sheet.idTable.withValue(hashes.id, p):
        p[].add(rule)
      do:
        sheet.idTable[hashes.id] = @[rule]
    elif hashes.class != CAtomNull:
      sheet.classTable.withValue(hashes.class, p):
        p[].add(rule)
      do:
        sheet.classTable[hashes.class] = @[rule]
    else:
      sheet.generalList.add(rule)

proc add*(sheet, sheet2: CSSStylesheet) =
  sheet.generalList.add(sheet2.generalList)
  for key, value in sheet2.tagTable.pairs:
    sheet.tagTable.withValue(key, p):
      p[].add(value)
    do:
      sheet.tagTable[key] = value
  for key, value in sheet2.idTable.pairs:
    sheet.idTable.withValue(key, p):
      p[].add(value)
    do:
      sheet.idTable[key] = value
  for key, value in sheet2.classTable.pairs:
    sheet.classTable.withValue(key, p):
      p[].add(value)
    do:
      sheet.classTable[key] = value
  for key, value in sheet2.attrTable.pairs:
    sheet.attrTable.withValue(key, p):
      p[].add(value)
    do:
      sheet.attrTable[key] = value

proc addRule(stylesheet: CSSStylesheet; rule: CSSQualifiedRule) =
  let sels = parseSelectors(rule.prelude, stylesheet.factory)
  if sels.len > 0:
    var normalVals: seq[CSSComputedEntry] = @[]
    var importantVals: seq[CSSComputedEntry] = @[]
    let decls = rule.oblock.value.parseDeclarations2()
    for decl in decls:
      let vals = parseComputedValues(decl.name, decl.value)
      if decl.important:
        importantVals.add(vals)
      else:
        normalVals.add(vals)
    stylesheet.add(CSSRuleDef(
      sels: sels,
      normalVals: normalVals,
      importantVals: importantVals,
      idx: stylesheet.len
    ))
    inc stylesheet.len

proc addAtRule(stylesheet: CSSStylesheet; atrule: CSSAtRule) =
  if atrule.name.equalsIgnoreCase("media"):
    if atrule.oblock == nil:
      # invalid at-rule
      return
    let query = parseMediaQueryList(atrule.prelude)
    let rules = atrule.oblock.value.parseListOfRules()
    if rules.len > 0:
      var media = CSSMediaQueryDef()
      media.children = newStylesheet(rules.len, stylesheet.factory)
      media.children.len = stylesheet.len
      media.query = query
      for rule in rules:
        if rule of CSSAtRule:
          media.children.addAtRule(CSSAtRule(rule))
        else:
          media.children.addRule(CSSQualifiedRule(rule))
      stylesheet.mqList.add(media)
      stylesheet.len = media.children.len

proc parseStylesheet*(ibuf: string; factory: CAtomFactory): CSSStylesheet =
  let raw = parseStylesheet(ibuf)
  let sheet = newStylesheet(raw.value.len, factory)
  for v in raw.value:
    if v of CSSAtRule:
      sheet.addAtRule(CSSAtRule(v))
    else:
      sheet.addRule(CSSQualifiedRule(v))
  return sheet