xmltree.nim 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2012 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## A simple XML tree generator.
  10. ##
  11. runnableExamples:
  12. var g = newElement("myTag")
  13. g.add newText("some text")
  14. g.add newComment("this is comment")
  15. var h = newElement("secondTag")
  16. h.add newEntity("some entity")
  17. let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
  18. let k = newXmlTree("treeTag", [g, h], att)
  19. doAssert $k == """<treeTag key1="first value" key2="second value">
  20. <myTag>some text<!-- this is comment --></myTag>
  21. <secondTag>&some entity;</secondTag>
  22. </treeTag>"""
  23. ## **See also:**
  24. ## * `xmlparser module <xmlparser.html>`_ for high-level XML parsing
  25. ## * `parsexml module <parsexml.html>`_ for low-level XML parsing
  26. ## * `htmlgen module <htmlgen.html>`_ for html code generator
  27. import std/private/since
  28. import macros, strtabs, strutils
  29. when defined(nimPreviewSlimSystem):
  30. import std/assertions
  31. type
  32. XmlNode* = ref XmlNodeObj ## An XML tree consisting of XML nodes.
  33. ##
  34. ## Use `newXmlTree proc <#newXmlTree,string,openArray[XmlNode],XmlAttributes>`_
  35. ## for creating a new tree.
  36. XmlNodeKind* = enum ## Different kinds of XML nodes.
  37. xnText, ## a text element
  38. xnVerbatimText, ##
  39. xnElement, ## an element with 0 or more children
  40. xnCData, ## a CDATA node
  41. xnEntity, ## an entity (like ``&thing;``)
  42. xnComment ## an XML comment
  43. XmlAttributes* = StringTableRef ## An alias for a string to string mapping.
  44. ##
  45. ## Use `toXmlAttributes proc <#toXmlAttributes,varargs[tuple[string,string]]>`_
  46. ## to create `XmlAttributes`.
  47. XmlNodeObj {.acyclic.} = object
  48. case k: XmlNodeKind # private, use the kind() proc to read this field.
  49. of xnText, xnVerbatimText, xnComment, xnCData, xnEntity:
  50. fText: string
  51. of xnElement:
  52. fTag: string
  53. s: seq[XmlNode]
  54. fAttr: XmlAttributes
  55. fClientData: int ## for other clients
  56. const
  57. xmlHeader* = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
  58. ## Header to use for complete XML output.
  59. proc newXmlNode(kind: XmlNodeKind): XmlNode =
  60. ## Creates a new ``XmlNode``.
  61. result = XmlNode(k: kind)
  62. proc newElement*(tag: sink string): XmlNode =
  63. ## Creates a new ``XmlNode`` of kind ``xnElement`` with the given `tag`.
  64. ##
  65. ## See also:
  66. ## * `newXmlTree proc <#newXmlTree,string,openArray[XmlNode],XmlAttributes>`_
  67. ## * [<> macro](#<>.m,untyped)
  68. runnableExamples:
  69. var a = newElement("firstTag")
  70. a.add newElement("childTag")
  71. assert a.kind == xnElement
  72. assert $a == """<firstTag>
  73. <childTag />
  74. </firstTag>"""
  75. result = newXmlNode(xnElement)
  76. result.fTag = tag
  77. result.s = @[]
  78. # init attributes lazily to save memory
  79. proc newText*(text: sink string): XmlNode =
  80. ## Creates a new ``XmlNode`` of kind ``xnText`` with the text `text`.
  81. runnableExamples:
  82. var b = newText("my text")
  83. assert b.kind == xnText
  84. assert $b == "my text"
  85. result = newXmlNode(xnText)
  86. result.fText = text
  87. proc newVerbatimText*(text: sink string): XmlNode {.since: (1, 3).} =
  88. ## Creates a new ``XmlNode`` of kind ``xnVerbatimText`` with the text `text`.
  89. ## **Since**: Version 1.3.
  90. result = newXmlNode(xnVerbatimText)
  91. result.fText = text
  92. proc newComment*(comment: sink string): XmlNode =
  93. ## Creates a new ``XmlNode`` of kind ``xnComment`` with the text `comment`.
  94. runnableExamples:
  95. var c = newComment("my comment")
  96. assert c.kind == xnComment
  97. assert $c == "<!-- my comment -->"
  98. result = newXmlNode(xnComment)
  99. result.fText = comment
  100. proc newCData*(cdata: sink string): XmlNode =
  101. ## Creates a new ``XmlNode`` of kind ``xnCData`` with the text `cdata`.
  102. runnableExamples:
  103. var d = newCData("my cdata")
  104. assert d.kind == xnCData
  105. assert $d == "<![CDATA[my cdata]]>"
  106. result = newXmlNode(xnCData)
  107. result.fText = cdata
  108. proc newEntity*(entity: string): XmlNode =
  109. ## Creates a new ``XmlNode`` of kind ``xnEntity`` with the text `entity`.
  110. runnableExamples:
  111. var e = newEntity("my entity")
  112. assert e.kind == xnEntity
  113. assert $e == "&my entity;"
  114. result = newXmlNode(xnEntity)
  115. result.fText = entity
  116. proc newXmlTree*(tag: sink string, children: openArray[XmlNode],
  117. attributes: XmlAttributes = nil): XmlNode =
  118. ## Creates a new XML tree with `tag`, `children` and `attributes`.
  119. ##
  120. ## See also:
  121. ## * `newElement proc <#newElement,string>`_
  122. ## * [<> macro](#<>.m,untyped)
  123. runnableExamples:
  124. var g = newElement("myTag")
  125. g.add newText("some text")
  126. g.add newComment("this is comment")
  127. var h = newElement("secondTag")
  128. h.add newEntity("some entity")
  129. let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
  130. let k = newXmlTree("treeTag", [g, h], att)
  131. doAssert $k == """<treeTag key1="first value" key2="second value">
  132. <myTag>some text<!-- this is comment --></myTag>
  133. <secondTag>&some entity;</secondTag>
  134. </treeTag>"""
  135. result = newXmlNode(xnElement)
  136. result.fTag = tag
  137. newSeq(result.s, children.len)
  138. for i in 0..children.len-1: result.s[i] = children[i]
  139. result.fAttr = attributes
  140. proc text*(n: XmlNode): lent string {.inline.} =
  141. ## Gets the associated text with the node `n`.
  142. ##
  143. ## `n` can be a CDATA, Text, comment, or entity node.
  144. ##
  145. ## See also:
  146. ## * `text= proc <#text=,XmlNode,string>`_ for text setter
  147. ## * `tag proc <#tag,XmlNode>`_ for tag getter
  148. ## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter
  149. ## * `innerText proc <#innerText,XmlNode>`_
  150. runnableExamples:
  151. var c = newComment("my comment")
  152. assert $c == "<!-- my comment -->"
  153. assert c.text == "my comment"
  154. assert n.k in {xnText, xnComment, xnCData, xnEntity}
  155. result = n.fText
  156. proc `text=`*(n: XmlNode, text: sink string) {.inline.} =
  157. ## Sets the associated text with the node `n`.
  158. ##
  159. ## `n` can be a CDATA, Text, comment, or entity node.
  160. ##
  161. ## See also:
  162. ## * `text proc <#text,XmlNode>`_ for text getter
  163. ## * `tag proc <#tag,XmlNode>`_ for tag getter
  164. ## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter
  165. runnableExamples:
  166. var e = newEntity("my entity")
  167. assert $e == "&my entity;"
  168. e.text = "a new entity text"
  169. assert $e == "&a new entity text;"
  170. assert n.k in {xnText, xnComment, xnCData, xnEntity}
  171. n.fText = text
  172. proc tag*(n: XmlNode): lent string {.inline.} =
  173. ## Gets the tag name of `n`.
  174. ##
  175. ## `n` has to be an ``xnElement`` node.
  176. ##
  177. ## See also:
  178. ## * `text proc <#text,XmlNode>`_ for text getter
  179. ## * `text= proc <#text=,XmlNode,string>`_ for text setter
  180. ## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter
  181. ## * `innerText proc <#innerText,XmlNode>`_
  182. runnableExamples:
  183. var a = newElement("firstTag")
  184. a.add newElement("childTag")
  185. assert $a == """<firstTag>
  186. <childTag />
  187. </firstTag>"""
  188. assert a.tag == "firstTag"
  189. assert n.k == xnElement
  190. result = n.fTag
  191. proc `tag=`*(n: XmlNode, tag: sink string) {.inline.} =
  192. ## Sets the tag name of `n`.
  193. ##
  194. ## `n` has to be an ``xnElement`` node.
  195. ##
  196. ## See also:
  197. ## * `text proc <#text,XmlNode>`_ for text getter
  198. ## * `text= proc <#text=,XmlNode,string>`_ for text setter
  199. ## * `tag proc <#tag,XmlNode>`_ for tag getter
  200. runnableExamples:
  201. var a = newElement("firstTag")
  202. a.add newElement("childTag")
  203. assert $a == """<firstTag>
  204. <childTag />
  205. </firstTag>"""
  206. a.tag = "newTag"
  207. assert $a == """<newTag>
  208. <childTag />
  209. </newTag>"""
  210. assert n.k == xnElement
  211. n.fTag = tag
  212. proc rawText*(n: XmlNode): string {.inline.} =
  213. ## Returns the underlying 'text' string by reference.
  214. ##
  215. ## This is only used for speed hacks.
  216. when defined(gcDestructors):
  217. result = move(n.fText)
  218. else:
  219. shallowCopy(result, n.fText)
  220. proc rawTag*(n: XmlNode): string {.inline.} =
  221. ## Returns the underlying 'tag' string by reference.
  222. ##
  223. ## This is only used for speed hacks.
  224. when defined(gcDestructors):
  225. result = move(n.fTag)
  226. else:
  227. shallowCopy(result, n.fTag)
  228. proc innerText*(n: XmlNode): string =
  229. ## Gets the inner text of `n`:
  230. ##
  231. ## - If `n` is `xnText` or `xnEntity`, returns its content.
  232. ## - If `n` is `xnElement`, runs recursively on each child node and
  233. ## concatenates the results.
  234. ## - Otherwise returns an empty string.
  235. ##
  236. ## See also:
  237. ## * `text proc <#text,XmlNode>`_
  238. runnableExamples:
  239. var f = newElement("myTag")
  240. f.add newText("my text")
  241. f.add newComment("my comment")
  242. f.add newEntity("my entity")
  243. assert $f == "<myTag>my text<!-- my comment -->&my entity;</myTag>"
  244. assert innerText(f) == "my textmy entity"
  245. proc worker(res: var string, n: XmlNode) =
  246. case n.k
  247. of xnText, xnEntity:
  248. res.add(n.fText)
  249. of xnElement:
  250. for sub in n.s:
  251. worker(res, sub)
  252. else:
  253. discard
  254. result = ""
  255. worker(result, n)
  256. proc add*(father, son: XmlNode) {.inline.} =
  257. ## Adds the child `son` to `father`.
  258. ##
  259. ## See also:
  260. ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
  261. ## * `delete proc <#delete,XmlNode,Natural>`_
  262. runnableExamples:
  263. var f = newElement("myTag")
  264. f.add newText("my text")
  265. f.add newElement("sonTag")
  266. f.add newEntity("my entity")
  267. assert $f == "<myTag>my text<sonTag />&my entity;</myTag>"
  268. add(father.s, son)
  269. proc insert*(father, son: XmlNode, index: int) {.inline.} =
  270. ## Inserts the child `son` to a given position in `father`.
  271. ##
  272. ## `father` and `son` must be of `xnElement` kind.
  273. ##
  274. ## See also:
  275. ## * `add proc <#add,XmlNode,XmlNode>`_
  276. ## * `delete proc <#delete,XmlNode,Natural>`_
  277. runnableExamples:
  278. var f = newElement("myTag")
  279. f.add newElement("first")
  280. f.insert(newElement("second"), 0)
  281. assert $f == """<myTag>
  282. <second />
  283. <first />
  284. </myTag>"""
  285. assert father.k == xnElement and son.k == xnElement
  286. if len(father.s) > index:
  287. insert(father.s, son, index)
  288. else:
  289. insert(father.s, son, len(father.s))
  290. proc delete*(n: XmlNode, i: Natural) =
  291. ## Deletes the `i`'th child of `n`.
  292. ##
  293. ## See also:
  294. ## * `add proc <#add,XmlNode,XmlNode>`_
  295. ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
  296. runnableExamples:
  297. var f = newElement("myTag")
  298. f.add newElement("first")
  299. f.insert(newElement("second"), 0)
  300. f.delete(0)
  301. assert $f == """<myTag>
  302. <first />
  303. </myTag>"""
  304. assert n.k == xnElement
  305. n.s.delete(i)
  306. proc len*(n: XmlNode): int {.inline.} =
  307. ## Returns the number of `n`'s children.
  308. runnableExamples:
  309. var f = newElement("myTag")
  310. f.add newElement("first")
  311. f.insert(newElement("second"), 0)
  312. assert len(f) == 2
  313. if n.k == xnElement: result = len(n.s)
  314. proc kind*(n: XmlNode): XmlNodeKind {.inline.} =
  315. ## Returns `n`'s kind.
  316. runnableExamples:
  317. var a = newElement("firstTag")
  318. assert a.kind == xnElement
  319. var b = newText("my text")
  320. assert b.kind == xnText
  321. result = n.k
  322. proc `[]`*(n: XmlNode, i: int): XmlNode {.inline.} =
  323. ## Returns the `i`'th child of `n`.
  324. runnableExamples:
  325. var f = newElement("myTag")
  326. f.add newElement("first")
  327. f.insert(newElement("second"), 0)
  328. assert $f[1] == "<first />"
  329. assert $f[0] == "<second />"
  330. assert n.k == xnElement
  331. result = n.s[i]
  332. proc `[]`*(n: var XmlNode, i: int): var XmlNode {.inline.} =
  333. ## Returns the `i`'th child of `n` so that it can be modified.
  334. assert n.k == xnElement
  335. result = n.s[i]
  336. proc clear*(n: var XmlNode) =
  337. ## Recursively clears all children of an XmlNode.
  338. ##
  339. runnableExamples:
  340. var g = newElement("myTag")
  341. g.add newText("some text")
  342. g.add newComment("this is comment")
  343. var h = newElement("secondTag")
  344. h.add newEntity("some entity")
  345. let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
  346. var k = newXmlTree("treeTag", [g, h], att)
  347. doAssert $k == """<treeTag key1="first value" key2="second value">
  348. <myTag>some text<!-- this is comment --></myTag>
  349. <secondTag>&some entity;</secondTag>
  350. </treeTag>"""
  351. clear(k)
  352. doAssert $k == """<treeTag key1="first value" key2="second value" />"""
  353. for i in 0 ..< n.len:
  354. clear(n[i])
  355. if n.k == xnElement:
  356. n.s.setLen(0)
  357. iterator items*(n: XmlNode): XmlNode {.inline.} =
  358. ## Iterates over all direct children of `n`.
  359. runnableExamples:
  360. var g = newElement("myTag")
  361. g.add newText("some text")
  362. g.add newComment("this is comment")
  363. var h = newElement("secondTag")
  364. h.add newEntity("some entity")
  365. g.add h
  366. assert $g == "<myTag>some text<!-- this is comment --><secondTag>&some entity;</secondTag></myTag>"
  367. # for x in g: # the same as `for x in items(g):`
  368. # echo x
  369. # some text
  370. # <!-- this is comment -->
  371. # <secondTag>&some entity;<![CDATA[some cdata]]></secondTag>
  372. assert n.k == xnElement
  373. for i in 0 .. n.len-1: yield n[i]
  374. iterator mitems*(n: var XmlNode): var XmlNode {.inline.} =
  375. ## Iterates over all direct children of `n` so that they can be modified.
  376. assert n.k == xnElement
  377. for i in 0 .. n.len-1: yield n[i]
  378. proc toXmlAttributes*(keyValuePairs: varargs[tuple[key,
  379. val: string]]): XmlAttributes =
  380. ## Converts `{key: value}` pairs into `XmlAttributes`.
  381. ##
  382. runnableExamples:
  383. let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
  384. var j = newElement("myTag")
  385. j.attrs = att
  386. doAssert $j == """<myTag key1="first value" key2="second value" />"""
  387. newStringTable(keyValuePairs)
  388. proc attrs*(n: XmlNode): XmlAttributes {.inline.} =
  389. ## Gets the attributes belonging to `n`.
  390. ##
  391. ## Returns `nil` if attributes have not been initialised for this node.
  392. ##
  393. ## See also:
  394. ## * `attrs= proc <#attrs=,XmlNode,XmlAttributes>`_ for XmlAttributes setter
  395. ## * `attrsLen proc <#attrsLen,XmlNode>`_ for number of attributes
  396. ## * `attr proc <#attr,XmlNode,string>`_ for finding an attribute
  397. runnableExamples:
  398. var j = newElement("myTag")
  399. assert j.attrs == nil
  400. let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
  401. j.attrs = att
  402. assert j.attrs == att
  403. assert n.k == xnElement
  404. result = n.fAttr
  405. proc `attrs=`*(n: XmlNode, attr: XmlAttributes) {.inline.} =
  406. ## Sets the attributes belonging to `n`.
  407. ##
  408. ## See also:
  409. ## * `attrs proc <#attrs,XmlNode>`_ for XmlAttributes getter
  410. ## * `attrsLen proc <#attrsLen,XmlNode>`_ for number of attributes
  411. ## * `attr proc <#attr,XmlNode,string>`_ for finding an attribute
  412. runnableExamples:
  413. var j = newElement("myTag")
  414. assert j.attrs == nil
  415. let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
  416. j.attrs = att
  417. assert j.attrs == att
  418. assert n.k == xnElement
  419. n.fAttr = attr
  420. proc attrsLen*(n: XmlNode): int {.inline.} =
  421. ## Returns the number of `n`'s attributes.
  422. ##
  423. ## See also:
  424. ## * `attrs proc <#attrs,XmlNode>`_ for XmlAttributes getter
  425. ## * `attrs= proc <#attrs=,XmlNode,XmlAttributes>`_ for XmlAttributes setter
  426. ## * `attr proc <#attr,XmlNode,string>`_ for finding an attribute
  427. runnableExamples:
  428. var j = newElement("myTag")
  429. assert j.attrsLen == 0
  430. let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
  431. j.attrs = att
  432. assert j.attrsLen == 2
  433. assert n.k == xnElement
  434. if not isNil(n.fAttr): result = len(n.fAttr)
  435. proc attr*(n: XmlNode, name: string): string =
  436. ## Finds the first attribute of `n` with a name of `name`.
  437. ## Returns "" on failure.
  438. ##
  439. ## See also:
  440. ## * `attrs proc <#attrs,XmlNode>`_ for XmlAttributes getter
  441. ## * `attrs= proc <#attrs=,XmlNode,XmlAttributes>`_ for XmlAttributes setter
  442. ## * `attrsLen proc <#attrsLen,XmlNode>`_ for number of attributes
  443. runnableExamples:
  444. var j = newElement("myTag")
  445. let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
  446. j.attrs = att
  447. assert j.attr("key1") == "first value"
  448. assert j.attr("key2") == "second value"
  449. assert n.kind == xnElement
  450. if n.attrs == nil: return ""
  451. return n.attrs.getOrDefault(name)
  452. proc clientData*(n: XmlNode): int {.inline.} =
  453. ## Gets the client data of `n`.
  454. ##
  455. ## The client data field is used by the HTML parser and generator.
  456. result = n.fClientData
  457. proc `clientData=`*(n: XmlNode, data: int) {.inline.} =
  458. ## Sets the client data of `n`.
  459. ##
  460. ## The client data field is used by the HTML parser and generator.
  461. n.fClientData = data
  462. proc addEscaped*(result: var string, s: string) =
  463. ## The same as `result.add(escape(s)) <#escape,string>`_, but more efficient.
  464. for c in items(s):
  465. case c
  466. of '<': result.add("&lt;")
  467. of '>': result.add("&gt;")
  468. of '&': result.add("&amp;")
  469. of '"': result.add("&quot;")
  470. of '\'': result.add("&apos;")
  471. else: result.add(c)
  472. proc escape*(s: string): string =
  473. ## Escapes `s` for inclusion into an XML document.
  474. ##
  475. ## Escapes these characters:
  476. ##
  477. ## ============ ===================
  478. ## char is converted to
  479. ## ============ ===================
  480. ## ``<`` ``&lt;``
  481. ## ``>`` ``&gt;``
  482. ## ``&`` ``&amp;``
  483. ## ``"`` ``&quot;``
  484. ## ``'`` ``&apos;``
  485. ## ============ ===================
  486. ##
  487. ## You can also use `addEscaped proc <#addEscaped,string,string>`_.
  488. result = newStringOfCap(s.len)
  489. addEscaped(result, s)
  490. proc addIndent(result: var string, indent: int, addNewLines: bool) =
  491. if addNewLines:
  492. result.add("\n")
  493. for i in 1 .. indent:
  494. result.add(' ')
  495. proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
  496. addNewLines = true) =
  497. ## Adds the textual representation of `n` to string `result`.
  498. runnableExamples:
  499. var
  500. a = newElement("firstTag")
  501. b = newText("my text")
  502. c = newComment("my comment")
  503. s = ""
  504. s.add(c)
  505. s.add(a)
  506. s.add(b)
  507. assert s == "<!-- my comment --><firstTag />my text"
  508. proc noWhitespace(n: XmlNode): bool =
  509. for i in 0 ..< n.len:
  510. if n[i].kind in {xnText, xnEntity}: return true
  511. proc addEscapedAttr(result: var string, s: string) =
  512. # `addEscaped` alternative with less escaped characters.
  513. # Only to be used for escaping attribute values enclosed in double quotes!
  514. for c in items(s):
  515. case c
  516. of '<': result.add("&lt;")
  517. of '>': result.add("&gt;")
  518. of '&': result.add("&amp;")
  519. of '"': result.add("&quot;")
  520. else: result.add(c)
  521. if n == nil: return
  522. case n.k
  523. of xnElement:
  524. if indent > 0:
  525. result.addIndent(indent, addNewLines)
  526. let
  527. addNewLines = if n.noWhitespace():
  528. false
  529. else:
  530. addNewLines
  531. result.add('<')
  532. result.add(n.fTag)
  533. if not isNil(n.fAttr):
  534. for key, val in pairs(n.fAttr):
  535. result.add(' ')
  536. result.add(key)
  537. result.add("=\"")
  538. result.addEscapedAttr(val)
  539. result.add('"')
  540. if n.len == 0:
  541. result.add(" />")
  542. return
  543. let
  544. indentNext = if n.noWhitespace():
  545. indent
  546. else:
  547. indent+indWidth
  548. result.add('>')
  549. for i in 0 ..< n.len:
  550. result.add(n[i], indentNext, indWidth, addNewLines)
  551. if not n.noWhitespace():
  552. result.addIndent(indent, addNewLines)
  553. result.add("</")
  554. result.add(n.fTag)
  555. result.add(">")
  556. of xnText:
  557. result.addEscaped(n.fText)
  558. of xnVerbatimText:
  559. result.add(n.fText)
  560. of xnComment:
  561. result.add("<!-- ")
  562. result.addEscaped(n.fText)
  563. result.add(" -->")
  564. of xnCData:
  565. result.add("<![CDATA[")
  566. result.add(n.fText)
  567. result.add("]]>")
  568. of xnEntity:
  569. result.add('&')
  570. result.add(n.fText)
  571. result.add(';')
  572. proc `$`*(n: XmlNode): string =
  573. ## Converts `n` into its string representation.
  574. ##
  575. ## No ``<$xml ...$>`` declaration is produced, so that the produced
  576. ## XML fragments are composable.
  577. result = ""
  578. result.add(n)
  579. proc child*(n: XmlNode, name: string): XmlNode =
  580. ## Finds the first child element of `n` with a name of `name`.
  581. ## Returns `nil` on failure.
  582. runnableExamples:
  583. var f = newElement("myTag")
  584. f.add newElement("firstSon")
  585. f.add newElement("secondSon")
  586. f.add newElement("thirdSon")
  587. assert $(f.child("secondSon")) == "<secondSon />"
  588. assert n.kind == xnElement
  589. for i in items(n):
  590. if i.kind == xnElement:
  591. if i.tag == name:
  592. return i
  593. proc findAll*(n: XmlNode, tag: string, result: var seq[XmlNode],
  594. caseInsensitive = false) =
  595. ## Iterates over all the children of `n` returning those matching `tag`.
  596. ##
  597. ## Found nodes satisfying the condition will be appended to the `result`
  598. ## sequence.
  599. runnableExamples:
  600. var
  601. b = newElement("good")
  602. c = newElement("bad")
  603. d = newElement("BAD")
  604. e = newElement("GOOD")
  605. b.add newText("b text")
  606. c.add newText("c text")
  607. d.add newText("d text")
  608. e.add newText("e text")
  609. let a = newXmlTree("father", [b, c, d, e])
  610. var s = newSeq[XmlNode]()
  611. a.findAll("good", s)
  612. assert $s == "@[<good>b text</good>]"
  613. s.setLen(0)
  614. a.findAll("good", s, caseInsensitive = true)
  615. assert $s == "@[<good>b text</good>, <GOOD>e text</GOOD>]"
  616. s.setLen(0)
  617. a.findAll("BAD", s)
  618. assert $s == "@[<BAD>d text</BAD>]"
  619. s.setLen(0)
  620. a.findAll("BAD", s, caseInsensitive = true)
  621. assert $s == "@[<bad>c text</bad>, <BAD>d text</BAD>]"
  622. assert n.k == xnElement
  623. for child in n.items():
  624. if child.k != xnElement:
  625. continue
  626. if child.tag == tag or
  627. (caseInsensitive and cmpIgnoreCase(child.tag, tag) == 0):
  628. result.add(child)
  629. child.findAll(tag, result)
  630. proc findAll*(n: XmlNode, tag: string, caseInsensitive = false): seq[XmlNode] =
  631. ## A shortcut version to assign in let blocks.
  632. runnableExamples:
  633. var
  634. b = newElement("good")
  635. c = newElement("bad")
  636. d = newElement("BAD")
  637. e = newElement("GOOD")
  638. b.add newText("b text")
  639. c.add newText("c text")
  640. d.add newText("d text")
  641. e.add newText("e text")
  642. let a = newXmlTree("father", [b, c, d, e])
  643. assert $(a.findAll("good")) == "@[<good>b text</good>]"
  644. assert $(a.findAll("BAD")) == "@[<BAD>d text</BAD>]"
  645. assert $(a.findAll("good", caseInsensitive = true)) == "@[<good>b text</good>, <GOOD>e text</GOOD>]"
  646. assert $(a.findAll("BAD", caseInsensitive = true)) == "@[<bad>c text</bad>, <BAD>d text</BAD>]"
  647. newSeq(result, 0)
  648. findAll(n, tag, result, caseInsensitive)
  649. proc xmlConstructor(a: NimNode): NimNode =
  650. if a.kind == nnkCall:
  651. result = newCall("newXmlTree", toStrLit(a[0]))
  652. var attrs = newNimNode(nnkBracket, a)
  653. var newStringTabCall = newCall(bindSym"newStringTable", attrs,
  654. bindSym"modeCaseSensitive")
  655. var elements = newNimNode(nnkBracket, a)
  656. for i in 1..a.len-1:
  657. if a[i].kind == nnkExprEqExpr:
  658. # In order to support attributes like `data-lang` we have to
  659. # replace whitespace because `toStrLit` gives `data - lang`.
  660. let attrName = toStrLit(a[i][0]).strVal.replace(" ", "")
  661. attrs.add(newStrLitNode(attrName))
  662. attrs.add(a[i][1])
  663. #echo repr(attrs)
  664. else:
  665. elements.add(a[i])
  666. result.add(elements)
  667. if attrs.len > 1:
  668. #echo repr(newStringTabCall)
  669. result.add(newStringTabCall)
  670. else:
  671. result = newCall("newXmlTree", toStrLit(a))
  672. macro `<>`*(x: untyped): untyped =
  673. ## Constructor macro for XML. Example usage:
  674. ##
  675. ## .. code-block:: nim
  676. ## <>a(href="http://nim-lang.org", newText("Nim rules."))
  677. ##
  678. ## Produces an XML tree for:
  679. ##
  680. ## <a href="http://nim-lang.org">Nim rules.</a>
  681. ##
  682. result = xmlConstructor(x)