dochack.nim 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. import dom
  2. import fuzzysearch
  3. proc textContent(e: Element): cstring {.
  4. importcpp: "#.textContent", nodecl.}
  5. proc textContent(e: Node): cstring {.
  6. importcpp: "#.textContent", nodecl.}
  7. proc tree(tag: string; kids: varargs[Element]): Element =
  8. result = document.createElement tag
  9. for k in kids:
  10. result.appendChild k
  11. proc add(parent, kid: Element) =
  12. if parent.nodeName == cstring"TR" and (
  13. kid.nodeName == cstring"TD" or kid.nodeName == cstring"TH"):
  14. let k = document.createElement("TD")
  15. appendChild(k, kid)
  16. appendChild(parent, k)
  17. else:
  18. appendChild(parent, kid)
  19. proc setClass(e: Element; value: string) =
  20. e.setAttribute("class", value)
  21. proc text(s: string): Element = cast[Element](document.createTextNode(s))
  22. proc text(s: cstring): Element = cast[Element](document.createTextNode(s))
  23. proc getElementById(id: cstring): Element {.importc: "document.getElementById", nodecl.}
  24. proc replaceById(id: cstring; newTree: Node) =
  25. let x = getElementById(id)
  26. x.parentNode.replaceChild(newTree, x)
  27. newTree.id = id
  28. proc findNodeWith(x: Element; tag, content: cstring): Element =
  29. if x.nodeName == tag and x.textContent == content:
  30. return x
  31. for i in 0..<x.len:
  32. let it = x[i]
  33. let y = findNodeWith(it, tag, content)
  34. if y != nil: return y
  35. return nil
  36. proc clone(e: Element): Element {.importcpp: "#.cloneNode(true)", nodecl.}
  37. proc parent(e: Element): Element {.importcpp: "#.parentNode", nodecl.}
  38. proc markElement(x: Element) {.importcpp: "#.__karaxMarker__ = true", nodecl.}
  39. proc isMarked(x: Element): bool {.
  40. importcpp: "#.hasOwnProperty('__karaxMarker__')", nodecl.}
  41. proc title(x: Element): cstring {.importcpp: "#.title", nodecl.}
  42. proc sort[T](x: var openArray[T]; cmp: proc(a, b: T): int) {.importcpp:
  43. "#.sort(#)", nodecl.}
  44. proc parentWith(x: Element; tag: cstring): Element =
  45. result = x.parent
  46. while result.nodeName != tag:
  47. result = result.parent
  48. if result == nil: return nil
  49. proc extractItems(x: Element; items: var seq[Element]) =
  50. if x == nil: return
  51. if x.nodeName == cstring"A":
  52. items.add x
  53. else:
  54. for i in 0..<x.len:
  55. let it = x[i]
  56. extractItems(it, items)
  57. # HTML trees are so shitty we transform the TOC into a decent
  58. # data-structure instead and work on that.
  59. type
  60. TocEntry = ref object
  61. heading: Element
  62. kids: seq[TocEntry]
  63. sortId: int
  64. doSort: bool
  65. proc extractItems(x: TocEntry; heading: cstring;
  66. items: var seq[Element]) =
  67. if x == nil: return
  68. if x.heading != nil and x.heading.textContent == heading:
  69. for i in 0..<x.kids.len:
  70. items.add x.kids[i].heading
  71. else:
  72. for i in 0..<x.kids.len:
  73. let it = x.kids[i]
  74. extractItems(it, heading, items)
  75. proc toHtml(x: TocEntry; isRoot=false): Element =
  76. if x == nil: return nil
  77. if x.kids.len == 0:
  78. if x.heading == nil: return nil
  79. return x.heading.clone
  80. result = tree("DIV")
  81. if x.heading != nil and not isMarked(x.heading):
  82. result.add x.heading.clone
  83. let ul = tree("UL")
  84. if isRoot:
  85. ul.setClass("simple simple-toc")
  86. else:
  87. ul.setClass("simple")
  88. if x.dosort:
  89. x.kids.sort(proc(a, b: TocEntry): int =
  90. if a.heading != nil and b.heading != nil:
  91. let x = a.heading.textContent
  92. let y = b.heading.textContent
  93. if x < y: return -1
  94. if x > y: return 1
  95. return 0
  96. else:
  97. # ensure sorting is stable:
  98. return a.sortId - b.sortId
  99. )
  100. for k in x.kids:
  101. let y = toHtml(k)
  102. if y != nil:
  103. ul.add tree("LI", y)
  104. if ul.len != 0: result.add ul
  105. if result.len == 0: result = nil
  106. #proc containsWord(a, b: cstring): bool {.asmNoStackFrame.} =
  107. #{.emit: """
  108. #var escaped = `b`.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
  109. #return new RegExp("\\b" + escaped + "\\b").test(`a`);
  110. #""".}
  111. proc isWhitespace(text: cstring): bool {.asmNoStackFrame.} =
  112. {.emit: """
  113. return !/[^\s]/.test(`text`);
  114. """.}
  115. proc isWhitespace(x: Element): bool =
  116. x.nodeName == cstring"#text" and x.textContent.isWhitespace or
  117. x.nodeName == cstring"#comment"
  118. proc toToc(x: Element; father: TocEntry) =
  119. if x.nodeName == cstring"UL":
  120. let f = TocEntry(heading: nil, kids: @[], sortId: father.kids.len)
  121. var i = 0
  122. while i < x.len:
  123. var nxt = i+1
  124. while nxt < x.len and x[nxt].isWhitespace:
  125. inc nxt
  126. if nxt < x.len and x[i].nodeName == cstring"LI" and x[i].len == 1 and
  127. x[nxt].nodeName == cstring"UL":
  128. let e = TocEntry(heading: x[i][0], kids: @[], sortId: f.kids.len)
  129. let it = x[nxt]
  130. for j in 0..<it.len:
  131. toToc(it[j], e)
  132. f.kids.add e
  133. i = nxt+1
  134. else:
  135. toToc(x[i], f)
  136. inc i
  137. father.kids.add f
  138. elif isWhitespace(x):
  139. discard
  140. elif x.nodeName == cstring"LI":
  141. var idx: seq[int] = @[]
  142. for i in 0 ..< x.len:
  143. if not x[i].isWhitespace: idx.add i
  144. if idx.len == 2 and x[idx[1]].nodeName == cstring"UL":
  145. let e = TocEntry(heading: x[idx[0]], kids: @[],
  146. sortId: father.kids.len)
  147. let it = x[idx[1]]
  148. for j in 0..<it.len:
  149. toToc(it[j], e)
  150. father.kids.add e
  151. else:
  152. for i in 0..<x.len:
  153. toToc(x[i], father)
  154. else:
  155. father.kids.add TocEntry(heading: x, kids: @[],
  156. sortId: father.kids.len)
  157. proc tocul(x: Element): Element =
  158. # x is a 'ul' element
  159. result = tree("UL")
  160. for i in 0..<x.len:
  161. let it = x[i]
  162. if it.nodeName == cstring"LI":
  163. result.add it.clone
  164. elif it.nodeName == cstring"UL":
  165. result.add tocul(it)
  166. proc getSection(toc: Element; name: cstring): Element =
  167. let sec = findNodeWith(toc, "A", name)
  168. if sec != nil:
  169. result = sec.parentWith("LI")
  170. proc uncovered(x: TocEntry): TocEntry =
  171. if x.kids.len == 0 and x.heading != nil:
  172. return if not isMarked(x.heading): x else: nil
  173. result = TocEntry(heading: x.heading, kids: @[], sortId: x.sortId,
  174. doSort: x.doSort)
  175. for i in 0..<x.kids.len:
  176. let y = uncovered(x.kids[i])
  177. if y != nil: result.kids.add y
  178. if result.kids.len == 0: result = nil
  179. proc mergeTocs(orig, news: TocEntry): TocEntry =
  180. result = uncovered(orig)
  181. if result == nil:
  182. result = news
  183. else:
  184. for i in 0..<news.kids.len:
  185. result.kids.add news.kids[i]
  186. proc buildToc(orig: TocEntry; types, procs: seq[Element]): TocEntry =
  187. var newStuff = TocEntry(heading: nil, kids: @[], doSort: true)
  188. for t in types:
  189. let c = TocEntry(heading: t.clone, kids: @[], doSort: true)
  190. t.markElement()
  191. for p in procs:
  192. if not isMarked(p):
  193. let xx = getElementsByClass(p.parent, cstring"attachedType")
  194. if xx.len == 1 and xx[0].textContent == t.textContent:
  195. #kout(cstring"found ", p.nodeName)
  196. let q = tree("A", text(p.title))
  197. q.setAttr("href", p.getAttribute("href"))
  198. c.kids.add TocEntry(heading: q, kids: @[])
  199. p.markElement()
  200. newStuff.kids.add c
  201. result = mergeTocs(orig, newStuff)
  202. var alternative: Element
  203. proc togglevis(d: Element) =
  204. asm """
  205. if (`d`.style.display == 'none')
  206. `d`.style.display = 'inline';
  207. else
  208. `d`.style.display = 'none';
  209. """
  210. proc groupBy*(value: cstring) {.exportc.} =
  211. let toc = getElementById("toc-list")
  212. if alternative.isNil:
  213. var tt = TocEntry(heading: nil, kids: @[])
  214. toToc(toc, tt)
  215. tt = tt.kids[0]
  216. var types: seq[Element] = @[]
  217. var procs: seq[Element] = @[]
  218. extractItems(tt, "Types", types)
  219. extractItems(tt, "Procs", procs)
  220. extractItems(tt, "Converters", procs)
  221. extractItems(tt, "Methods", procs)
  222. extractItems(tt, "Templates", procs)
  223. extractItems(tt, "Macros", procs)
  224. extractItems(tt, "Iterators", procs)
  225. let ntoc = buildToc(tt, types, procs)
  226. let x = toHtml(ntoc, isRoot=true)
  227. alternative = tree("DIV", x)
  228. if value == cstring"type":
  229. replaceById("tocRoot", alternative)
  230. else:
  231. replaceById("tocRoot", tree("DIV"))
  232. togglevis(getElementById"toc-list")
  233. var
  234. db: seq[Node]
  235. contents: seq[cstring]
  236. template normalize(x: cstring): cstring = x.toLower.replace("_", "")
  237. proc escapeCString(x: var cstring) =
  238. var s = ""
  239. for c in x:
  240. case c
  241. of '&': s.add("&amp;")
  242. of '<': s.add("&lt;")
  243. of '>': s.add("&gt;")
  244. of '"': s.add("&quot;")
  245. of '\'': s.add("&#039;")
  246. of '/': s.add("&#x2F;")
  247. else: s.add(c)
  248. x = s.cstring
  249. proc dosearch(value: cstring): Element =
  250. if db.len == 0:
  251. var stuff: Element
  252. {.emit: """
  253. var request = new XMLHttpRequest();
  254. request.open("GET", "theindex.html", false);
  255. request.send(null);
  256. var doc = document.implementation.createHTMLDocument("theindex");
  257. doc.documentElement.innerHTML = request.responseText;
  258. //parser=new DOMParser();
  259. //doc=parser.parseFromString("<html></html>", "text/html");
  260. `stuff` = doc.documentElement;
  261. """.}
  262. db = stuff.getElementsByClass"reference"
  263. contents = @[]
  264. for ahref in db:
  265. contents.add ahref.getAttribute("data-doc-search-tag")
  266. let ul = tree("UL")
  267. result = tree("DIV")
  268. result.setClass"search_results"
  269. var matches: seq[(Node, int)] = @[]
  270. for i in 0..<db.len:
  271. let c = contents[i]
  272. if c == cstring"Examples" or c == cstring"PEG construction":
  273. # Some manual exclusions.
  274. # Ideally these should be fixed in the index to be more
  275. # descriptive of what they are.
  276. continue
  277. let (score, matched) = fuzzymatch(value, c)
  278. if matched:
  279. matches.add((db[i], score))
  280. matches.sort(proc(a, b: auto): int = b[1] - a[1])
  281. for i in 0 ..< min(matches.len, 29):
  282. matches[i][0].innerHTML = matches[i][0].getAttribute("data-doc-search-tag")
  283. escapeCString(matches[i][0].innerHTML)
  284. ul.add(tree("LI", cast[Element](matches[i][0])))
  285. if ul.len == 0:
  286. result.add tree("B", text"no search results")
  287. else:
  288. result.add tree("B", text"search results")
  289. result.add ul
  290. var oldtoc: Element
  291. var timer: Timeout
  292. proc search*() {.exportc.} =
  293. proc wrapper() =
  294. let elem = getElementById("searchInput")
  295. let value = elem.value
  296. if value.len != 0:
  297. if oldtoc.isNil:
  298. oldtoc = getElementById("tocRoot")
  299. let results = dosearch(value)
  300. replaceById("tocRoot", results)
  301. elif not oldtoc.isNil:
  302. replaceById("tocRoot", oldtoc)
  303. if timer != nil: clearTimeout(timer)
  304. timer = setTimeout(wrapper, 400)