xmltree.nim 25 KB

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