import algorithm import streams import sugar import css/mediaquery import css/cssparser import css/select import css/selectorparser import css/sheet import css/values import html/dom type ApplyResult = object normal: seq[CSSDeclaration] important: seq[CSSDeclaration] DeclarationList* = array[PseudoElem, seq[CSSDeclaration]] proc applyProperty(elem: Element, d: CSSDeclaration, pseudo: PseudoElem) = var parent: CSSSpecifiedValues if elem.parentElement != nil: parent = elem.parentElement.css else: parent = rootProperties() if pseudo == PSEUDO_NONE: elem.css.applyValue(parent, d) else: if elem.pseudo[pseudo] == nil: elem.pseudo[pseudo] = elem.css.inheritProperties() elem.pseudo[pseudo].applyValue(elem.css, d) elem.cssapplied = true func applies(mq: MediaQuery): bool = case mq.t of CONDITION_MEDIA: case mq.media of MEDIA_TYPE_ALL: return true of MEDIA_TYPE_PRINT: return false of MEDIA_TYPE_SCREEN: return true of MEDIA_TYPE_SPEECH: return false of MEDIA_TYPE_TTY: return true of MEDIA_TYPE_UNKNOWN: return false of CONDITION_NOT: return not mq.n.applies() of CONDITION_AND: return mq.anda.applies() and mq.andb.applies() of CONDITION_OR: return mq.ora.applies() or mq.orb.applies() of CONDITION_FEATURE: case mq.feature.t of FEATURE_COLOR: return true #TODO of FEATURE_GRID: return mq.feature.b of FEATURE_HOVER: return mq.feature.b of FEATURE_PREFERS_COLOR_SCHEME: return mq.feature.b func applies(mqlist: MediaQueryList): bool = for mq in mqlist: if mq.applies(): return true return false type ToSorts = array[PseudoElem, seq[(int, seq[CSSDeclaration])]] proc calcRule(tosorts: var ToSorts, elem: Element, rule: CSSRuleDef) = for sel in rule.sels: if elem.selectorsMatch(sel): let spec = getSpecificity(sel) tosorts[sel.pseudo].add((spec,rule.decls)) func calcRules(elem: Element, sheet: CSSStylesheet): DeclarationList = var tosorts: ToSorts for rule in sheet.gen_rules(elem.tagType, elem.id, elem.classList): tosorts.calcRule(elem, rule) for i in PseudoElem: tosorts[i].sort((x, y) => cmp(x[0], y[0])) result[i] = collect(newSeq): for item in tosorts[i]: for dl in item[1]: dl #TODO couldn't these two procedures be merged? proc applyNormal(ares: var ApplyResult, decls: seq[CSSDeclaration]) = for decl in decls: if not decl.important: ares.normal.add(decl) proc applyImportant(ares: var ApplyResult, decls: seq[CSSDeclaration]) = for decl in decls: if decl.important: ares.important.add(decl) proc checkRendered(element: Element, prev: CSSSpecifiedValues, ppseudo: array[PSEUDO_BEFORE..PSEUDO_AFTER, CSSSpecifiedValues]) = if element.rendered: for p in PSEUDO_BEFORE..PSEUDO_AFTER: if ppseudo[p] != element.pseudo[p] and ppseudo[p] == nil: if element.parentElement != nil: element.parentElement.rendered = false element.rendered = false return for t in CSSPropertyType: if not element.css[t].equals(prev[t]): if element.parentElement != nil: element.parentElement.rendered = false element.rendered = false return for p in PSEUDO_BEFORE..PSEUDO_AFTER: if ppseudo[p] != nil: for t in CSSPropertyType: if not element.pseudo[p][t].equals(ppseudo[p][t]): element.rendered = false return proc applyDeclarations(element: Element, ua, user: DeclarationList, author: seq[DeclarationList], pseudo: PseudoElem) = var ares: ApplyResult ares.applyNormal(ua[pseudo]) ares.applyNormal(user[pseudo]) for rule in author: ares.applyNormal(rule[pseudo]) for rule in author: ares.applyImportant(rule[pseudo]) if pseudo == PSEUDO_NONE: let style = element.attr("style") if style.len > 0: let inline_rules = newStringStream(style).parseListOfDeclarations2() ares.applyNormal(inline_rules) ares.applyImportant(inline_rules) ares.applyImportant(user[pseudo]) ares.applyImportant(ua[pseudo]) for rule in ares.normal: element.applyProperty(rule, pseudo) for rule in ares.important: element.applyProperty(rule, pseudo) func applyMediaQuery(ss: CSSStylesheet): CSSStylesheet = result = ss for mq in ss.mq_list: if mq.query.applies(): result.add(mq.children.applyMediaQuery()) proc resetRules(elem: Element) = elem.css = if elem.parentElement != nil: elem.parentElement.css.inheritProperties() else: rootProperties() for pseudo in PSEUDO_BEFORE..PSEUDO_AFTER: elem.pseudo[pseudo] = nil proc applyRules(elem: Element, ua, user: CSSStylesheet, author: seq[CSSStylesheet]) {.inline.} = let uadecls = calcRules(elem, ua) let userdecls = calcRules(elem, user) var authordecls: seq[DeclarationList] for rule in author: authordecls.add(calcRules(elem, rule)) for pseudo in PseudoElem: elem.applyDeclarations(uadecls, userdecls, authordecls, pseudo) proc applyRules(document: Document, ua, user: CSSStylesheet) = var author: seq[CSSStylesheet] for sheet in document.head.sheets: author.add(sheet) var stack: seq[Element] stack.add(document.root) var lenstack = newSeqOfCap[int](15) while stack.len > 0: let elem = stack.pop() # Remove stylesheets on nil if elem == nil: let len = lenstack.pop() author.setLen(author.len - len) continue if not elem.cssapplied: let prev = elem.css let ppseudo = elem.pseudo elem.resetRules() elem.applyRules(ua, user, author) elem.checkRendered(prev, ppseudo) # Add nil before the last element (in-stack), so we can remove the # stylesheets if elem.sheets.len > 0: author.add(elem.sheets) lenstack.add(elem.sheets.len) stack.add(nil) for i in countdown(elem.children.high, 0): stack.add(elem.children[i]) proc applyStylesheets*(document: Document, uass, userss: CSSStylesheet) = let uass = uass.applyMediaQuery() let userss = userss.applyMediaQuery() document.applyRules(uass, userss) proc refreshStyle*(elem: Element) = elem.cssapplied = false for child in elem.children: child.refreshStyle()