about summary refs log tree commit diff stats
path: root/src/html
diff options
context:
space:
mode:
Diffstat (limited to 'src/html')
-rw-r--r--src/html/dom.nim128
-rw-r--r--src/html/htmlparser.nim68
2 files changed, 77 insertions, 119 deletions
diff --git a/src/html/dom.nim b/src/html/dom.nim
index 74ebe5ea..1a393134 100644
--- a/src/html/dom.nim
+++ b/src/html/dom.nim
@@ -6,13 +6,13 @@ import tables
 import streams
 import sequtils
 import sugar
+import algorithm
 
 import ../css/style
 import ../css/cssparser
 import ../css/selector
 
 import ../types/enums
-import ../types/tagtypes
 
 import ../utils/twtstr
 
@@ -46,6 +46,7 @@ type
     width*: int
     height*: int
     hidden*: bool
+    box*: CSSBox
 
   Attr* = ref AttrObj
   AttrObj = object of NodeObj
@@ -89,7 +90,8 @@ type
     id*: string
     classList*: seq[string]
     attributes*: Table[string, Attr]
-    box*: CSSBox
+    style*: CSS2Properties
+    cssvalues*: seq[CSSComputedValue]
 
   HTMLElement* = ref HTMLElementObj
   HTMLElementObj = object of ElementObj
@@ -159,8 +161,8 @@ func nodeAttr*(node: Node): HtmlElement =
 
 func getStyle*(node: Node): CSS2Properties =
   case node.nodeType
-  of TEXT_NODE: return node.parentElement.box.props
-  of ELEMENT_NODE: return Element(node).box.props
+  of TEXT_NODE: return node.parentElement.style
+  of ELEMENT_NODE: return Element(node).style
   else: assert(false)
 
 func displayed*(node: Node): bool =
@@ -260,11 +262,11 @@ proc getRawText*(htmlNode: Node): string =
     #eprint "char data", chardata.data
     if htmlNode.parentElement != nil and htmlNode.parentElement.tagType != TAG_PRE:
       result = chardata.data.remove("\n")
-      if unicode.strip(result).runeLen() > 0:
-        if htmlNode.getStyle().display != DISPLAY_INLINE:
-          result = unicode.strip(result)
-      else:
-        result = ""
+      #if unicode.strip(result).runeLen() > 0:
+      #  if htmlNode.getStyle().display != DISPLAY_INLINE:
+      #    result = unicode.strip(result)
+      #else:
+      #  result = ""
     else:
       result = unicode.strip(chardata.data)
     if htmlNode.parentElement != nil and htmlNode.parentElement.tagType == TAG_OPTION:
@@ -290,7 +292,7 @@ func getFmtText*(node: Node): seq[string] =
 
       if style.bold:
         result = result.ansiStyle(styleBright).ansiReset()
-      if style.italic:
+      if style.fontStyle == FONTSTYLE_ITALIC or style.fontStyle == FONTSTYLE_OBLIQUE:
         result = result.ansiStyle(styleItalic).ansiReset()
       if style.underscore:
         result = result.ansiStyle(styleUnderscore).ansiReset()
@@ -307,10 +309,6 @@ func newComment*(): Comment =
   new(result)
   result.nodeType = COMMENT_NODE
 
-func newBox*(element: HTMLElement): CSSBox =
-  new(result)
-  result.props = CSS2Properties()
-
 func newHtmlElement*(tagType: TagType): HTMLElement =
   case tagType
   of TAG_INPUT:
@@ -332,7 +330,7 @@ func newHtmlElement*(tagType: TagType): HTMLElement =
 
   result.nodeType = ELEMENT_NODE
   result.tagType = tagType
-  result.box = result.newBox()
+  result.style = CSS2Properties()
 
 func newDocument*(): Document =
   new(result)
@@ -414,7 +412,6 @@ func selectElems(document: Document, sel: Selector): seq[Element] =
     return document.class_elements[sel.class]
   of UNIVERSAL_SELECTOR:
     return document.all_elements
-  #TODO: following selectors are rather inefficient
   of ATTR_SELECTOR:
     return document.all_elements.filter((elem) => attrSelectorMatches(elem, sel))
   of PSEUDO_SELECTOR:
@@ -422,38 +419,13 @@ func selectElems(document: Document, sel: Selector): seq[Element] =
   of PSELEM_SELECTOR:
     return document.all_elements.filter((elem) => pseudoElemSelectorMatches(elem, sel))
   of FUNC_SELECTOR:
-    if sel.name == "not":
+    case sel.name
+    of "not":
       return document.all_elements.filter((elem) => not selectorsMatch(elem, sel.selectors))
+    of "is", "where":
+      return document.all_elements.filter((elem) => selectorsMatch(elem, sel.selectors))
     return newSeq[Element]()
 
-func optimizeSelectorList(selectors: SelectorList): SelectorList =
-  new(result)
-  #pass 1: check for invalid sequences
-  var i = 1
-  while i < selectors.len:
-    let sel = selectors[i]
-    if sel.t == TYPE_SELECTOR or sel.t == UNIVERSAL_SELECTOR:
-      return SelectorList()
-    inc i
-
-  #pass 2: move selectors in combination
-  if selectors.len > 1:
-    var i = 0
-    var slow = SelectorList()
-    if selectors[0].t == UNIVERSAL_SELECTOR:
-      inc i
-
-    while i < selectors.len:
-      if selectors[i].t in {ATTR_SELECTOR, PSEUDO_SELECTOR, PSELEM_SELECTOR}:
-        slow.add(selectors[i])
-      else:
-        result.add(selectors[i])
-      inc i
-
-    result.add(slow)
-  else:
-    result.add(selectors[0])
-
 func selectElems(document: Document, selectors: SelectorList): seq[Element] =
   assert(selectors.len > 0)
   let sellist = optimizeSelectorList(selectors)
@@ -462,8 +434,12 @@ func selectElems(document: Document, selectors: SelectorList): seq[Element] =
 
   while i < sellist.len:
     if sellist[i].t == FUNC_SELECTOR:
-      if sellist[i].name == "not":
+      case sellist[i].name
+      of "not":
         result = result.filter((elem) => not selectorsMatch(elem, sellist[i].selectors))
+      of "is", "where":
+        result = result.filter((elem) => selectorsMatch(elem, sellist[i].selectors))
+      else: discard
     else:
       result = result.filter((elem) => selectorMatches(elem, sellist[i]))
     inc i
@@ -476,17 +452,65 @@ proc querySelector*(document: Document, q: string): seq[Element] =
   for sel in selectors:
     result.add(document.selectElems(sel))
 
-proc applyRule(elem: Element, rule: CSSRule) =
-  let selectors = parseSelectors(rule.prelude)
-  for sel in selectors:
-    if elem.selectorsMatch(sel):
-      eprint "match!"
+func calcRules(elem: Element, rules: CSSStylesheet): seq[CSSSimpleBlock] =
+  var tosort: seq[tuple[s:int,b:CSSSimpleBlock]]
+  for rule in rules.value:
+    let selectors = parseSelectors(rule.prelude) #TODO perf: compute this once
+    for sel in selectors:
+      if elem.selectorsMatch(sel):
+        let spec = getSpecificity(sel)
+        tosort.add((spec,rule.oblock))
 
-proc applyRules(document: Document, rules: CSSStylesheet) =
+  tosort.sort((x, y) => cmp(x.s,y.s))
+  return tosort.map((x) => x.b)
+
+proc applyRules*(document: Document, rules: CSSStylesheet): seq[tuple[e:Element,d:CSSDeclaration]] =
+  var stack: seq[Element]
+
+  stack.add(document.firstElementChild)
+  while stack.len > 0:
+    let elem = stack.pop()
+    for oblock in calcRules(elem, rules):
+      let decls = parseCSSListOfDeclarations(oblock.value)
+      for item in decls:
+        if item of CSSDeclaration:
+          if ((CSSDeclaration)item).important:
+            result.add((elem, (CSSDeclaration)item))
+          else:
+            elem.style.applyProperty((CSSDeclaration)item)
+
+    for child in elem.children:
+      stack.add(child)
+
+proc addBoxes*(elem: Element) =
+  var b = false
+  for child in elem.childNodes:
+    if child.nodeType == ELEMENT_NODE:
+      if ((Element)child).style.display == DISPLAY_BLOCK:
+        b = true
+        break
+  for child in elem.childNodes:
+    if child.nodeType == ELEMENT_NODE:
+      if b:
+        child.box = CSSBox(display: DISPLAY_BLOCK)
+      else:
+        child.box = CSSBox()
+
+proc generateBoxModel(document: Document) =
   var stack: seq[Element]
 
   stack.add(document.firstElementChild)
+  document.firstElementChild.box = CSSBox()
   while stack.len > 0:
     let elem = stack.pop()
+    elem.addBoxes()
     for child in elem.children:
       stack.add(child)
+
+proc applyDefaultStylesheet*(document: Document) =
+  let important = document.applyRules(stylesheet)
+  for rule in important:
+    rule.e.style.applyProperty(rule.d)
+    rule.e.cssvalues.add(getComputedValue(rule.d))
+
+  document.generateBoxModel()
diff --git a/src/html/htmlparser.nim b/src/html/htmlparser.nim
index f43bcf40..3cfc1d8d 100644
--- a/src/html/htmlparser.nim
+++ b/src/html/htmlparser.nim
@@ -31,71 +31,6 @@ type
     parentNode: Node
     textNode: Text
 
-#func newHtmlElement(tagType: TagType, parentNode: Node): HtmlElement =
-#  case tagType
-#  of TAG_INPUT: result = new(HtmlInputElement)
-#  of TAG_A: result = new(HtmlAnchorElement)
-#  of TAG_SELECT: result = new(HtmlSelectElement)
-#  of TAG_OPTION: result = new(HtmlOptionElement)
-#  else: result = new(HtmlElement)
-#
-#  result.nodeType = ELEMENT_NODE
-#  result.tagType = tagType
-#  result.parentNode = parentNode
-#  if parentNode.isElemNode():
-#    result.parentElement = HtmlElement(parentNode)
-#
-#  if tagType in DisplayInlineTags:
-#    result.display = DISPLAY_INLINE
-#  elif tagType in DisplayBlockTags:
-#    result.display = DISPLAY_BLOCK
-#  elif tagType in DisplayInlineBlockTags:
-#    result.display = DISPLAY_INLINE_BLOCK
-#  elif tagType == TAG_LI:
-#    result.display = DISPLAY_LIST_ITEM
-#  else:
-#    result.display = DISPLAY_NONE
-#
-#  case tagType
-#  of TAG_CENTER:
-#    result.centered = true
-#  of TAG_B:
-#    result.bold = true
-#  of TAG_I:
-#    result.italic = true
-#  of TAG_U:
-#    result.underscore = true
-#  of TAG_HEAD:
-#    result.hidden = true
-#  of TAG_STYLE:
-#    result.hidden = true
-#  of TAG_SCRIPT:
-#    result.hidden = true
-#  of TAG_OPTION:
-#    result.hidden = true #TODO
-#  of TAG_PRE, TAG_TD, TAG_TH:
-#    result.margin = 1
-#  of TAG_UL, TAG_OL:
-#    result.indent = 2
-#    result.margin = 1
-#  of TAG_H1, TAG_H2, TAG_H3, TAG_H4, TAG_H5, TAG_H6:
-#    result.bold = true
-#    result.margin = 1
-#  of TAG_A:
-#    result.islink = true
-#  of TAG_INPUT:
-#    HtmlInputElement(result).size = 20
-#  else: discard
-#
-#  if parentNode.isElemNode():
-#    let parent = HtmlElement(parentNode)
-#    result.centered = result.centered or parent.centered
-#    result.bold = result.bold or parent.bold
-#    result.italic = result.italic or parent.italic
-#    result.underscore = result.underscore or parent.underscore
-#    result.hidden = result.hidden or parent.hidden
-#    result.islink = result.islink or parent.islink
-
 func inputSize*(str: string): int =
   if str.len == 0:
     return 20
@@ -104,7 +39,7 @@ func inputSize*(str: string): int =
       return 20
   return str.parseInt()
 
-#w3m's getescapecmd and parse_tag, transpiled to nim.
+#w3m's getescapecmd and parse_tag, transpiled to nim and heavily modified.
 #(C) Copyright 1994-2002 by Akinori Ito
 #(C) Copyright 2002-2011 by Akinori Ito, Hironori Sakamoto, Fumitoshi Ukai
 #
@@ -153,7 +88,6 @@ proc getescapecmd(buf: string, at: var int): string =
   elif not isAlphaAscii(buf[i]):
     return ""
 
-  #TODO this could be way more efficient (and radixnode needs better interface)
   when defined(small):
     var n = entityMap
     var s = ""