# # # Nimrod'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. More efficient and simpler than the DOM. import macros, strtabs type PXmlNode* = ref TXmlNode ## an XML tree consists of ``PXmlNode``'s. TXmlNodeKind* = enum ## different kinds of ``PXmlNode``'s xnText, ## a text element xnElement, ## an element with 0 or more children xnCData, ## a CDATA node xnEntity, ## an entity (like ``&thing;``) xnComment ## an XML comment PXmlAttributes* = PStringTable ## an alias for a string to string mapping TXmlNode {.pure, final, acyclic.} = object case k: TXmlNodeKind # private, use the kind() proc to read this field. of xnText, xnComment, xnCData, xnEntity: fText: string of xnElement: fTag: string s: seq[PXmlNode] fAttr: PXmlAttributes fClientData: int ## for other clients proc newXmlNode(kind: TXmlNodeKind): PXmlNode = ## creates a new ``PXmlNode``. new(result) result.k = kind proc newElement*(tag: string): PXmlNode = ## creates a new ``PXmlNode`` of kind ``xnText`` with the given `tag`. result = newXmlNode(xnElement) result.fTag = tag result.s = @[] # init attributes lazily to safe memory proc newText*(text: string): PXmlNode = ## creates a new ``PXmlNode`` of kind ``xnText`` with the text `text`. result = newXmlNode(xnText) result.fText = text proc newComment*(comment: string): PXmlNode = ## creates a new ``PXmlNode`` of kind ``xnComment`` with the text `comment`. result = newXmlNode(xnComment) result.fText = comment proc newCData*(cdata: string): PXmlNode = ## creates a new ``PXmlNode`` of kind ``xnComment`` with the text `cdata`. result = newXmlNode(xnCData) result.fText = cdata proc newEntity*(entity: string): PXmlNode = ## creates a new ``PXmlNode`` of kind ``xnEntity`` with the text `entity`. result = newXmlNode(xnCData) result.fText = entity proc text*(n: PXmlNode): string {.inline.} = ## gets the associated text with the node `n`. `n` can be a CDATA, Text, ## comment, or entity node. assert n.k in {xnText, xnComment, xnCData, xnEntity} result = n.fText proc rawText*(n: PXmlNode): string {.inline.} = ## returns the underlying 'text' string by reference. ## This is only used for speed hacks. shallowCopy(result, n.fText) proc rawTag*(n: PXmlNode): string {.inline.} = ## returns the underlying 'tag' string by reference. ## This is only used for speed hacks. shallowCopy(result, n.fTag) proc innerText*(n: PXmlNode): string = ## gets the inner text of `n`. `n` has to be an ``xnElement`` node. Only ## ``xnText`` and ``xnEntity`` nodes are considered part of `n`'s inner text, ## other child nodes are silently ignored. result = "" assert n.k == xnElement for i in 0 .. n.s.len-1: if n.s[i].k in {xnText, xnEntity}: result.add(n.s[i].fText) proc tag*(n: PXmlNode): string {.inline.} = ## gets the tag name of `n`. `n` has to be an ``xnElement`` node. assert n.k == xnElement result = n.fTag proc add*(father, son: PXmlNode) {.inline.} = ## adds the child `son` to `father`. add(father.s, son) proc len*(n: PXmlNode): int {.inline.} = ## returns the number `n`'s children. if n.k == xnElement: result = len(n.s) proc kind*(n: PXmlNode): TXmlNodeKind {.inline.} = ## returns `n`'s kind. result = n.k proc `[]`* (n: PXmlNode, i: int): PXmlNode {.inline.} = ## returns the `i`'th child of `n`. assert n.k == xnElement result = n.s[i] iterator items*(n: PXmlNode): PXmlNode {.inline.} = ## iterates over any child of `n`. assert n.k == xnElement for i in 0 .. n.len-1: yield n[i] proc attrs*(n: PXmlNode): PXmlAttributes {.inline.} = ## gets the attributes belonging to `n`. ## Returns `nil` if attributes have not been initialised for this node. assert n.k == xnElement result = n.fAttr proc `attrs=`*(n: PXmlNode, attr: PXmlAttributes) {.inline.} = ## sets the attributes belonging to `n`. assert n.k == xnElement n.fAttr = attr proc attrsLen*(n: PXmlNode): int {.inline.} = ## returns the number of `n`'s attributes. assert n.k == xnElement if not isNil(n.fAttr): result = len(n.fAttr) proc clientData*(n: PXmlNode): int {.inline.} = ## gets the client data of `n`. The client data field is used by the HTML ## parser and generator. result = n.fClientData proc `clientData=`*(n: PXmlNode, data: int) {.inline.} = ## sets the client data of `n`. The client data field is used by the HTML ## parser and generator. n.fClientData = data proc addEscaped*(result: var string, s: string) = ## same as ``result.add(escape(s))``, but more efficient. for c in items(s): case c of '<': result.add("<") of '>': result.add(">") of '&': result.add("&") of '"': result.add(""") else: result.add(c) proc escape*(s: string): string = ## escapes `s` for inclusion into an XML document. ## Escapes these characters: ## ## ------------ ------------------- ## char is converted to ## ------------ ------------------- ## ``<`` ``<`` ## ``>`` ``>`` ## ``&`` ``&`` ## ``"`` ``"`` ## ------------ ------------------- result = newStringOfCap(s.len) addEscaped(result, s) proc addIndent(result: var string, indent: int) = result.add("\n") for i