xmltree.nim 24 KB

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