IXml and IXmlNode interfaces

IXml and IXmlNode interfaces are used to work with XML.

XML is a text file format used to store data or settings. It can be used to store tables as well as hierarchical data structures (trees) of any format. To save tables, CSV or Sqlite often is better and easier to use. You can find many information about XML on the Internet.

 

XML format is similar to HTML. Example:

 

<note>
<to>Tove</to>
<from>Jani</from>
<heading>Reminder</heading>
<body>Don't forget me this weekend!</body>
</note>

 

You can find many examples on your computer, since many programs save their settings and data in XML format. Press Win+F and search for *.xml files.

 

Alternatively can be used MSXML. It has more features. It is a COM component, included in Windows. You can find an example in the QM forum. However different Windows versions have different MSXML versions. Also it is quite slow. IXml works on all Windows versions, is faster, uses less memory and does not load any dlls. IXml supports basic XML features. However it does not help you to work with advanced XML features, such as namespaces, DTD, schemas, XPath (partially supported), custom entities, etc. In most cases it successfully parses and composes documents that use these features, but does not help you to manage the advanced features.

 

Added in QM 2.3.0.

 

To create an XML object, use function CreateXml or _create (QM 2.3.4). To work with it, use IXml and IXmlNode interfaces. Examples:

 

 This macro loads XML file and adds several new nodes.

out
IXml x._create

x.FromFile("$qm$\test.xml") ;;load XML file
err out "Error: %s" x.XmlParsingError; ret ;;error if the file is corrupted

IXmlNode my=x.RootElement.Add("myelement") ;;add one new element as child of the root element
my.Add("mysubelement" "text of my subelement") ;;add its child element
my.Add("mysubelement2" "some text").SetAttribute("a" "my attribute") ;;add another child element and an attribute

str s
x.ToString(s) ;;compose xml string
out s

 

 This macro creates new XML document, adds elements, attributess, finds and gets values.

out
IXml x._create

x.Add("?xml") ;;add xml declaration (optional)
IXmlNode re=x.Add("rootelem") ;;add root element (XML must have exactly 1 root element)
re.Add("child" "text").SetAttribute("a" "10") ;;add child element with text and 1 attribute

IXmlNode e=re.Add("elem2") ;;add another child element
e.Add("cc" "text of cc") ;;add child of child
e=e.Add("cc2") ;;add another child of child
e.SetAttribute("a" "AAA") ;;add attribute
e.SetAttribute("b" "BBB") ;;add another attribute

str v1=re.ChildValue("child") ;;get value of a child (same as re.Child("child").Value)
e=x.Path("rootelem/elem2/cc2/@b") ;;find a node by path
str v2=e.Value ;;get its value
out v1
out v2

out "-----"
str s
x.ToString(s) ;;compose xml string
out s

 

 /
function IXml&xml [withAttr]

 This function displays all XML nodes and their properties.
 To test it, create new function, name it XmlOut and paste this code.

 EXAMPLE
 IXml x=CreateXml
 x.FromFile("$my qm$\test.xml")
 XmlOut x 1


lpstr st="root[]el[]a[]text[]xml[]DOC[]PI[]CD[]comm"
ARRAY(str) at=st

ARRAY(IXmlNode) a; int i
xml.Root.GetAll(withAttr!=0 a)

for(i 0 a.len)
	XMLNODE xi; a[i].Properties(&xi)
	out "%-15s %-4s F=0x%X L=%i V='%s'", xi.name, at[xi.xtype], xi.flags, xi.level, xi.value

 

XML nodes

Example XML, containing all node types:

 

<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/xsl" href="example.xsl"?>
<!DOCTYPE example >
<!--this is an example xml-->
<example>
<simple>
<elem>text</elem>
text before<a>left<b>center</b>right</a>after
<elem>text again</elem>
</simple>
<entities>&lt;&gt;&amp;&quot;&apos;</entities>
<unicode>&#2345; &#x5678; &#x10FFFF;</unicode>
<attr-and-text a="aaa" b="bbb" c="ccc">text</attr-and-text>
<empty />
text after empty
<notext></notext>
<empty-attr a="1" />
<notext-attr b=""></notext-attr>
<lang name="qm">
<styles>
<s32 f="Courier' &quot;New" fs="8">Default</s32>
<s1 u="1">Tabs</s1>
<?pi abc?>
</styles>
<misc>
<multiline>
line1
line2
</multiline>
<mixed-content>
line1
<?pi intext?>
line2
</mixed-content>
</misc>
</lang>
<![CDATA[can contain < & etc]]>
<!--<rem>text</rem>-->
</example>

XML node types and properties used by IXml and IXmlNode:

Node type Constant Examples Name Value Can have children Can have attributes Can be at the root Can be not at the root
virtual root XT_Root 0 Not used in XML. Yes
element XT_Element 1

<elem>text</elem>

 

<example></example>

 

<empty />

 

<empty-attr a="1" />

 

<s1 u="1">Tabs</s1>

name Text between <elem> and </elem>. Empty if <elem /> or <elem></elem> or has child nodes. Yes Yes Yes, 1 Yes
attribute XT_Attribute 2

a="1"

 

encoding="utf-8"

name Attribute value. It is text within " or '.
text XT_Text 3

text

 

line1

line2

The text, including all spaces and leading/trailing new lines. Note that this element type can be found only where the parent element also has other child nodes. If not, text is interpreted as value of the parent element, not as a separate node. Yes
xml declaration XT_XmlDeclaration 4 <?xml version="1.0" encoding="utf-8" ?> ?xml Yes Yes, 1, first
DOCTYPE (document type) XT_DocumentType 5 <!DOCTYPE example > !DOCTYPE Text between <!DOCTYPE and >, not including leading and trailing spaces. QM does not parse it. Yes, 1, before root element
processing instruction XT_ProcessingInstruction 6 <?xml-stylesheet type="text/xsl" href="example.xsl"?> ?name Text between <?instructionname and ?>, not including leading and trailing spaces. QM does not parse it. Yes Yes
CDATA (custom data) XT_CDATA 7 <![CDATA[can contain < & etc]]> ![ Text between <![CDATA[ and ]]>, including all spaces. Yes
comment XT_Comment 8

<!--this is an example xml-->

 

<!--<rem>text</rem>-->

!- Text between <!-- and -->, including all spaces. Yes Yes

 

Note that XML element types 'end element' and 'white space' are not used. It is managed automatically.

Interfaces

There are two COM interfaces.

 

Interface IXml represents an XML document. It is responsible for loading, saving, parsing and composing XML. It manages XML nodes and has functions to access them.

 

Interface IXmlNode represents a node in the XML document. It has functions to get and set node values and other properties, find other nodes, add child nodes.

 

Global functions

 

IXml'CreateXml [flags] ;;flags: 1 normalize newlines, 2 enable UserData, 4 ignore encoding, 8 auto save, 0x100 safe save, 0x200 safe save+backup

 

Creates XML object and returns IXml COM interface pointer.

 

flags - same as with function Flags, see below.

 

QM 2.3.4. You can instead use _create.

 

IXml member functions

Member functions are called like: variable.Function(arguments). See the example code at the top of this topic. Note that the colored code lines below are not function calling examples. They are copied from interface declaration and used here to show function name, arguments etc.

 


[p]Flags(flags) ;;1 normalize newlines, 2 enable UserData, 4 ignore encoding, 8 auto save, 0x100 safe save, 0x200 safe save+backup
[g]#Flags()

 

Sets or gets flags to change default behavior of other functions.

 

flags:

1 When parsing, replace various new line forms ([], [13]) to [10] (XML standard). When composing, make all new lines []. If this flag is not set, does not touch new lines.
2 Enable IXmlNode.UserData, which is disabled by default.
4 When parsing XML, ignore "encoding" attribute in xml declaration. Read more below, in Notes chapter.
8 Autosave. After successful FromFile, before destroying the IXml object automatically saves to the same file. However will not save if you after call FromString.
0x100 QM 2.4.0. Safe/atomic saving. This flag is added to ToFile flags.
0x200 QM 2.4.0. Safe saving and backup. This flag is added to ToFile flags.

 

Added in QM 2.3.4.

 


[g]IXmlNode'Root()

 

Gets the virtual root node. It actually does not exist in XML, but can be used as parent of XML root nodes (xml declaration, DOCTYPE, root element, etc).

 

When you read "get node", "get element" or "get attribute", it means "get IXmlNode interface pointer that can be used to manipulate the node".

 


[g]IXmlNode'RootElement()

 

Gets the root element. For example, in XML <?xml version="1.0"?><elem><elem2>text</elem2></elem>, the root element is elem.

 


IXmlNode'Path($path [ARRAY(IXmlNode)&allMatching])

 

Gets any node by path. The IXmlNode interface also has these functions, look there.

 


IXmlNode'Add($name [$value])

 

Adds a node at the root. The IXmlNode interface also has this function, and it is documented there. Note that xml declaration and DOCTYPE nodes are always added where they should be, even if more nodes already exist at the root.

 


Delete(IXmlNode&node)

 

Delete a node. The argument must be a variable of IXmlNode type. If it is an element, also deletes its attributes, text and descendant nodes. Can be used to delete an attribute too.

 


Clear()

 

Delete all nodes.

 


[g]$XmlParsingError

 

If FromString or FromFile failed to parse XML, gets the place (substring) in XML that has error.

 


[g]#Count()

 

Gets the number of nodes in XML, including attributes.

 


IXmlNode'FromString($s)

 

Parses an XML string and creates a tree of node objects in memory. Returns root element.

 


ToString(str&so)

 

Composes XML string from the tree.

 


IXmlNode'FromFile($file [$defaultXML])

 

Parses an XML file and creates a tree of node objects in memory. Returns root element.

 

Error if the file does not exist, unless you provide defaultXML.

 

defaultXML - if the file does not exist, initializes the object (like FromString). The XML must contain at least the root element. Example: "<r/>".

 

After successful FromFile, the object remembers file, and can use it to autosave (see CreateXml flag 8) and with ToFile. The object loses this memory after FromString or unsuccessful FromFile.

 

Supports macro resources (QM 2.4.1) and exe resources.

 


ToFile([$file] [flags]) ;;flags: 0x100 safe, 0x200 safe+backup

 

Saves to an XML file.

 

flags (QM 2.4.0):

0x100 QM 2.4.0. Safe/atomic saving. The file will never be corrupted on power failure etc. Writes to a temporary file, flushes its buffers, and renames the temporary file to file, replacing if exists.
0x200 QM 2.4.0. Safe saving and backup. Same as 0x100, but also creates a backup file, named file-backup.

 

If file is omitted or empty, uses the same file as used with FromFile.

Flags 0x100 and 0x200 also can be specified earlier with Flags or CreateXml. It is useful when you use the auto-save feature.

 

QM 2.3.5. Creates parent folder if does not exist.

 

IXmlNode member functions

 

[g]IUnknown'XmlDoc()

 

Gets parent IXml.

 


[g]IXmlNode'Parent()
[g]IXmlNode'Prev()
[g]IXmlNode'Next()
[g]IXmlNode'FirstChild()
[g]IXmlNode'LastChild()

 

Gets parent, sibling or child node. The first three functions can be used with attribute nodes too.

 


[g]IXmlNode'Child($name [index])

 

Gets a child node by name and/or index. Tip: if you need its value, you can instead use ChildValue.

 

name - child node name.

index - 1-based match index. If name is "*", it is index in all children, else - in children whose name is name. If omitted or 0 or 1, gets first matching child.

 


[g]IXmlNode'Attribute($name)

 

Gets an attribute node. If name is "*", gets first attribute. If name is "", gets last attribute. Tip: if you need its value, you can instead use AttributeValue.

 


IXmlNode'Path($path [ARRAY(IXmlNode)&allMatching] [flags])

 

Gets any node by path.

 

The IXml interface also has this function.

 

path - node path, like "rootElement/itsChild/theNode".

allMatching - array variable that receives IXmlNode objects of all matching nodes. Optional, can be 0.

flags (QM 2.3.5):

1 Search in all matching paths (like XPath). For example if path is "elem1/elem2", searches for "elem2" in all "elem1". Without this flag searches only in the first matching path (in the example - in the first found "elem1").
2 If last part of path is "*", get only elements (like XPath). Without this flag gets nodes of any type except attributes.

 

Examples of supported paths:

 

"node" Get child node "node", like Child does. The advantage is that here you can use allMatching.
"elem/node" Get child node "node" of element "elem".
"elem/@a" Get attribute "a" of element "elem".
"*" Get first child node, like FirstChild does. If allMatching is used, it receives all child nodes.
"elem/*" Get first child node of element "elem".
"elem/@*" Get first attribute of element "elem".
"*/node"

Get child node "node" of the first or any (flag 1) child element.

"../node" Get child node "node" of the parent element.
"/*/node" QM 2.3.5. Get child node "node" of the root element.
".//node" QM 2.3.5. Get any descendant node "node". If "//node", always searches from the root.

 

An element name can be followed by a filter expression. Examples:

"elem[='abc']" Get element "elem" whose value is "abc".
"*[='abc']" Get first node whose value is "abc".
"elem[@id='abc']" Get element "elem" that has attribute "id" whose value is "abc".
"elem[@id*='ab*']" Get element "elem" that has attribute "id" whose value begins with "ab".
"elem[node='abc']" Get element "elem" that has a child "node" whose value is "abc".
"elem[node*='ab*']" Get element "elem" that has a child "node" whose value begins with "ab".
"elem[.='abc']" QM 2.3.5. Same as "elem[='abc']".
"elem[@id>0]" QM 2.3.5. Get element "elem" if numeric value of its attribute "id" is > 0.
"elem[node]" QM 2.3.5. Get element "elem" if it has child node "node".
"elem[@id]" QM 2.3.5. Get element "elem" if it has attribute "id".

 

Filter expression operators:

=

If the right part is enclosed in ', compare as strings, case insensitive.

Else compare as integer numbers (QM 2.3.5, in older QM use #=).

*= Compare as strings with wildcard characters, case insensitive.
! Logical not. Can be used before other operators. For example, != means not equal.
>, <, >=, <=, & QM 2.3.5. Compares as integers.

 

Filter expressions can be anywhere in path. Example: "elem1[@id='abc']/elem2".

Filter expressions can be used with all node types except attributes.

 

New in QM 2.3.5:

 

Tip: To create path where some parts of it are variables, use operator F or str.format.

 

See also: GetAll

 


[g]$Name()

 

Gets name of the node. Can be used with attributes too.

 


[g]$Value()
[p]Value($value)

 

Gets or set value of the node. Can be used with attributes too.

 

When used to set value, error if CDATA value contains ]]> or comments value contains --. Values of elements and attributes can contain any characters because special characters are replaced with XML escape sequences.

 


ValueBinaryGet(str&value)
ValueBinarySet(str&value [flags]) ;;flags: 1 compress, 2 hex

 

ValueBinarySet - sets value of the node.

 

value - a str variable. It can contain binary data. In XML file the data will be converted to text, because binary data cannot be used.

flags:

1 compress.
2 use Hex encoding (fast encoding/decoding, 100% bigger encoded string). If not set, uses Base64 encoding (fast encoding, slow decoding, 33% bigger encoded string).

 

ValueBinaryGet - gets value of the node. It must be set by ValueBinarySet.

 


[g]#Type()

 

Gets type of the node. It is a numeric value listed in the Nodes chapter above. Can be used with attributes too.

 


[g]#UserData()
[p]UserData(userdata)

 

Attaches a numeric value to the node, or gets the attached value. You can use the value for any purpose. It is used only in memory, and is not saved or included in XML string. By default this function is disabled. To enable it, use flag 2 with CreateXml. Can be used with attributes too.

 


Properties(XMLNODE&xi)

 

Gets all node properties using single function call. The argument must be a variable of type XMLNODE. Can be used with attributes too.

 

type XMLNODE $name $value @level !xtype !flags userdata

 

name, value, xtype, userdata - described above.

level - level of the node in the XML hierarchy.

flags:

1 the element has text.
2

the element has children.

  • Note that an element cannot have both text and children.
4 the element or xml declaration has attributes.
128 the attribute is in xml declaration.

 


[g]$ChildValue($name [index])

 

Gets value of a child node. Can be used instead of n.Child("name").Value.

 

Parameters are same as with Child.

 


[g]$AttributeValue($name)
[g]#AttributeValueInt($name)

 

AttributeValue - gets value of an attribute. Can be used instead of n.Attribute("name").Value.

 

AttributeValueInt - the same, but converts the value to integer number.

 


IXmlNode'Add($name [$value])

 

Adds a child node and optionally sets its value. Adds to the end of the list of children. Does not replace existing items with the same name (use SetChild instead). Not used to add attributes (use SetAttribute instead).

 


IXmlNode'Insert(IXmlNode'iafter $name [$value])

 

The same as Add, but adds after iafter, which must be a child node (variable of IXmlNode type) of the element. If iafter is 0, adds to the beginning.

 


IXmlNode'SetChild($name $value)

 

Adds or replaces a child node. If the child node exists, sets its value like Value does, else adds new node like Add does.

 


IXmlNode'SetAttribute($name $value)
IXmlNode'SetAttributeInt($name value)

 

SetAttribute - adds or replaces an attribute. Ensures that the element will not have duplicate attributes.

 

SetAttributeInt - the same, but value must be an integer number.

 


Move(IXmlNode'parent IXmlNode'iafter)

 

Moves an element to another place. Can be used only for elements.

 

parent - new parent element. If 0, moves within the same parent.

iafter - node after which to insert. If 0, inserts at the beginning.

 


GetAll(flags ARRAY(IXmlNode)&a)

 

Gets all descendants (direct children, their children, and so on).

 

flags:

1 include attributes.
2 get only direct children.

 

To get all nodes in whole XML, call this function for the virtual root node. See example at the beginning of this topic.

 

To get specific nodes, use Path instead. Example: x.Path(".//name" a).

 


[g]#ChildCount()

 

Gets number of direct child nodes.

 

To get number of specific child nodes, instead use Path with array. Then the number will be equal to the array length.

 


DeleteChild($name)

 

Deletes all child nodes that match name.

 

name - child node name.

 

Not error if there are no matching nodes. Then sets variable _hresult to 1.

 

Added in QM 2.4.0. In older QM versions would need to find child nodes, for example with Child or Path, and call IXml.Delete for each.

 


DeleteAttribute($name)

 

Deletes an attribute.

 

Not error if the attribute does not exist. Then sets variable _hresult to 1.

 

Added in QM 2.4.0. In older QM versions would need to call Attribute and IXml.Delete.

 

Notes

XML encoding

 

IXml can load ANSI and Unicode XML files. Unicode can be in UTF-8 or UTF-16 format.

 

When loading UTF-16 XML file, QM converts the loaded XML to UTF-8.

 

In Unicode mode, when loading ANSI XML file where 'encoding' attribute in xml declaration is ISO-8859-x or Windows-125x, QM converts the XML to UTF-8, unless flag 4 is used with CreateXml or the encoding is not supported by the OS.

 

In both cases, if you then call ToFile or ToString, it saves/gets XML in UTF-8 format.

 

UTF-8 is used everywhere in QM when it is running in Unicode mode. It is also the default encoding used in XML (used if the XML is not UTF-16 and does not contain a non UTF-8 'encoding' attribute in xml declaration).

 

In ANSI mode QM ignores the 'encoding' attribute. It does not convert UTF-8 XML to ANSI.

 

If you are creating XML while QM is running in ANSI mode, and the XML may contain non ASCII characters, and the XML file will be used not only in QM on your computer, you should add xml declaration with 'encoding' attribute. In Unicode mode it is not necessary because QM text encoding matches XML default text encoding (UTF-8).

 

XML validation and errors

 

When loading XML file or string, and when adding nodes and setting values, IXml validates it quite strictly. If the file is not well formed, or a name/value is invalid, it generates error. In some cases it silently makes corrections.

 

Functions that are used to get nodes don't generate error if the node does not exist. They set variable _hresult to 1 and return 0.

 

Other notes

 

These functions are not thread-safe. Don't use a single variable in multiple threads simultaneously. It can damage data. If need to use in multiple threads, use lock.

 

IXmlNode does not use reference counting. Don't use variables of this type after the parent IXml variable is destroyed or cleared or the node is deleted. Declare IXmlNode variables after (not before) the IXml variable. Otherwise may be generated exception or damaged data.