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()