# # # Nim's Runtime Library # (c) Copyright 2012 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## A simple XML tree generator. ## 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 == """ some text &some entity; """ ## **See also:** ## * `xmlparser module `_ for high-level XML parsing ## * `parsexml module `_ for low-level XML parsing ## * `htmlgen module `_ for html code generator import std/private/since import macros, strtabs, strutils, sequtils when defined(nimPreviewSlimSystem): import std/assertions type XmlNode* = ref XmlNodeObj ## An XML tree consisting of XML nodes. ## ## Use `newXmlTree proc <#newXmlTree,string,openArray[XmlNode],XmlAttributes>`_ ## for creating a new tree. 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;``) xnComment ## an XML comment XmlAttributes* = StringTableRef ## An alias for a string to string mapping. ## ## Use `toXmlAttributes proc <#toXmlAttributes,varargs[tuple[string,string]]>`_ ## to create `XmlAttributes`. XmlNodeObj {.acyclic.} = object case k: XmlNodeKind # private, use the kind() proc to read this field. of xnText, xnVerbatimText, xnComment, xnCData, xnEntity: fText: string of xnElement: fTag: string s: seq[XmlNode] fAttr: XmlAttributes fClientData: int ## for other clients const xmlHeader* = "\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: sink string): XmlNode = ## Creates a new ``XmlNode`` of kind ``xnElement`` with the given `tag`. ## ## See also: ## * `newXmlTree proc <#newXmlTree,string,openArray[XmlNode],XmlAttributes>`_ ## * [<> macro](#<>.m,untyped) runnableExamples: var a = newElement("firstTag") a.add newElement("childTag") assert a.kind == xnElement assert $a == """ """ result = newXmlNode(xnElement) result.fTag = tag result.s = @[] # init attributes lazily to save memory proc newText*(text: sink string): XmlNode = ## Creates a new ``XmlNode`` of kind ``xnText`` with the text `text`. runnableExamples: var b = newText("my text") assert b.kind == xnText assert $b == "my text" result = newXmlNode(xnText) result.fText = text 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") assert c.kind == xnComment assert $c == "" result = newXmlNode(xnComment) result.fText = comment proc newCData*(cdata: sink string): XmlNode = ## Creates a new ``XmlNode`` of kind ``xnCData`` with the text `cdata`. runnableExamples: var d = newCData("my cdata") assert d.kind == xnCData assert $d == "" result = newXmlNode(xnCData) result.fText = cdata proc newEntity*(entity: string): XmlNode = ## Creates a new ``XmlNode`` of kind ``xnEntity`` with the text `entity`. runnableExamples: var e = newEntity("my entity") assert e.kind == xnEntity assert $e == "&my entity;" result = newXmlNode(xnEntity) result.fText = entity 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) 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 == """ some text &some entity; """ result = newXmlNode(xnElement) result.fTag = tag newSeq(result.s, children.len) for i in 0..children.len-1: result.s[i] = children[i] result.fAttr = attributes 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. ## ## See also: ## * `text= proc <#text=,XmlNode,string>`_ for text setter ## * `tag proc <#tag,XmlNode>`_ for tag getter ## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter ## * `innerText proc <#innerText,XmlNode>`_ runnableExamples: var c = newComment("my comment") assert $c == "" assert c.text == "my comment" n.expect {xnText, xnComment, xnCData, xnEntity} result = n.fText 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. ## ## See also: ## * `text proc <#text,XmlNode>`_ for text getter ## * `tag proc <#tag,XmlNode>`_ for tag getter ## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter runnableExamples: var e = newEntity("my entity") assert $e == "&my entity;" e.text = "a new entity text" assert $e == "&a new entity text;" n.expect {xnText, xnComment, xnCData, xnEntity} n.fText = text proc tag*(n: XmlNode): lent string {.inline.} = ## Gets the tag name of `n`. ## ## `n` has to be an ``xnElement`` node. ## ## See also: ## * `text proc <#text,XmlNode>`_ for text getter ## * `text= proc <#text=,XmlNode,string>`_ for text setter ## * `tag= proc <#tag=,XmlNode,string>`_ for tag setter ## * `innerText proc <#innerText,XmlNode>`_ runnableExamples: var a = newElement("firstTag") a.add newElement("childTag") assert $a == """ """ assert a.tag == "firstTag" n.expect xnElement result = n.fTag proc `tag=`*(n: XmlNode, tag: sink string) {.inline.} = ## Sets the tag name of `n`. ## ## `n` has to be an ``xnElement`` node. ## ## See also: ## * `text proc <#text,XmlNode>`_ for text getter ## * `text= proc <#text=,XmlNode,string>`_ for text setter ## * `tag proc <#tag,XmlNode>`_ for tag getter runnableExamples: var a = newElement("firstTag") a.add newElement("childTag") assert $a == """ """ a.tag = "newTag" assert $a == """ """ n.expect xnElement n.fTag = tag proc rawText*(n: XmlNode): string {.inline.} = ## Returns the underlying 'text' string by reference. ## ## This is only used for speed hacks. when defined(gcDestructors): result = move(n.fText) else: shallowCopy(result, n.fText) proc rawTag*(n: XmlNode): string {.inline.} = ## Returns the underlying 'tag' string by reference. ## ## This is only used for speed hacks. when defined(gcDestructors): result = move(n.fTag) else: shallowCopy(result, n.fTag) proc innerText*(n: XmlNode): string = ## Gets the inner text of `n`: ## ## - If `n` is `xnText` or `xnEntity`, returns its content. ## - If `n` is `xnElement`, runs recursively on each child node and ## concatenates the results. ## - Otherwise returns an empty string. ## ## See also: ## * `text proc <#text,XmlNode>`_ runnableExamples: var f = newElement("myTag") f.add newText("my text") f.add newComment("my comment") f.add newEntity("my entity") assert $f == "my text&my entity;" assert innerText(f) == "my textmy entity" proc worker(res: var string, n: XmlNode) = case n.k of xnText, xnEntity: res.add(n.fText) of xnElement: for sub in n.s: worker(res, sub) else: discard result = "" worker(result, n) 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 == "my text&my entity;" 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 == "my text&my entity;" father.expect xnElement add(father.s, sons) proc insert*(father, son: XmlNode, index: int) {.inline.} = ## Inserts the child `son` to a given position in `father`. ## ## `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: var f = newElement("myTag") f.add newElement("first") f.insert(newElement("second"), 0) assert $f == """ """ father.expect xnElement if len(father.s) > index: insert(father.s, son, index) else: insert(father.s, son, len(father.s)) 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 == """ """ 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 == """ """ 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 == """ """ 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 == """ """ 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 == """ """ n.expect xnElement n.s.delete(slice) n.s.insert(replacement, slic
#
#
#              Nim's Runtime Library
#        (c) Copyright 2021 Nim Contributors
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module provides some high performance string operations.
##
## Experimental API, subject to change.

const whitespaces = {' ', '\t', '\v', '\r', '\l', '\f'}

proc add*(x: var string, y: openArray[char]) =
  ## Concatenates `x` and `y` in place. `y` must not overlap with `x` to
  ## allow future `memcpy` optimizations.
  # Use `{.noalias.}` ?
  let n = x.len
  x.setLen n + y.len
    # pending https://github.com/nim-lang/Nim/issues/14655#issuecomment-643671397
    # use x.setLen(n + y.len, isInit = false)
  var i = 0
  while i < y.len:
    x[n + i] = y[i]
    i.inc
  # xxx use `nimCopyMem(x[n].addr, y[0].addr, y.len)` after some refactoring

func stripSlice(s: openArray[char], leading = true, trailing = true, chars: set[char] = whitespaces): Slice[int] =
  ## Returns the slice range of `s` which is stripped `chars`.
  runnableExamples:
    assert stripSlice(" abc  ") == 1 .. 3
  var
    first = 0
    last = high(s)
  if leading:
    while first <= last and s[first] in chars: inc(first)
  if trailing:
    while last >= first and s[last] in chars: dec(last)
  result =