summary refs log tree commit diff stats
path: root/lib/pure/xmltree.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/xmltree.nim')
-rw-r--r--lib/pure/xmltree.nim523
1 files changed, 353 insertions, 170 deletions
diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim
index b23e00fdb..5c0cbc5e4 100644
--- a/lib/pure/xmltree.nim
+++ b/lib/pure/xmltree.nim
@@ -9,32 +9,33 @@
 
 ## A simple XML tree generator.
 ##
-## .. code-block::
-##   import xmltree
-##
-##   var g = newElement("myTag")
-##   g.add newText("some text")
-##   g.add newComment("this is comment")
-##
-##   var h = newElement("secondTag")
-##   h.add newEntity("some entity")
-##
-##   let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
-##   let k = newXmlTree("treeTag", [g, h], att)
-##
-##   echo k
-##   # <treeTag key2="second value" key1="first value">
-##   #   <myTag>some text<!-- this is comment --></myTag>
-##   #   <secondTag>&some entity;</secondTag>
-##   # </treeTag>
-##
-##
+runnableExamples:
+  var g = newElement("myTag")
+  g.add newText("some text")
+  g.add newComment("this is comment")
+
+  var h = newElement("secondTag")
+  h.add newEntity("some entity")
+
+  let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
+  let k = newXmlTree("treeTag", [g, h], att)
+
+  doAssert $k == """<treeTag key1="first value" key2="second value">
+  <myTag>some text<!-- this is comment --></myTag>
+  <secondTag>&some entity;</secondTag>
+</treeTag>"""
+
 ## **See also:**
 ## * `xmlparser module <xmlparser.html>`_ for high-level XML parsing
 ## * `parsexml module <parsexml.html>`_ for low-level XML parsing
 ## * `htmlgen module <htmlgen.html>`_ for html code generator
 
-import macros, strtabs, strutils
+import std/private/since
+import std/[macros, strtabs, strutils, sequtils]
+
+when defined(nimPreviewSlimSystem):
+  import std/assertions
+
 
 type
   XmlNode* = ref XmlNodeObj ## An XML tree consisting of XML nodes.
@@ -44,6 +45,7 @@ type
 
   XmlNodeKind* = enum ## Different kinds of XML nodes.
     xnText,           ## a text element
+    xnVerbatimText,   ##
     xnElement,        ## an element with 0 or more children
     xnCData,          ## a CDATA node
     xnEntity,         ## an entity (like ``&thing;``)
@@ -56,7 +58,7 @@ type
 
   XmlNodeObj {.acyclic.} = object
     case k: XmlNodeKind # private, use the kind() proc to read this field.
-    of xnText, xnComment, xnCData, xnEntity:
+    of xnText, xnVerbatimText, xnComment, xnCData, xnEntity:
       fText: string
     of xnElement:
       fTag: string
@@ -68,11 +70,19 @@ const
   xmlHeader* = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n"
     ## Header to use for complete XML output.
 
+template expect(node: XmlNode, kind: set[XmlNodeKind]) =
+  ## Check the node's kind is within a set of values
+  assert node.k in kind, "Got " & $node.k
+
+template expect(node: XmlNode, kind: XmlNodeKind) =
+  ## Check the node's kind equals a value
+  assert node.k == kind, "Got " & $node.k
+
 proc newXmlNode(kind: XmlNodeKind): XmlNode =
   ## Creates a new ``XmlNode``.
   result = XmlNode(k: kind)
 
-proc newElement*(tag: string): XmlNode =
+proc newElement*(tag: sink string): XmlNode =
   ## Creates a new ``XmlNode`` of kind ``xnElement`` with the given `tag`.
   ##
   ## See also:
@@ -82,14 +92,16 @@ proc newElement*(tag: string): XmlNode =
     var a = newElement("firstTag")
     a.add newElement("childTag")
     assert a.kind == xnElement
-    assert $a == "<firstTag><childTag /></firstTag>"
+    assert $a == """<firstTag>
+  <childTag />
+</firstTag>"""
 
   result = newXmlNode(xnElement)
   result.fTag = tag
   result.s = @[]
   # init attributes lazily to save memory
 
-proc newText*(text: string): XmlNode =
+proc newText*(text: sink string): XmlNode =
   ## Creates a new ``XmlNode`` of kind ``xnText`` with the text `text`.
   runnableExamples:
     var b = newText("my text")
@@ -99,7 +111,13 @@ proc newText*(text: string): XmlNode =
   result = newXmlNode(xnText)
   result.fText = text
 
-proc newComment*(comment: string): XmlNode =
+proc newVerbatimText*(text: sink string): XmlNode {.since: (1, 3).} =
+  ## Creates a new ``XmlNode`` of kind ``xnVerbatimText`` with the text `text`.
+  ## **Since**: Version 1.3.
+  result = newXmlNode(xnVerbatimText)
+  result.fText = text
+
+proc newComment*(comment: sink string): XmlNode =
   ## Creates a new ``XmlNode`` of kind ``xnComment`` with the text `comment`.
   runnableExamples:
     var c = newComment("my comment")
@@ -109,7 +127,7 @@ proc newComment*(comment: string): XmlNode =
   result = newXmlNode(xnComment)
   result.fText = comment
 
-proc newCData*(cdata: string): XmlNode =
+proc newCData*(cdata: sink string): XmlNode =
   ## Creates a new ``XmlNode`` of kind ``xnCData`` with the text `cdata`.
   runnableExamples:
     var d = newCData("my cdata")
@@ -129,28 +147,27 @@ proc newEntity*(entity: string): XmlNode =
   result = newXmlNode(xnEntity)
   result.fText = entity
 
-proc newXmlTree*(tag: string, children: openArray[XmlNode],
+proc newXmlTree*(tag: sink string, children: openArray[XmlNode],
                  attributes: XmlAttributes = nil): XmlNode =
   ## Creates a new XML tree with `tag`, `children` and `attributes`.
   ##
   ## See also:
   ## * `newElement proc <#newElement,string>`_
   ## * [<> macro](#<>.m,untyped)
-  ##
-  ## .. code-block::
-  ##   var g = newElement("myTag")
-  ##   g.add newText("some text")
-  ##   g.add newComment("this is comment")
-  ##   var h = newElement("secondTag")
-  ##   h.add newEntity("some entity")
-  ##   let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
-  ##   let k = newXmlTree("treeTag", [g, h], att)
-  ##
-  ##   echo k
-  ##   ## <treeTag key2="second value" key1="first value">
-  ##   ##   <myTag>some text<!-- this is comment --></myTag>
-  ##   ##   <secondTag>&some entity;</secondTag>
-  ##   ## </treeTag>
+
+  runnableExamples:
+    var g = newElement("myTag")
+    g.add newText("some text")
+    g.add newComment("this is comment")
+    var h = newElement("secondTag")
+    h.add newEntity("some entity")
+    let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
+    let k = newXmlTree("treeTag", [g, h], att)
+
+    doAssert $k == """<treeTag key1="first value" key2="second value">
+  <myTag>some text<!-- this is comment --></myTag>
+  <secondTag>&some entity;</secondTag>
+</treeTag>"""
 
   result = newXmlNode(xnElement)
   result.fTag = tag
@@ -158,7 +175,7 @@ proc newXmlTree*(tag: string, children: openArray[XmlNode],
   for i in 0..children.len-1: result.s[i] = children[i]
   result.fAttr = attributes
 
-proc text*(n: XmlNode): string {.inline.} =
+proc text*(n: XmlNode): lent string {.inline.} =
   ## Gets the associated text with the node `n`.
   ##
   ## `n` can be a CDATA, Text, comment, or entity node.
@@ -173,10 +190,10 @@ proc text*(n: XmlNode): string {.inline.} =
     assert $c == "<!-- my comment -->"
     assert c.text == "my comment"
 
-  assert n.k in {xnText, xnComment, xnCData, xnEntity}
+  n.expect {xnText, xnVerbatimText, xnComment, xnCData, xnEntity}
   result = n.fText
 
-proc `text=`*(n: XmlNode, text: string){.inline.} =
+proc `text=`*(n: XmlNode, text: sink string) {.inline.} =
   ## Sets the associated text with the node `n`.
   ##
   ## `n` can be a CDATA, Text, comment, or entity node.
@@ -191,10 +208,10 @@ proc `text=`*(n: XmlNode, text: string){.inline.} =
     e.text = "a new entity text"
     assert $e == "&a new entity text;"
 
-  assert n.k in {xnText, xnComment, xnCData, xnEntity}
+  n.expect {xnText, xnVerbatimText, xnComment, xnCData, xnEntity}
   n.fText = text
 
-proc tag*(n: XmlNode): string {.inline.} =
+proc tag*(n: XmlNode): lent string {.inline.} =
   ## Gets the tag name of `n`.
   ##
   ## `n` has to be an ``xnElement`` node.
@@ -207,13 +224,15 @@ proc tag*(n: XmlNode): string {.inline.} =
   runnableExamples:
     var a = newElement("firstTag")
     a.add newElement("childTag")
-    assert $a == "<firstTag><childTag /></firstTag>"
+    assert $a == """<firstTag>
+  <childTag />
+</firstTag>"""
     assert a.tag == "firstTag"
 
-  assert n.k == xnElement
+  n.expect xnElement
   result = n.fTag
 
-proc `tag=`*(n: XmlNode, tag: string) {.inline.} =
+proc `tag=`*(n: XmlNode, tag: sink string) {.inline.} =
   ## Sets the tag name of `n`.
   ##
   ## `n` has to be an ``xnElement`` node.
@@ -225,11 +244,15 @@ proc `tag=`*(n: XmlNode, tag: string) {.inline.} =
   runnableExamples:
     var a = newElement("firstTag")
     a.add newElement("childTag")
-    assert $a == "<firstTag><childTag /></firstTag>"
+    assert $a == """<firstTag>
+  <childTag />
+</firstTag>"""
     a.tag = "newTag"
-    assert $a == "<newTag><childTag /></newTag>"
+    assert $a == """<newTag>
+  <childTag />
+</newTag>"""
 
-  assert n.k == xnElement
+  n.expect xnElement
   n.fTag = tag
 
 proc rawText*(n: XmlNode): string {.inline.} =
@@ -283,55 +306,206 @@ proc innerText*(n: XmlNode): string =
 
 proc add*(father, son: XmlNode) {.inline.} =
   ## Adds the child `son` to `father`.
+  ## `father` must be of `xnElement` type
   ##
   ## See also:
+  ## * `add proc <#add,XmlNode,openArray[XmlNode]>`_
   ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
+  ## * `insert proc <#insert,XmlNode,openArray[XmlNode],int>`_
   ## * `delete proc <#delete,XmlNode,Natural>`_
+  ## * `delete proc <#delete.XmlNode,Slice[int]>`_
+  ## * `replace proc <#replace.XmlNode,int,openArray[XmlNode]>`_
+  ## * `replace proc <#replace.XmlNode,Slice[int],openArray[XmlNode]>`_
   runnableExamples:
     var f = newElement("myTag")
     f.add newText("my text")
     f.add newElement("sonTag")
     f.add newEntity("my entity")
     assert $f == "<myTag>my text<sonTag />&my entity;</myTag>"
+
+  father.expect xnElement
   add(father.s, son)
 
+proc add*(father: XmlNode, sons: openArray[XmlNode]) {.inline.} =
+  ## Adds the children `sons` to `father`.
+  ## `father` must be of `xnElement` type
+  ##
+  ## See also:
+  ## * `add proc <#add,XmlNode,XmlNode>`_
+  ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
+  ## * `insert proc <#insert,XmlNode,openArray[XmlNode],int>`_
+  ## * `delete proc <#delete,XmlNode,Natural>`_
+  ## * `delete proc <#delete.XmlNode,Slice[int]>`_
+  ## * `replace proc <#replace.XmlNode,int,openArray[XmlNode]>`_
+  ## * `replace proc <#replace.XmlNode,Slice[int],openArray[XmlNode]>`_
+  runnableExamples:
+    var f = newElement("myTag")
+    f.add(@[newText("my text"), newElement("sonTag"), newEntity("my entity")])
+    assert $f == "<myTag>my text<sonTag />&my entity;</myTag>"
+
+  father.expect xnElement
+  add(father.s, sons)
+
+
 proc insert*(father, son: XmlNode, index: int) {.inline.} =
-  ## Insert the child `son` to a given position in `father`.
+  ## Inserts the child `son` to a given position in `father`.
   ##
-  ## `father` and `son` must be of `xnElement` kind.
+  ## `father` must be of `xnElement` kind.
   ##
   ## See also:
+  ## * `insert proc <#insert,XmlNode,openArray[XmlNode],int>`_
   ## * `add proc <#add,XmlNode,XmlNode>`_
+  ## * `add proc <#add,XmlNode,openArray[XmlNode]>`_
   ## * `delete proc <#delete,XmlNode,Natural>`_
+  ## * `delete proc <#delete.XmlNode,Slice[int]>`_
+  ## * `replace proc <#replace.XmlNode,int,openArray[XmlNode]>`_
+  ## * `replace proc <#replace.XmlNode,Slice[int],openArray[XmlNode]>`_
   runnableExamples:
-    from strutils import unindent
     var f = newElement("myTag")
     f.add newElement("first")
     f.insert(newElement("second"), 0)
-    assert ($f).unindent == "<myTag>\n<second />\n<first />\n</myTag>"
+    assert $f == """<myTag>
+  <second />
+  <first />
+</myTag>"""
 
-  assert father.k == xnElement and son.k == xnElement
+  father.expect xnElement
   if len(father.s) > index:
     insert(father.s, son, index)
   else:
     insert(father.s, son, len(father.s))
 
-proc delete*(n: XmlNode, i: Natural) {.noSideEffect.} =
-  ## Delete the `i`'th child of `n`.
+proc insert*(father: XmlNode, sons: openArray[XmlNode], index: int) {.inline.} =
+  ## Inserts the children openArray[`sons`] to a given position in `father`.
+  ##
+  ## `father` must be of `xnElement` kind.
+  ##
+  ## See also:
+  ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
+  ## * `add proc <#add,XmlNode,XmlNode>`_
+  ## * `add proc <#add,XmlNode,openArray[XmlNode]>`_
+  ## * `delete proc <#delete,XmlNode,Natural>`_
+  ## * `delete proc <#delete.XmlNode,Slice[int]>`_
+  ## * `replace proc <#replace.XmlNode,int,openArray[XmlNode]>`_
+  ## * `replace proc <#replace.XmlNode,Slice[int],openArray[XmlNode]>`_
+  runnableExamples:
+    var f = newElement("myTag")
+    f.add newElement("first")
+    f.insert([newElement("second"), newElement("third")], 0)
+    assert $f == """<myTag>
+  <second />
+  <third />
+  <first />
+</myTag>"""
+
+  father.expect xnElement
+  if len(father.s) > index:
+    insert(father.s, sons, index)
+  else:
+    insert(father.s, sons, len(father.s))
+
+proc delete*(n: XmlNode, i: Natural) =
+  ## Deletes the `i`'th child of `n`.
   ##
   ## See also:
+  ## * `delete proc <#delete.XmlNode,Slice[int]>`_
   ## * `add proc <#add,XmlNode,XmlNode>`_
+  ## * `add proc <#add,XmlNode,openArray[XmlNode]>`_
   ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
+  ## * `insert proc <#insert,XmlNode,openArray[XmlNode],int>`_
+  ## * `replace proc <#replace.XmlNode,int,openArray[XmlNode]>`_
+  ## * `replace proc <#replace.XmlNode,Slice[int],openArray[XmlNode]>`_
   runnableExamples:
     var f = newElement("myTag")
     f.add newElement("first")
     f.insert(newElement("second"), 0)
     f.delete(0)
-    assert $f == "<myTag><first /></myTag>"
+    assert $f == """<myTag>
+  <first />
+</myTag>"""
 
-  assert n.k == xnElement
+  n.expect xnElement
   n.s.delete(i)
 
+proc delete*(n: XmlNode, slice: Slice[int]) =
+  ## Deletes the items `n[slice]` of `n`.
+  ##
+  ## See also:
+  ## * `delete proc <#delete.XmlNode,int>`_
+  ## * `add proc <#add,XmlNode,XmlNode>`_
+  ## * `add proc <#add,XmlNode,openArray[XmlNode]>`_
+  ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
+  ## * `insert proc <#insert,XmlNode,openArray[XmlNode],int>`_
+  ## * `replace proc <#replace.XmlNode,int,openArray[XmlNode]>`_
+  ## * `replace proc <#replace.XmlNode,Slice[int],openArray[XmlNode]>`_
+  runnableExamples:
+    var f = newElement("myTag")
+    f.add newElement("first")
+    f.insert([newElement("second"), newElement("third")], 0)
+    f.delete(0..1)
+    assert $f == """<myTag>
+  <first />
+</myTag>"""
+
+  n.expect xnElement
+  n.s.delete(slice)
+
+proc replace*(n: XmlNode, i: Natural, replacement: openArray[XmlNode]) =
+  ## Replaces the `i`'th child of `n` with `replacement` openArray.
+  ##
+  ## `n` must be of `xnElement` kind.
+  ##
+  ## See also:
+  ## * `replace proc <#replace.XmlNode,Slice[int],openArray[XmlNode]>`_
+  ## * `add proc <#add,XmlNode,XmlNode>`_
+  ## * `add proc <#add,XmlNode,openArray[XmlNode]>`_
+  ## * `delete proc <#delete,XmlNode,Natural>`_
+  ## * `delete proc <#delete.XmlNode,Slice[int]>`_
+  ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
+  ## * `insert proc <#insert,XmlNode,openArray[XmlNode],int>`_
+  runnableExamples:
+    var f = newElement("myTag")
+    f.add newElement("first")
+    f.insert(newElement("second"), 0)
+    f.replace(0, @[newElement("third"), newElement("fourth")])
+    assert $f == """<myTag>
+  <third />
+  <fourth />
+  <first />
+</myTag>"""
+
+  n.expect xnElement
+  n.s.delete(i)
+  n.s.insert(replacement, i)
+
+proc replace*(n: XmlNode, slice: Slice[int], replacement: openArray[XmlNode]) =
+  ## Deletes the items `n[slice]` of `n`.
+  ##
+  ## `n` must be of `xnElement` kind.
+  ##
+  ## See also:
+  ## * `replace proc <#replace.XmlNode,int,openArray[XmlNode]>`_
+  ## * `add proc <#add,XmlNode,XmlNode>`_
+  ## * `add proc <#add,XmlNode,openArray[XmlNode]>`_
+  ## * `delete proc <#delete,XmlNode,Natural>`_
+  ## * `delete proc <#delete.XmlNode,Slice[int]>`_
+  ## * `insert proc <#insert,XmlNode,XmlNode,int>`_
+  ## * `insert proc <#insert,XmlNode,openArray[XmlNode],int>`_
+  runnableExamples:
+    var f = newElement("myTag")
+    f.add newElement("first")
+    f.insert([newElement("second"), newElement("fifth")], 0)
+    f.replace(0..1, @[newElement("third"), newElement("fourth")])
+    assert $f == """<myTag>
+  <third />
+  <fourth />
+  <first />
+</myTag>"""
+
+  n.expect xnElement
+  n.s.delete(slice)
+  n.s.insert(replacement, slice.a)
+
 proc len*(n: XmlNode): int {.inline.} =
   ## Returns the number of `n`'s children.
   runnableExamples:
@@ -359,37 +533,35 @@ proc `[]`*(n: XmlNode, i: int): XmlNode {.inline.} =
     assert $f[1] == "<first />"
     assert $f[0] == "<second />"
 
-  assert n.k == xnElement
+  n.expect xnElement
   result = n.s[i]
 
 proc `[]`*(n: var XmlNode, i: int): var XmlNode {.inline.} =
   ## Returns the `i`'th child of `n` so that it can be modified.
-  assert n.k == xnElement
+  n.expect xnElement
   result = n.s[i]
 
 proc clear*(n: var XmlNode) =
-  ## Recursively clear all children of an XmlNode.
-  ##
-  ## .. code-block::
-  ##   var g = newElement("myTag")
-  ##   g.add newText("some text")
-  ##   g.add newComment("this is comment")
-  ##
-  ##   var h = newElement("secondTag")
-  ##   h.add newEntity("some entity")
-  ##
-  ##   let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
-  ##   var k = newXmlTree("treeTag", [g, h], att)
+  ## Recursively clears all children of an XmlNode.
   ##
-  ##   echo k
-  ##   ## <treeTag key2="second value" key1="first value">
-  ##   ##   <myTag>some text<!-- this is comment --></myTag>
-  ##   ##   <secondTag>&some entity;</secondTag>
-  ##   ## </treeTag>
-  ##
-  ##   clear(k)
-  ##   echo k
-  ##   ## <treeTag key2="second value" key1="first value" />
+  runnableExamples:
+    var g = newElement("myTag")
+    g.add newText("some text")
+    g.add newComment("this is comment")
+
+    var h = newElement("secondTag")
+    h.add newEntity("some entity")
+
+    let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
+    var k = newXmlTree("treeTag", [g, h], att)
+
+    doAssert $k == """<treeTag key1="first value" key2="second value">
+  <myTag>some text<!-- this is comment --></myTag>
+  <secondTag>&some entity;</secondTag>
+</treeTag>"""
+
+    clear(k)
+    doAssert $k == """<treeTag key1="first value" key2="second value" />"""
 
   for i in 0 ..< n.len:
     clear(n[i])
@@ -399,45 +571,43 @@ proc clear*(n: var XmlNode) =
 
 iterator items*(n: XmlNode): XmlNode {.inline.} =
   ## Iterates over all direct children of `n`.
-  ##
-  ## **Examples:**
-  ##
-  ## .. code-block::
-  ##   var g = newElement("myTag")
-  ##   g.add newText("some text")
-  ##   g.add newComment("this is comment")
-  ##
-  ##   var h = newElement("secondTag")
-  ##   h.add newEntity("some entity")
-  ##   g.add h
-  ##
-  ##   assert $g == "<myTag>some text<!-- this is comment --><secondTag>&some entity;</secondTag></myTag>"
-  ##   for x in g: # the same as `for x in items(g):`
-  ##     echo x
-  ##
-  ##   # some text
-  ##   # <!-- this is comment -->
-  ##   # <secondTag>&some entity;<![CDATA[some cdata]]></secondTag>
 
-  assert n.k == xnElement
+  runnableExamples:
+    var g = newElement("myTag")
+    g.add newText("some text")
+    g.add newComment("this is comment")
+
+    var h = newElement("secondTag")
+    h.add newEntity("some entity")
+    g.add h
+
+    assert $g == "<myTag>some text<!-- this is comment --><secondTag>&some entity;</secondTag></myTag>"
+
+    # for x in g: # the same as `for x in items(g):`
+    #   echo x
+
+    # some text
+    # <!-- this is comment -->
+    # <secondTag>&some entity;<![CDATA[some cdata]]></secondTag>
+
+  n.expect xnElement
   for i in 0 .. n.len-1: yield n[i]
 
 iterator mitems*(n: var XmlNode): var XmlNode {.inline.} =
   ## Iterates over all direct children of `n` so that they can be modified.
-  assert n.k == xnElement
+  n.expect xnElement
   for i in 0 .. n.len-1: yield n[i]
 
 proc toXmlAttributes*(keyValuePairs: varargs[tuple[key,
     val: string]]): XmlAttributes =
   ## Converts `{key: value}` pairs into `XmlAttributes`.
   ##
-  ## .. code-block::
-  ##   let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
-  ##   var j = newElement("myTag")
-  ##   j.attrs = att
-  ##
-  ##   echo j
-  ##   ## <myTag key2="second value" key1="first value" />
+  runnableExamples:
+    let att = {"key1": "first value", "key2": "second value"}.toXmlAttributes
+    var j = newElement("myTag")
+    j.attrs = att
+
+    doAssert $j == """<myTag key1="first value" key2="second value" />"""
 
   newStringTable(keyValuePairs)
 
@@ -457,7 +627,7 @@ proc attrs*(n: XmlNode): XmlAttributes {.inline.} =
     j.attrs = att
     assert j.attrs == att
 
-  assert n.k == xnElement
+  n.expect xnElement
   result = n.fAttr
 
 proc `attrs=`*(n: XmlNode, attr: XmlAttributes) {.inline.} =
@@ -474,7 +644,7 @@ proc `attrs=`*(n: XmlNode, attr: XmlAttributes) {.inline.} =
     j.attrs = att
     assert j.attrs == att
 
-  assert n.k == xnElement
+  n.expect xnElement
   n.fAttr = attr
 
 proc attrsLen*(n: XmlNode): int {.inline.} =
@@ -491,7 +661,7 @@ proc attrsLen*(n: XmlNode): int {.inline.} =
     j.attrs = att
     assert j.attrsLen == 2
 
-  assert n.k == xnElement
+  n.expect xnElement
   if not isNil(n.fAttr): result = len(n.fAttr)
 
 proc attr*(n: XmlNode, name: string): string =
@@ -509,7 +679,7 @@ proc attr*(n: XmlNode, name: string): string =
     assert j.attr("key1") == "first value"
     assert j.attr("key2") == "second value"
 
-  assert n.kind == xnElement
+  n.expect xnElement
   if n.attrs == nil: return ""
   return n.attrs.getOrDefault(name)
 
@@ -541,15 +711,15 @@ proc escape*(s: string): string =
   ##
   ## Escapes these characters:
   ##
-  ## ------------    -------------------
+  ## ============    ===================
   ## char            is converted to
-  ## ------------    -------------------
+  ## ============    ===================
   ##  ``<``          ``&lt;``
   ##  ``>``          ``&gt;``
   ##  ``&``          ``&amp;``
   ##  ``"``          ``&quot;``
   ##  ``'``          ``&apos;``
-  ## ------------    -------------------
+  ## ============    ===================
   ##
   ## You can also use `addEscaped proc <#addEscaped,string,string>`_.
   result = newStringOfCap(s.len)
@@ -558,25 +728,14 @@ proc escape*(s: string): string =
 proc addIndent(result: var string, indent: int, addNewLines: bool) =
   if addNewLines:
     result.add("\n")
-  for i in 1..indent: result.add(' ')
+  for i in 1 .. indent:
+    result.add(' ')
 
-proc noWhitespace(n: XmlNode): bool =
-  for i in 0..n.len-1:
-    if n[i].kind in {xnText, xnEntity}: return true
-
-proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
-          addNewLines = true) =
-  ## Adds the textual representation of `n` to string `result`.
-  runnableExamples:
-    var
-      a = newElement("firstTag")
-      b = newText("my text")
-      c = newComment("my comment")
-      s = ""
-    s.add(c)
-    s.add(a)
-    s.add(b)
-    assert s == "<!-- my comment --><firstTag />my text"
+proc addImpl(result: var string, n: XmlNode, indent = 0, indWidth = 2,
+          addNewLines = true, lastNodeIsText = false) =
+  proc noWhitespace(n: XmlNode): bool =
+    for i in 0 ..< n.len:
+      if n[i].kind in {xnText, xnVerbatimText, xnEntity}: return true
 
   proc addEscapedAttr(result: var string, s: string) =
     # `addEscaped` alternative with less escaped characters.
@@ -590,8 +749,18 @@ proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
       else: result.add(c)
 
   if n == nil: return
+
   case n.k
   of xnElement:
+    if indent > 0 and not lastNodeIsText:
+      result.addIndent(indent, addNewLines)
+
+    let
+      addNewLines = if n.noWhitespace():
+                      false
+                    else:
+                      addNewLines
+
     result.add('<')
     result.add(n.fTag)
     if not isNil(n.fAttr):
@@ -601,29 +770,32 @@ proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
         result.add("=\"")
         result.addEscapedAttr(val)
         result.add('"')
-    if n.len > 0:
-      result.add('>')
-      if n.len > 1:
-        if noWhitespace(n):
-          # for mixed leaves, we cannot output whitespace for readability,
-          # because this would be wrong. For example: ``a<b>b</b>`` is
-          # different from ``a <b>b</b>``.
-          for i in 0..n.len-1:
-            result.add(n[i], indent+indWidth, indWidth, addNewLines)
-        else:
-          for i in 0..n.len-1:
-            result.addIndent(indent+indWidth, addNewLines)
-            result.add(n[i], indent+indWidth, indWidth, addNewLines)
-          result.addIndent(indent, addNewLines)
-      else:
-        result.add(n[0], indent+indWidth, indWidth, addNewLines)
-      result.add("</")
-      result.add(n.fTag)
-      result.add(">")
-    else:
+
+    if n.len == 0:
       result.add(" />")
+      return
+
+    let
+      indentNext = if n.noWhitespace():
+                     indent
+                   else:
+                     indent+indWidth
+    result.add('>')
+    var lastNodeIsText = false
+    for i in 0 ..< n.len:
+      result.addImpl(n[i], indentNext, indWidth, addNewLines, lastNodeIsText)
+      lastNodeIsText = (n[i].kind == xnText) or (n[i].kind == xnVerbatimText)
+
+    if not n.noWhitespace():
+      result.addIndent(indent, addNewLines)
+
+    result.add("</")
+    result.add(n.fTag)
+    result.add(">")
   of xnText:
     result.addEscaped(n.fText)
+  of xnVerbatimText:
+    result.add(n.fText)
   of xnComment:
     result.add("<!-- ")
     result.addEscaped(n.fText)
@@ -637,6 +809,21 @@ proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
     result.add(n.fText)
     result.add(';')
 
+proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
+          addNewLines = true) {.inline.} =
+  ## Adds the textual representation of `n` to string `result`.
+  runnableExamples:
+    var
+      a = newElement("firstTag")
+      b = newText("my text")
+      c = newComment("my comment")
+      s = ""
+    s.add(c)
+    s.add(a)
+    s.add(b)
+    assert s == "<!-- my comment --><firstTag />my text"
+  result.addImpl(n, indent, indWidth, addNewLines)
+
 proc `$`*(n: XmlNode): string =
   ## Converts `n` into its string representation.
   ##
@@ -655,7 +842,7 @@ proc child*(n: XmlNode, name: string): XmlNode =
     f.add newElement("thirdSon")
     assert $(f.child("secondSon")) == "<secondSon />"
 
-  assert n.kind == xnElement
+  n.expect xnElement
   for i in items(n):
     if i.kind == xnElement:
       if i.tag == name:
@@ -691,7 +878,7 @@ proc findAll*(n: XmlNode, tag: string, result: var seq[XmlNode],
     a.findAll("BAD", s, caseInsensitive = true)
     assert $s == "@[<bad>c text</bad>, <BAD>d text</BAD>]"
 
-  assert n.k == xnElement
+  n.expect xnElement
   for child in n.items():
     if child.k != xnElement:
       continue
@@ -721,7 +908,7 @@ proc findAll*(n: XmlNode, tag: string, caseInsensitive = false): seq[XmlNode] =
   newSeq(result, 0)
   findAll(n, tag, result, caseInsensitive)
 
-proc xmlConstructor(a: NimNode): NimNode {.compileTime.} =
+proc xmlConstructor(a: NimNode): NimNode =
   if a.kind == nnkCall:
     result = newCall("newXmlTree", toStrLit(a[0]))
     var attrs = newNimNode(nnkBracket, a)
@@ -748,16 +935,12 @@ proc xmlConstructor(a: NimNode): NimNode {.compileTime.} =
 macro `<>`*(x: untyped): untyped =
   ## Constructor macro for XML. Example usage:
   ##
-  ## .. code-block:: nim
+  ##   ```nim
   ##   <>a(href="http://nim-lang.org", newText("Nim rules."))
+  ##   ```
   ##
-  ## Produces an XML tree for::
+  ## Produces an XML tree for:
   ##
-  ##  <a href="http://nim-lang.org">Nim rules.</a>
+  ##     <a href="http://nim-lang.org">Nim rules.</a>
   ##
   result = xmlConstructor(x)
-
-
-when isMainModule:
-  assert """<a href="http://nim-lang.org">Nim rules.</a>""" ==
-    $(<>a(href = "http://nim-lang.org", newText("Nim rules.")))