import std/algorithm
import std/streams
import std/tables
import css/cssparser
import css/mediaquery
import css/selectorparser
import html/catom
type
CSSRuleBase* = ref object of RootObj
CSSRuleDef* = ref object of CSSRuleBase
sels*: SelectorList
decls*: seq[CSSDeclaration]
# 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]]
generalList: seq[CSSRuleDef]
len: int
factory: CAtomFactory
type SelectorHashes = object
tag: CAtom
id: CAtom
class: 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),
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 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, PSELEM_SELECTOR, UNIVERSAL_SELECTOR:
return false
of PSEUDO_SELECTOR:
if sel.pseudo.t notin {PSEUDO_IS, PSEUDO_WHERE}:
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 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 not cancelTag and nhashes.tag != CAtomNull and
nhashes.tag != hashes.tag:
cancelTag = true
if hashes.id == CAtomNull:
hashes.id = nhashes.id
elif not cancelId and nhashes.id != CAtomNull and
nhashes.id != hashes.id:
cancelId = true
if hashes.class == CAtomNull:
hashes.class = nhashes.class
elif not cancelClass and nhashes.class != CAtomNull and
nhashes.class != hashes.class:
cancelClass = true
inc i
if cancelTag:
hashes.tag = CAtomNull
if cancelId:
hashes.id = CAtomNull
if cancelClass:
hashes.class = CAtomNull
return hashes.tag != CAtomNull or hashes.id != CAtomNull or
hashes.class != CAtomNull
proc ruleDefCmp(a, b: CSSRuleDef): int =
cmp(a.idx, b.idx)
iterator genRules*(sheet: CSSStylesheet, tag, id: CAtom, classes: seq[CAtom]):
CSSRuleDef =
var rules: seq[CSSRuleDef]
sheet.tagTable.withValue(tag, v):
for rule in v[]:
rules.add(rule)
if id != CAtomNull:
sheet.idTable.withValue(id, v):
for rule in v[]:
rules.add(rule)
for class in classes:
sheet.classTable.withValue(class, v):
for rule in v[]:
rules.add(rule)
for rule in sheet.generalList:
rules.add(rule)
rules.sort(ruleDefCmp, order = Ascending)
for rule in rules:
yield rule
proc add(sheet: var 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: var CSSStylesheet, 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
proc addRule(stylesheet: var CSSStylesheet, rule: CSSQualifiedRule) =
let sels = parseSelectors(rule.prelude, stylesheet.factory)
if sels.len > 0:
let r = CSSRuleDef(
sels: sels,
decls: rule.oblock.value.parseListOfDeclarations2(),
idx: stylesheet.len
)
stylesheet.add(r)
inc stylesheet.len
proc addAtRule(stylesheet: var CSSStylesheet, atrule: CSSAtRule) =
case atrule.name
of "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
else: discard #TODO
proc parseStylesheet*(s: Stream, factory: CAtomFactory): CSSStylesheet =
let css = parseCSS(s)
result = newStylesheet(css.value.len, factory)
for v in css.value:
if v of CSSAtRule: result.addAtRule(CSSAtRule(v))
else: result.addRule(CSSQualifiedRule(v))
s.close()
proc parseStylesheet*(s: string, factory: CAtomFactory): CSSStylesheet =
return newStringStream(s).parseStylesheet(factory)