xmltree.nim 23 KB

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