cXml class
A cXml holds one XML document. It reads the document from a string or a file, or starts a new one with a root element. It finds, reads and changes elements and attributes by a path such as library/book, and writes the document back as a string or a file. The elements, text and comments of the document are cXmlNode objects, which the Document and RootElement properties and the search methods return.
An object is created with Dim x As New cXml. A new object holds no document. Parse, LoadFromFile, CreateNew or the XmlString property gives it one, and until then the members that take a path find nothing.
Dim x As New cXml
x.Parse "<library><book id=""1"">Dune</book><book id=""2"">Emma</book></library>"
Debug.Print x.GetElementText("library/book")
Debug.Print x.GetAttribute("library/book", "id")
Debug.Print x.SelectElements("book").Count
x.AddElement "library", "note"
x.SetElementText "library/note", "checked"
Debug.Print x.ToXmlString(False)
' Output:
' Dune
' 1
' 2
' <library><book id="1">Dune</book><book id="2">Emma</book><note>checked</note></library>
Warning
In version 1.0.0.0, the parser reads some valid XML wrongly and raises no error: a self-closing element, & and other entities in text, a tag that uses both kinds of quote or has a line break between its attributes, and a processing instruction. A comment, a CDATA section or a DOCTYPE declaration in the wrong place makes Parse loop forever. Defects in version 1.0.0.0 lists each case.
Paths
The members that take an xpath parameter find an element by a path: SelectElement, GetElementText, SetElementText, GetAttribute, SetAttribute, RemoveAttribute and RemoveElement, and AddElement for its parentXPath. The name of the parameter is misleading: the syntax is a small one of the package’s own and is not XPath.
- A path is a list of names separated by
/, such aslibrary/book. The search starts at the document node, so the first name is the name of the root element:library/bookfinds a book, andbookfinds nothing. - Each name selects the first child, of the node found so far, whose name equals it. Letter case is ignored. Only direct children count, and when several children have the name, the first one is taken.
- White space around a name is removed, and an empty name is ignored.
/library//bookandlibrary / bookboth meanlibrary/book. - A name is compared as a whole, so a namespace prefix is part of it:
ns:itemis the name of an element written<ns:item>. The package does no namespace processing. - Nothing else has a meaning. A position (
book[2]), an attribute (@id),*,.and..are names that no element has, so the path selects nothing.//bookdoes not search at any depth: the empty name is ignored, and the path meansbook. SelectElements searches at any depth, and the ChildNodes of a node reach the other children. - The nodes that are not elements have names too,
#text,#commentand#cdata-section, and a path can select them:library/#commentis the first comment directly inside the root element. - A path with no name, an empty string or
/, selects nothing, and the Debug Console showsSelectElement failed: Subscript out of range.
Dim x As New cXml
x.Parse "<library><book id=""1"">Dune</book><book id=""2"">Emma</book></library>"
Debug.Print "[" & x.GetElementText("book") & "]"
Debug.Print "[" & x.GetElementText("library/book") & "]"
Debug.Print "[" & x.GetElementText("/LIBRARY/Book") & "]"
Debug.Print "[" & x.GetElementText(" library // book ") & "]"
Debug.Print "[" & x.GetElementText("library/book[2]") & "]"
' Output:
' []
' [Dune]
' [Dune]
' [Dune]
' []
To reach the second book, SelectElements returns every book, or the ChildNodes of the parent can be searched:
Dim x As New cXml
x.Parse "<library><book id=""1"">Dune</book><book id=""2"">Emma</book></library>"
Dim books As Collection
Set books = x.SelectElements("book")
Debug.Print books(2).GetAttribute("id") & " " & books(2).ChildNodes(1).Value
Dim children As Collection
Set children = x.RootElement.GetChildNodesByName("book")
Debug.Print children(2).GetAttribute("id")
' Output:
' 2 Emma
' 2
Reporting failures
A member that cannot do its work returns a value that says so, and does not stop the program. Parse, LoadFromFile, SaveToFile, Validate and RemoveElement return False. SelectElement and AddElement return Nothing. GetElementText and GetAttribute return an empty string, and SelectElements an empty collection. A method that returns nothing, such as SetAttribute, does nothing.
When an error causes the failure, the member also:
- writes a line to the Debug Console that names the member and gives the error’s description, such as
Parse failed: XML string cannot be emptyorSelectElement failed: Document not initialized. In a built executable, Debug.Print writes nothing (see Debug), so the line appears in the IDE only; - leaves the error in the Err object. After the call returns, Err.Number, Err.Source and Err.Description describe it. A program reads them straight after the call, because the next call of most members clears them, and calls
Err.Clearbefore the call so that an earlier error does not remain in them.
The errors that occur are ERR_EMPTY_DOCUMENT, which Parse raises for an empty string and SelectElement for a document that has not been read or created, and the errors of the Visual Basic runtime that the code meets while it works: 5 (Invalid procedure call or argument) for a tag cut off by the end of the text, 9 (Subscript out of range) for a path with no name, and 91 (Object variable or With block variable not set) in RemoveElement for a path that selects nothing. The other error constants of mXml are not used.
The members that let an error reach the program are XmlString, ToXmlString and Merge.
Dim x As New cXml
Err.Clear
Debug.Print x.Parse("")
Debug.Print Err.Number = ERR_EMPTY_DOCUMENT
Debug.Print Err.Source & ": " & Err.Description
Err.Clear
Debug.Print (x.SelectElement("library") Is Nothing)
Debug.Print Err.Number = ERR_EMPTY_DOCUMENT
' Output:
' Parse failed: XML string cannot be empty
' False
' True
' cXml.Parse: XML string cannot be empty
' SelectElement failed: Document not initialized
' True
' True
Properties
Document
Returns the document node.
Syntax: object.Document As cXmlNode
- object
- required An object expression that evaluates to a cXml.
Read-only. The node has the NodeType Document and the Name #document. Its ChildNodes hold the root element and any comments and text outside it, in document order, including text that is only white space. A new object holds an empty document node. Parse, LoadFromFile, CreateNew and XmlString replace it.
Dim x As New cXml
x.Parse "<!-- top --><r></r>"
Debug.Print x.Document.Name & " " & x.Document.NodeType
Dim n As cXmlNode
For Each n In x.Document.ChildNodes
Debug.Print n.Name
Next
' Output:
' #document 9
' #comment
' r
Encoding
Returns or sets the encoding name that the XML declaration states.
Syntax: object.Encoding [ = value ]
- object
- required An object expression that evaluates to a cXml.
A String. The default is UTF-8, the value of DEFAULT_ENCODING. ToXmlString and SaveToFile write it into the declaration. Parse and LoadFromFile set it from the encoding of the declaration of the text they read, when the declaration has one written with double quotes. Without one, the property keeps the value it had, which is the value of an earlier document if the object read one. CreateNew leaves it as it is.
The property is a label. SaveToFile always writes UTF-8 and LoadFromFile always reads UTF-8, whatever the label says.
Dim x As New cXml
Debug.Print x.Encoding
x.Parse "<?xml version=""1.0"" encoding=""ISO-8859-1""?><r></r>"
Debug.Print x.Encoding
x.Encoding = "UTF-16"
Debug.Print x.ToXmlString()
' Output:
' UTF-8
' ISO-8859-1
' <?xml version="1.0" encoding="UTF-16"?><r/>
Initialized
Returns whether the object holds a document.
Syntax: object.Initialized As Boolean
- object
- required An object expression that evaluates to a cXml.
Read-only. It is False for a new object. It becomes True when Parse, LoadFromFile or XmlString reads text, even text with no element in it, and when CreateNew creates a document, and Clone and Merge can set it. A Parse that fails leaves it as it was.
While it is False, SelectElement returns Nothing, and the members built on it find nothing. SelectElements returns an empty collection, and Validate returns False.
Dim x As New cXml
Debug.Print x.Initialized
x.CreateNew "r"
Debug.Print x.Initialized
' Output:
' False
' True
RootElement
Returns the root element.
Syntax: object.RootElement As cXmlNode
- object
- required An object expression that evaluates to a cXml.
Read-only. It returns the first element directly inside the document node, or Nothing for a new object and for text that holds no element. Parse, LoadFromFile, XmlString, CreateNew and Clone set it. Nothing else changes it, so it can name an element that is no longer in the document; see Clone, Merge and the root element.
Dim x As New cXml
Debug.Print (x.RootElement Is Nothing)
x.Parse "<a><b>1</b></a>"
Debug.Print x.RootElement.Name
Debug.Print x.RootElement.ChildNodes.Count
' Output:
' True
' a
' 1
Version
Returns or sets the XML version that the XML declaration states.
Syntax: object.Version [ = value ]
- object
- required An object expression that evaluates to a cXml.
A String. The default is 1.0, the value of DEFAULT_XML_VERSION. ToXmlString and SaveToFile write it into the declaration. Parse and LoadFromFile set it from the version of the declaration of the text they read, when the declaration has one written with double quotes; otherwise the property keeps the value it had. The package does not check the value.
Dim x As New cXml
x.Parse "<?xml version=""1.1""?><r></r>"
Debug.Print x.Version
Debug.Print x.ToXmlString()
' Output:
' 1.1
' <?xml version="1.1" encoding="UTF-8"?><r/>
XmlString
Returns or sets the text of the document that was last read.
Syntax: object.XmlString [ = value ]
- object
- required An object expression that evaluates to a cXml.
A String. Assigning to it reads the text as Parse does and replaces the document. Reading it returns the text that Parse, LoadFromFile or an assignment last read. It is not made again from the document, so it does not show later changes: ToXmlString does. Clone does not copy it.
Assigning an empty string is not an error, unlike Parse with an empty string: it gives an initialised document with no nodes.
Note
Assigning text that the parser cannot read raises an error, where Parse returns False for the same text. Assigning < raises error 5, Invalid procedure call or argument. The text is kept, so reading the property returns it.
Dim x As New cXml
x.XmlString = "<r><a>1</a></r>"
Debug.Print x.GetElementText("r/a")
x.SetElementText "r/a", "2"
Debug.Print x.XmlString
Debug.Print x.ToXmlString(False)
' Output:
' 1
' <r><a>1</a></r>
' <r><a>2</a></r>
Methods
New
Creates a cXml object.
Syntax: New cXml
The constructor takes no arguments. The new object has an empty document node, the Version 1.0 and the Encoding UTF-8, and Initialized is False.
Dim x As New cXml
Debug.Print x.Initialized & " " & x.Version & " " & x.Encoding
' Output:
' False 1.0 UTF-8
AddElement
Adds an empty element as the last child of an element.
Syntax: object.AddElement ( parentXPath , elementName ) As cXmlNode
- object
- required An object expression that evaluates to a cXml.
- parentXPath
- required A String, the path of the element that receives the new one.
- elementName
- required A String, the name of the new element.
Returns the new cXmlNode, whose Parent is the element found. Returns Nothing, and adds nothing, when the path selects nothing. A document has one root element, which CreateNew or the text read gives it, so there is no way to add a second.
The name is not checked. An empty name or a name with a space is accepted, and Validate rejects an empty one. The new element has no text. SetElementText gives it some, by the path of the new element, which is the path of the parent and the name; when several siblings have the name, the path reaches the first. A program that holds the returned node can instead add a text node to its ChildNodes (see ChildNodes).
Dim x As New cXml
x.CreateNew "library"
Dim book As cXmlNode
Set book = x.AddElement("library", "book")
book.SetAttribute "id", "1"
x.SetElementText "library/book", "Dune"
Debug.Print (x.AddElement("library/shelf", "book") Is Nothing)
Debug.Print x.ToXmlString(False)
' Output:
' True
' <library><book id="1">Dune</book></library>
Clone
Returns a copy of the document.
Syntax: object.Clone ( ) As cXml
- object
- required An object expression that evaluates to a cXml.
Returns a new cXml with its own copy of every node, so that a change to one document does not reach the other. The copy has the same Version and Encoding, and is initialised when the original is. For an object that holds no document, the result holds none either. The XmlString of the copy is empty.
The copied nodes have no Parent, which stops RemoveElement from removing them; see Clone, Merge and the root element.
Dim x As New cXml
x.Parse "<r><a>1</a></r>"
Dim y As cXml
Set y = x.Clone()
y.SetElementText "r/a", "2"
Debug.Print x.GetElementText("r/a") & " " & y.GetElementText("r/a")
' Output:
' 1 2
CreateNew
Replaces the document with a new one that holds only a root element.
Syntax: object.CreateNew ( rootElementName )
- object
- required An object expression that evaluates to a cXml.
- rootElementName
- required A String, the name of the root element. It is not checked.
Discards the document and creates an empty root element with that name. Initialized becomes True. Version, Encoding and XmlString keep the values they had.
Dim x As New cXml
x.CreateNew "config"
Debug.Print x.ToXmlString()
' Output:
' <?xml version="1.0" encoding="UTF-8"?><config/>
GetAttribute
Returns an attribute of an element.
Syntax: object.GetAttribute ( xpath , attrName ) As String
- object
- required An object expression that evaluates to a cXml.
- xpath
- required A String, the path of the element.
- attrName
- required A String, the name of the attribute. Letter case is ignored.
Returns the value of the attribute, as GetAttribute of the element’s node does. Returns an empty string when the path selects nothing or the element has no such attribute, which a program cannot tell from an attribute whose value is empty.
Dim x As New cXml
x.Parse "<r><a id=""7"" empty=""""></a></r>"
Debug.Print "[" & x.GetAttribute("r/a", "id") & "]"
Debug.Print "[" & x.GetAttribute("r/a", "ID") & "]"
Debug.Print "[" & x.GetAttribute("r/a", "empty") & "]"
Debug.Print "[" & x.GetAttribute("r/a", "missing") & "]"
Debug.Print "[" & x.GetAttribute("r/nothing", "id") & "]"
' Output:
' [7]
' [7]
' []
' []
' []
GetElementText
Returns the text inside an element.
Syntax: object.GetElementText ( xpath ) As String
- object
- required An object expression that evaluates to a cXml.
- xpath
- required A String, the path of the element.
Returns the text of the element: the values of all the text nodes and CDATA sections inside it, at any depth, one after the other in document order with nothing between them. Comments add nothing. Text that is only white space is included, so the text of an element in an indented document holds the line breaks and the indentation. Entities in the text are returned as written; see Entities.
Returns an empty string when the path selects nothing, which a program cannot tell from an element with no text.
Dim x As New cXml
x.Parse "<p>Hello <b>big</b> world<!-- c --><![CDATA[ & more]]></p>"
Debug.Print "[" & x.GetElementText("p") & "]"
Debug.Print "[" & x.GetElementText("p/b") & "]"
Debug.Print "[" & x.GetElementText("p/i") & "]"
x.Parse "<r>" & vbCrLf & " <a>1</a>" & vbCrLf & "</r>"
Debug.Print Replace(Replace(x.GetElementText("r"), " ", "."), vbCrLf, "|")
' Output:
' [Hello big world & more]
' [big]
' []
' |..1|
LoadFromFile
Reads a document from a file.
Syntax: object.LoadFromFile ( filePath ) As Boolean
- object
- required An object expression that evaluates to a cXml.
- filePath
- required A String, the path of the file.
Reads the whole file, removes a UTF-8 byte order mark if the file starts with one, converts the bytes as UTF-8 with UTF8BytesToString and reads the text as Parse does. Returns True when the file was read.
Returns False, and writes LoadFromFile failed: and the description to the Debug Console, when the file cannot be opened, is empty, or has fewer than three bytes. It returns True when the file was read but the text then fails to parse, because it ignores the result of Parse; see Files. The file is read as UTF-8 whatever its declaration says, and the declaration’s encoding only sets Encoding.
Dim path As String
path = Environ$("TEMP") & "\tbxml_loadfromfile.xml"
Dim f As Long
f = FreeFile
Open path For Output As #f
Print #f, "<?xml version=""1.0"" encoding=""UTF-8""?>"
Print #f, "<config><name>demo</name></config>"
Close #f
Dim x As New cXml
Debug.Print x.LoadFromFile(path)
Debug.Print x.GetElementText("config/name")
Kill path
' Output:
' True
' demo
Merge
Appends the content of another document to this one.
Syntax: object.Merge ( otherXml )
- object
- required An object expression that evaluates to a cXml.
- otherXml
- required A cXml, the document to copy from. It is not changed.
Appends a copy of every child of the root element of otherXml, text and comments included, to the end of the root element of this document. The names and attributes of the two root elements are not compared or merged, and this document keeps its own. Nothing happens when otherXml holds no document.
When this object holds no document, it becomes a copy of the whole of otherXml, and Initialized becomes True. Its RootElement stays Nothing, so a second Merge into it does nothing.
The copies have no Parent; see Clone, Merge and the root element.
Note
Merge with Nothing as otherXml raises error 91, Object variable or With block variable not set, because the code tests the object and its property in one expression.
Dim a As New cXml
Dim b As New cXml
a.Parse "<list a=""1""><x>1</x></list>"
b.Parse "<other b=""2""><y>2</y></other>"
a.Merge b
Debug.Print a.ToXmlString(False)
Debug.Print b.ToXmlString(False)
' Output:
' <list a="1"><x>1</x><y>2</y></list>
' <other b="2"><y>2</y></other>
Parse
Reads a document from a string.
Syntax: object.Parse ( xml ) As Boolean
- object
- required An object expression that evaluates to a cXml.
- xml
- required A String, the text to read.
Replaces the document with the one in the text, sets RootElement and XmlString, and returns True. The version and encoding of an XML declaration, written with double quotes, set Version and Encoding.
The parser builds these nodes: elements with their attributes, text, comments and CDATA sections. Text that is only white space is kept, in the document and inside elements. An end tag closes the innermost open element whatever its name is, and an element still open at the end of the text is closed by it. Processing instructions and the declaration are not kept.
Returns False, and writes Parse failed: and the description to the Debug Console, for an empty string and for a tag that the end of the text cuts off, such as < or <a. Any other text gives True: the parser does not check that the text is well formed XML, so text that is not XML, an unclosed element and an end tag that names another element all succeed. Validate detects almost none of them. A Parse that fails part-way has already replaced the document with what it had read; see Malformed text and failed parses.
For text that holds a comment or a CDATA section with no end, or a DOCTYPE declaration, Parse may never return; see Comments, CDATA sections and declarations.
Dim x As New cXml
Debug.Print x.Parse("<?xml version=""1.1"" encoding=""ISO-8859-1""?><r a=""1""><b>text</b></r>")
Debug.Print x.Version & " " & x.Encoding & " " & x.RootElement.Name
Debug.Print x.Parse("")
Debug.Print x.Parse("not xml")
Debug.Print x.Parse("<a><b></a>")
Debug.Print x.Parse("<r")
' Output:
' True
' 1.1 ISO-8859-1 r
' Parse failed: XML string cannot be empty
' False
' True
' True
' Parse failed: Invalid procedure call or argument
' False
RemoveAttribute
Removes an attribute from an element.
Syntax: object.RemoveAttribute ( xpath , attrName )
- object
- required An object expression that evaluates to a cXml.
- xpath
- required A String, the path of the element.
- attrName
- required A String, the name of the attribute. Letter case is ignored.
Removes the first attribute whose name matches, with its value. Nothing happens, and no error is raised, when the path selects nothing or the element has no such attribute.
Dim x As New cXml
x.Parse "<r a=""1"" b=""2""></r>"
x.RemoveAttribute "r", "A"
x.RemoveAttribute "r", "missing"
x.RemoveAttribute "nothing", "b"
Debug.Print x.ToXmlString(False)
' Output:
' <r b="2"/>
RemoveElement
Removes an element, with everything inside it.
Syntax: object.RemoveElement ( xpath ) As Boolean
- object
- required An object expression that evaluates to a cXml.
- xpath
- required A String, the path of the element.
Takes the first element that the path selects out of the ChildNodes of its parent, and returns True. Returns False when the path selects nothing, and for a node that has no Parent, which includes the nodes that Clone and Merge copy. In the first case the Debug Console shows RemoveElement failed: Object variable or With block variable not set.
Removing the root element leaves RootElement as it was; see Clone, Merge and the root element.
Dim x As New cXml
x.Parse "<r><a>1</a><a>2</a><b></b></r>"
Debug.Print x.RemoveElement("r/a")
Debug.Print x.GetElementText("r/a")
Debug.Print x.RemoveElement("r/missing")
' Output:
' True
' 2
' RemoveElement failed: Object variable or With block variable not set
' False
SaveToFile
Writes the document to a file.
Syntax: object.SaveToFile ( filePath [ , withDeclaration ] ) As Boolean
- object
- required An object expression that evaluates to a cXml.
- filePath
- required A String, the path of the file. An existing file is deleted first.
- withDeclaration
- optional A Boolean. True (the default) writes the XML declaration before the document.
Writes the three bytes of a UTF-8 byte order mark, then the document as ToXmlString returns it with withDeclaration and no other option, converted with StringToUTF8Bytes. The text is never indented. Returns True when the file was written, and False, with SaveToFile failed: and the description in the Debug Console, when it could not be, for example because the folder does not exist.
The file is UTF-8 whatever Encoding says, and the declaration states that value. A document with no content written without a declaration is the byte order mark followed by one zero byte.
Dim path As String
path = Environ$("TEMP") & "\tbxml_savetofile.xml"
Dim x As New cXml
x.CreateNew "note"
x.AddElement "note", "to"
x.SetElementText "note/to", "Ada"
Debug.Print x.SaveToFile(path)
Dim f As Long
f = FreeFile
Open path For Binary Access Read As #f
Dim head(0 To 2) As Byte
Get #f, , head
Close #f
Debug.Print Hex$(head(0)) & " " & Hex$(head(1)) & " " & Hex$(head(2))
Dim y As New cXml
Debug.Print y.LoadFromFile(path)
Debug.Print y.GetElementText("note/to")
Kill path
' Output:
' True
' EF BB BF
' True
' Ada
SelectElement
Returns the element that a path selects.
Syntax: object.SelectElement ( xpath ) As cXmlNode
- object
- required An object expression that evaluates to a cXml.
- xpath
- required A String, the path of the element.
Returns the cXmlNode that the path selects, or Nothing when it selects nothing. It also returns Nothing when Initialized is False and when the path holds no name, and in both cases the Debug Console shows a line that begins SelectElement failed:.
The other members that take a path call this one.
Dim x As New cXml
x.Parse "<library><book id=""1"">Dune</book><book id=""2"">Emma</book></library>"
Dim n As cXmlNode
Set n = x.SelectElement("library/book")
Debug.Print n.GetAttribute("id") & " " & n.ChildNodes(1).Value
Debug.Print (x.SelectElement("library/magazine") Is Nothing)
' Output:
' 1 Dune
' True
SelectElements
Returns every node with a given name.
Syntax: object.SelectElements ( tagName ) As Collection
- object
- required An object expression that evaluates to a cXml.
- tagName
- required A String, one name and not a path. Letter case is ignored.
Returns a new Collection of the cXmlNode objects, at any depth in the document, whose Name matches, in document order. An element inside another element of the same name is returned too. The collection is empty when nothing matches and when Initialized is False; the method never returns Nothing.
The names of the nodes that are not elements match, so #text returns every text node. The method does not read paths: r/a matches nothing.
Dim x As New cXml
x.Parse "<r><a>1</a><b><a>2</a></b><A>3</A></r>"
Dim found As Collection
Set found = x.SelectElements("a")
Debug.Print found.Count
Dim n As cXmlNode
For Each n In found
Debug.Print n.Name & " " & n.ChildNodes(1).Value
Next
Debug.Print x.SelectElements("r/a").Count
' Output:
' 3
' a 1
' a 2
' A 3
' 0
SetAttribute
Sets an attribute of an element.
Syntax: object.SetAttribute ( xpath , attrName , attrValue )
- object
- required An object expression that evaluates to a cXml.
- xpath
- required A String, the path of the element.
- attrName
- required A String, the name of the attribute.
- attrValue
- required A String, the value.
Adds the attribute, or replaces the one with the same name, as SetAttribute of the element’s node does: a replaced attribute moves to the end of the element’s attributes. Nothing happens, and no error is raised, when the path selects nothing.
Dim x As New cXml
x.CreateNew "r"
x.SetAttribute "r", "a", "1"
x.SetAttribute "r", "b", "2"
x.SetAttribute "r", "a", "3"
x.SetAttribute "r/missing", "z", "1"
Debug.Print x.ToXmlString(False)
' Output:
' <r b="2" a="3"/>
SetElementText
Replaces the text of an element.
Syntax: object.SetElementText ( xpath , text )
- object
- required An object expression that evaluates to a cXml.
- xpath
- required A String, the path of the element.
- text
- required A String, the new text, as plain text: ToXmlString escapes it when it writes it.
Removes the text nodes directly inside the element and adds one text node that holds text, after the element’s other children. The child elements, comments and CDATA sections stay, so the text moves to the end of an element with mixed content. Setting an empty string leaves an empty text node, which makes ToXmlString write <c></c> and not <c/>. Nothing happens, and no error is raised, when the path selects nothing.
Dim x As New cXml
x.Parse "<r><a>one<b>bee</b>two</a></r>"
x.SetElementText "r/a", "NEW"
x.SetElementText "r/missing", "lost"
Debug.Print x.ToXmlString(False)
' Output:
' <r><a><b>bee</b>NEW</a></r>
ToXmlString
Returns the document as text.
Syntax: object.ToXmlString ( [ withDeclaration [ , prettyPrint [ , indent ] ] ] ) As String
- object
- required An object expression that evaluates to a cXml.
- withDeclaration
- optional A Boolean. True (the default) starts the text with a declaration such as
<?xml version="1.0" encoding="UTF-8"?>, written from Version and Encoding, with no line break after it. - prettyPrint
- optional A Boolean. False (the default) writes the document compactly: nothing is added between the nodes. True puts each element, comment, CDATA section and text on a line of its own, ends every line with a carriage return and a line feed, and leaves out the text nodes that are only white space. See Pretty printing for what the indentation looks like.
- indent
- optional A String, the indentation for each level of nesting. The default is two spaces. Only the first character of the string is used.
The text holds elements, text, comments and CDATA sections. An element with no child is written as <name/>, and an element with children as <name>...</name>. The characters &, <, >, " and ' are replaced by &, <, >, " and ' in text and in the values of attributes. Comments and CDATA sections are written as they are. The attributes of an element are written in the order of its attribute list.
A document that holds nothing gives the declaration alone, or an empty string without one.
Note
An empty indent with prettyPrint True raises error 5, Invalid procedure call or argument, as soon as the document has an element.
Dim x As New cXml
x.CreateNew "r"
x.SetAttribute "r", "id", "1"
x.AddElement "r", "a"
x.SetElementText "r/a", "x < y"
x.AddElement "r", "empty"
Debug.Print x.ToXmlString()
Debug.Print x.ToXmlString(False)
' Output:
' <?xml version="1.0" encoding="UTF-8"?><r id="1"><a>x < y</a><empty/></r>
' <r id="1"><a>x < y</a><empty/></r>
Validate
Checks that every element and attribute has a name.
Syntax: object.Validate ( ) As Boolean
- object
- required An object expression that evaluates to a cXml.
Returns True when the object holds a document and every element in it has a name that is not empty and does not begin with /, and every attribute has a name that is not empty. Returns False otherwise, and for an object that holds no document.
That is all it checks. It does not test that the text was well formed, that the names are legal XML names, or anything about a schema or a DTD. In practice it detects an element added with an empty name, an attribute set with an empty name, and the element named /a that the parser makes from the self-closing tag <a/> (see Self-closing elements).
Dim x As New cXml
Debug.Print x.Validate()
x.Parse "<r><a></a></r>"
Debug.Print x.Validate()
x.Parse "<r><a/></r>"
Debug.Print x.Validate()
x.Parse "<a><b></a>"
Debug.Print x.Validate()
' Output:
' False
' True
' False
' True
Defects in version 1.0.0.0
Each of these sections describes a case in which the package gives a wrong result or never returns. The samples show the result of the version named here.
Self-closing elements
Warning
A self-closing tag is read wrongly. <a/>, a name and nothing else, makes an element named /a. A tag with attributes, or with a space before the slash, such as <a x="1"/> or <a />, is read as a start tag, so the elements that follow it become its children, and the end tag of the parent closes it. ToXmlString writes an empty element as <a/>, so text that a cXml wrote is read wrongly by a cXml.
Dim x As New cXml
x.Parse "<r><a/><b x=""1""/><c>t</c></r>"
Debug.Print x.ToXmlString(False)
Debug.Print "[" & x.GetElementText("r/c") & "]"
Debug.Print x.GetElementText("r/b/c")
Debug.Print x.Validate()
x.CreateNew "r"
x.AddElement "r", "empty"
Dim y As New cXml
y.Parse x.ToXmlString(False)
Debug.Print y.RootElement.ChildNodes(1).Name
' Output:
' <r></a/><b x="1"><c>t</c></b></r>
' []
' t
' False
' /empty
Entities and character references
Warning
Entities in text are not decoded. GetElementText and the Value of a text node return &, < and A as they stand in the XML, and ToXmlString escapes the & again, so each read and write adds amp;. In the value of an attribute the five predefined entities <, >, &, " and ' are decoded, in that order, so &quot; becomes a quotation mark. Numeric character references such as A and A are not decoded anywhere.
Dim x As New cXml
x.Parse "<a title=""Tom & Jerry A"">Tom & Jerry</a>"
Debug.Print x.GetAttribute("a", "title")
Debug.Print x.GetElementText("a")
Debug.Print x.ToXmlString(False)
' Output:
' Tom & Jerry A
' Tom & Jerry
' <a title="Tom & Jerry &#65;">Tom &amp; Jerry</a>
Text set with SetElementText or SetAttribute is plain text and is written correctly, once. A program that reads text with entities in it has to decode them itself.
Tags
Warning
A tag is cut apart with plain searches, so these valid forms give wrong attributes and no error. A tag that uses both kinds of quote loses an attribute: the first double quote is taken as the start of the first value, even when a single-quoted value comes before it. A > inside an attribute value ends the tag at that point, and the rest of the value becomes text. A line break between two attributes becomes the name of an attribute, and the real name is lost. A line break between the element name and the first attribute becomes part of the element’s name. An attribute with no value takes the value of the next one.
Dim x As New cXml
x.Parse "<a b='1' c=""2""></a>"
Debug.Print x.GetAttribute("a", "b") & "|" & x.GetAttribute("a", "c")
x.Parse "<a b=""x>y""></a>"
Debug.Print x.GetAttribute("a", "b") & "|" & x.GetElementText("a")
x.Parse "<a b=""1""" & vbCrLf & " c=""2""></a>"
Debug.Print x.GetAttribute("a", "b") & "|" & x.GetAttribute("a", "c") & "|" & x.RootElement.Attributes.Count
x.Parse "<a" & vbLf & "b=""1""></a>"
Debug.Print Replace(x.RootElement.Name, vbLf, "\n")
x.Parse "<a disabled c=""2""></a>"
Debug.Print x.GetAttribute("a", "disabled") & "|" & x.GetAttribute("a", "c")
' Output:
' 2|
' |y">
' 1||4
' a\nb="1"
' 2|
A tab between the attributes is read correctly, and so is a space around the =.
Comments, CDATA sections and declarations
Warning
Parse never returns for text that holds a comment with no -->, a CDATA section with no ]]>, or a <! declaration such as <!DOCTYPE, unless the declaration comes first in the text. A <! item that is not a comment or a CDATA section is skipped up to the next ?> anywhere later in the text, which discards everything between, or, when there is none, up to the first > of the whole text, which is the end of the item itself only when the item is the first thing in the text. In every other position the search goes back to an earlier >, and the parser reads the item again, without end. A processing instruction <?target data?> is skipped, but the > that ends it stays behind as a text node.
These calls return, and show how a processing instruction and a DOCTYPE declaration are read:
Dim x As New cXml
x.Parse "<root><?pi data?><a>x</a></root>"
Debug.Print "[" & x.GetElementText("root") & "]"
x.Parse "<!DOCTYPE note SYSTEM ""Note.dtd""><note>text</note>"
Debug.Print "[" & x.GetElementText("note") & "]"
Dim y As New cXml
y.Parse "<?xml version=""1.0""?><!DOCTYPE note><note>text</note><?pi?>"
Debug.Print (y.RootElement Is Nothing)
' Output:
' [>x]
' [text]
' True
Each of these calls never returns. The sample is compiled and not run:
Dim x As New cXml
x.Parse "<root><!-- never closed </root>"
x.Parse "<root><![CDATA[ never closed </root>"
x.Parse "<?xml version=""1.0""?><!DOCTYPE root><root></root>"
x.Parse "<root><!DOCTYPE x><a>1</a></root>"
Malformed text and failed parses
Warning
A Parse that returns False has already thrown away the previous document. The document is replaced before the text is read, so it holds what was read before the failure, which is nothing when the failure is at the start. Initialized is not changed, so after a failed Parse into an object that held a document, the path members work on the part that was read.
Dim x As New cXml
x.Parse "<a>x</a>"
Debug.Print x.Parse("<")
Debug.Print x.Initialized & " " & x.Document.ChildNodes.Count
Debug.Print "[" & x.GetElementText("a") & "]"
' Output:
' Parse failed: Invalid procedure call or argument
' False
' True 0
' []
Pretty printing
Warning
ToXmlString with prettyPrint True indents one level with the first character of indent alone, so the default of two spaces indents by one space, and vbTab & vbTab by one tab. It also puts the text of an element on lines of its own, so the text of the element in the written document is the text with line breaks and indentation around it. Reading it back with GetElementText returns them. The text changes each time the document is written and read.
Dim x As New cXml
x.CreateNew "r"
x.AddElement "r", "a"
x.SetElementText "r/a", "t"
x.AddElement "r", "empty"
Dim s As String
s = x.ToXmlString(False, True)
Debug.Print Replace(Replace(s, " ", "."), vbCrLf, "|")
Dim y As New cXml
y.Parse s
Debug.Print Replace(Replace(y.GetElementText("r/a"), " ", "."), vbCrLf, "|")
' Output:
' <r>|.<a>|..t|.</a>|.<empty/>|</r>|
' |..t|.
Clone, Merge and the root element
Warning
The nodes that Clone copies and Merge adds have no Parent, so RemoveElement returns False for them and removes nothing. RootElement is set only when a document is read, created or cloned, so it keeps naming the old root after RemoveElement removes the root element and after Parse reads text that has no element. A Merge into an object that holds no document sets Initialized and leaves RootElement Nothing, so later merges into it do nothing.
Dim x As New cXml
x.Parse "<r><a>1</a></r>"
Dim y As cXml
Set y = x.Clone()
Debug.Print y.RemoveElement("r/a")
Debug.Print x.RemoveElement("r")
Debug.Print x.RootElement.Name
Debug.Print "[" & x.ToXmlString(False) & "]"
x.Parse "just text"
Debug.Print x.RootElement.Name
Dim c As New cXml
c.Merge y
Debug.Print c.Initialized & " " & (c.RootElement Is Nothing)
' Output:
' False
' True
' r
' []
' r
' True True
A byte order mark in a string
Warning
Parse and the XmlString property cut three characters from the start of a string that begins with the character U+FEFF, the character that a byte order mark decodes to. Only the first of the three is the mark: the next two are the start of the document. LoadFromFile removes the mark from the bytes of the file first, so a file is not affected.
Dim x As New cXml
x.Parse ChrW(&HFEFF) & "<root><a>t</a></root>"
Debug.Print x.RootElement.Name
' Output:
' a
Files
Warning
LoadFromFile creates an empty file when the path names a file that does not exist, before it returns False. It returns True after the text of an existing file fails to parse, since the result of Parse is not tested. SaveToFile writes UTF-8 whatever Encoding says, so a document labelled with another encoding is mislabelled in the file.
Dim path As String
path = Environ$("TEMP") & "\tbxml_files.xml"
If Dir(path) <> "" Then Kill path
Dim x As New cXml
Debug.Print x.LoadFromFile(path)
Debug.Print (Dir(path) <> "") & " " & FileLen(path)
Kill path
x.CreateNew "r"
x.Encoding = "ISO-8859-1"
x.AddElement "r", "t"
x.SetElementText "r/t", ChrW(233)
x.SaveToFile path
Dim y As New cXml
y.LoadFromFile path
Debug.Print y.Encoding & " " & AscW(y.GetElementText("r/t"))
Kill path
Dim f As Long
f = FreeFile
Open path For Output As #f
Print #f, "<r"
Close #f
Debug.Print y.LoadFromFile(path)
Kill path
' Output:
' LoadFromFile failed: Unspecified error
' False
' True 0
' ISO-8859-1 233
' Parse failed: Invalid procedure call or argument
' True
A file with fewer than three bytes cannot be loaded, and an empty file gives False: the code reads three bytes before it tests the length.
See Also
- cXmlNode class – the nodes of the document
- mXml module – the node types, the error numbers and the UTF-8 functions
- TBMANLIB_Xml package – overview