summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--changelog.md2
-rw-r--r--lib/pure/xmltree.nim169
2 files changed, 137 insertions, 34 deletions
diff --git a/changelog.md b/changelog.md
index 1943df634..7be7a722b 100644
--- a/changelog.md
+++ b/changelog.md
@@ -132,3 +132,5 @@
   `ioselector_select` now properly handle the `Event.User` select event type.
 - `joinPath` path normalization when `/` is the first argument works correctly:
   `assert "/" / "/a" == "/a"`. Fixed the edgecase: `assert "" / "" == ""`.
+- `xmltree` now adds indentation consistently to child nodes for any number
+  of children nodes.
diff --git a/lib/pure/xmltree.nim b/lib/pure/xmltree.nim
index b23e00fdb..fbb0ab13a 100644
--- a/lib/pure/xmltree.nim
+++ b/lib/pure/xmltree.nim
@@ -82,7 +82,9 @@ 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
@@ -207,7 +209,9 @@ 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
@@ -225,9 +229,13 @@ 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.fTag = tag
@@ -304,11 +312,13 @@ proc insert*(father, son: XmlNode, index: int) {.inline.} =
   ## * `add proc <#add,XmlNode,XmlNode>`_
   ## * `delete proc <#delete,XmlNode,Natural>`_
   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
   if len(father.s) > index:
@@ -327,7 +337,9 @@ proc delete*(n: XmlNode, i: Natural) {.noSideEffect.} =
     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.s.delete(i)
@@ -558,11 +570,8 @@ 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(' ')
-
-proc noWhitespace(n: XmlNode): bool =
-  for i in 0..n.len-1:
-    if n[i].kind in {xnText, xnEntity}: return true
+  for i in 1 .. indent:
+    result.add(' ')
 
 proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
           addNewLines = true) =
@@ -578,6 +587,10 @@ proc add*(result: var string, n: XmlNode, indent = 0, indWidth = 2,
     s.add(b)
     assert s == "<!-- my comment --><firstTag />my text"
 
+  proc noWhitespace(n: XmlNode): bool =
+    for i in 0 ..< n.len:
+      if n[i].kind in {xnText, xnEntity}: return true
+
   proc addEscapedAttr(result: var string, s: string) =
     # `addEscaped` alternative with less escaped characters.
     # Only to be used for escaping attribute values enclosed in double quotes!
@@ -590,8 +603,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:
+      result.addIndent(indent, addNewLines)
+
+    let
+      addNewLines = if n.noWhitespace():
+                      false
+                    else:
+                      addNewLines
+
     result.add('<')
     result.add(n.fTag)
     if not isNil(n.fAttr):
@@ -601,27 +624,26 @@ 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('>')
+    for i in 0 ..< n.len:
+      result.add(n[i], indentNext, indWidth, addNewLines)
+
+    if not n.noWhitespace():
+      result.addIndent(indent, addNewLines)
+
+    result.add("</")
+    result.add(n.fTag)
+    result.add(">")
   of xnText:
     result.addEscaped(n.fText)
   of xnComment:
@@ -759,5 +781,84 @@ macro `<>`*(x: untyped): untyped =
 
 
 when isMainModule:
-  assert """<a href="http://nim-lang.org">Nim rules.</a>""" ==
-    $(<>a(href = "http://nim-lang.org", newText("Nim rules.")))
+  var
+    x: XmlNode
+
+  x = <>a(href = "http://nim-lang.org", newText("Nim rules."))
+  assert $x == """<a href="http://nim-lang.org">Nim rules.</a>"""
+
+  x = <>outer(<>inner())
+  assert $x == """<outer>
+  <inner />
+</outer>"""
+
+  x = <>outer(<>middle(<>inner1(), <>inner2(), <>inner3(), <>inner4()))
+  assert $x == """<outer>
+  <middle>
+    <inner1 />
+    <inner2 />
+    <inner3 />
+    <inner4 />
+  </middle>
+</outer>"""
+
+  x = <>l0(<>l1(<>l2(<>l3(<>l4()))))
+  assert $x == """<l0>
+  <l1>
+    <l2>
+      <l3>
+        <l4 />
+      </l3>
+    </l2>
+  </l1>
+</l0>"""
+
+  x = <>l0(<>l1p1(), <>l1p2(), <>l1p3())
+  assert $x == """<l0>
+  <l1p1 />
+  <l1p2 />
+  <l1p3 />
+</l0>"""
+
+  x = <>l0(<>l1(<>l2p1(), <>l2p2()))
+  assert $x == """<l0>
+  <l1>
+    <l2p1 />
+    <l2p2 />
+  </l1>
+</l0>"""
+
+  x = <>l0(<>l1(<>l2_1(), <>l2_2(<>l3_1(), <>l3_2(), <>l3_3(<>l4_1(), <>l4_2(), <>l4_3())), <>l2_3(), <>l2_4()))
+  assert $x == """<l0>
+  <l1>
+    <l2_1 />
+    <l2_2>
+      <l3_1 />
+      <l3_2 />
+      <l3_3>
+        <l4_1 />
+        <l4_2 />
+        <l4_3 />
+      </l3_3>
+    </l2_2>
+    <l2_3 />
+    <l2_4 />
+  </l1>
+</l0>"""
+
+  let
+    innermost = newElement("innermost")
+    middle = newXmlTree("middle", [innermost])
+  innermost.add newText("innermost text")
+  x = newXmlTree("outer", [middle])
+  assert $x == """<outer>
+  <middle>
+    <innermost>innermost text</innermost>
+  </middle>
+</outer>"""
+
+  x = newElement("myTag")
+  x.add newText("my text")
+  x.add newElement("sonTag")
+  x.add newEntity("my entity")
+  assert $x == "<myTag>my text<sonTag />&my entity;</myTag>"