xmltree.nim 29 KB

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