IXml and IXmlNode interfaces

IXml and IXmlNode interfaces are used to work with XML.

 

Added in QM 2.3.0.

 

Use function CreateXml to create an XML object. To work with it, use IXml and IXmlNode interfaces. Examples:

 

 This macro loads XML file and adds several new nodes.

out
IXml x=CreateXml

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=CreateXml

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.

 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.

Functions

There are two 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.

IXml interface

 

interface# IXml :IUnknown
	[g]IXmlNode'Root()
	[g]IXmlNode'RootElement()
	IXmlNode'Path($path [ARRAY(IXmlNode)*allMatching])
	IXmlNode'Add($name [$value])
	Delete(IXmlNode*node)
	Clear()
	[g]$XmlParsingError
	[g]#Count()
	IXmlNode'FromString($s)
	ToString(str*so)
	IXmlNode'FromFile($file [$defaultXML])
	ToFile([$file])
	{3426CF3C-F7C1-4322-A292-463DB8729B55}

 


 

CreateXml - creates an XML object and returns IXml interface pointer. Since it is a COM object, it is destroyed automatically when goes out of scope.

 

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 to save memory.

4 - When parsing XML, ignore "encoding" attribute in xml declaration. Read more below, in Notes chapter.

8 - after successful FromFile, before destroying the IXml object automatically save to the same file. However will not save if you after call FromString.

 


 

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".

 


 

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

 


 

Path - gets any node by path. The IXmlNode interface also has this function, and it is documented there (below).

 


 

Add - 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 - 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.

 


 

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

 


 

Count - gets the number of nodes in XML, including attributes.

 


 

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

 


 

ToString - composes XML string from the tree.

 


 

FromFile - 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.

 

If defaultXML is used and not empty, and the file does not exist, initializes the object from defaultXML (like FromString). The default 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.

 

QM 2.3.0. Can use syntax ":resourceid filepath" to add the file to exe resources.

 


 

ToFile - saves the object tree to an XML file. If file is omitted or empty, uses the same file as used with FromFile.

 


 

IXmlNode interface

 

interface# IXmlNode :IUnknown
	[g]IUnknown'XmlDoc()
	[g]IXmlNode'Parent()
	[g]IXmlNode'Prev()
	[g]IXmlNode'Next()
	[g]IXmlNode'FirstChild()
	[g]IXmlNode'LastChild()
	[g]IXmlNode'Child($name [index])
	[g]IXmlNode'Attribute($name)
	IXmlNode'Path($path [ARRAY(IXmlNode)*allMatching])
	[g]$Name()
	[p]Name($name)
	[g]$Value()
	[p]Value($value)
	ValueBinaryGet(str*value)
	ValueBinarySet(str*value [flags]) ;;flags: 1 compress, 2 hex
	[g]#Type()
	[g]#UserData()
	[p]UserData(userdata)
	Properties(XMLNODE*xi)
	[g]$ChildValue($name [index])
	[g]$AttributeValue($name)
	[g]#AttributeValueInt($name)
	IXmlNode'Add($name [$value])
	IXmlNode'Insert(IXmlNode'iafter $name [$value])
	IXmlNode'SetChild($name $value)
	IXmlNode'SetAttribute($name $value)
	IXmlNode'SetAttributeInt($name value)
	Move(IXmlNode'parent IXmlNode'iafter)
	GetAll(flags ARRAY(IXmlNode)*a)
	[g]#ChildCount()
	{3426CF3C-F7C1-4322-A292-463DB8729B56}

 

 


 

XmlDoc - gets parent IXml.

 


 

Parent, Prev, Next, FirstChild, LastChild - gets parent, sibling or child node. The first three functions can be used with attributes too.

 


 

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

 

name - child node name. If "*", matches any name. Can be followed by a filter expression, like with Path.

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

 


 

Attribute - 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.

 


 

Path - gets any node by path. Note that it is not XPath, although supports something from it.

 

The IXml interface also has this function. The difference is that path used with IXmlNode starts from the node (not including itself), whereas path used with IXml starts from the root.

 

If allMatching is used, it is populated with all matching nodes. If path is like "elem1/elem2", searches for "elem2" only in the first found child element "elem1", not in all child elements "elem1".

 

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 is populated with all child nodes.
"elem/*" Get first child node of element "elem". If allMatching is used, it is populated with all child nodes of "elem".
"elem/@*" Get first attribute of element "elem".
"*/node"

Get child node "node" of the first child element. If allMatching is used, it is populated with all child nodes of the first child element that are named "node".

 

Note that *, if not at end of path, finds first element. If it is at end of path, finds first node of any type.

"../node" Get child node/nodes "node" of the parent element.

 

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*='*']" Get element "elem" that has attribute "id".
"elem[id='abc']" Get element "elem" that has a child element "id" whose value is "abc".
"elem[id*='*']" Get element "elem" that has a child element "id".

Filter expression operators:

= Compare as strings. Case insensitive.
*= Compare as strings. Can be used wildcard characters. Case insensitive.
#= Compare as integer numbers. For example, '10' in filter expression would match "10", "0x10", etc in XML.
! Logical not. Can be used before other operators. For example, != means not equal.

 

Filter expressions can be at the end of the path or not. For example, this path is valid: "elem1[@id='abc']/elem2".

 

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

 


 

Name - gets name of the node. Can be used with attributes too.

 


 

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 to XML escape sequences.

 


 

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.

 


 

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

 


 

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 - 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. Root nodes have level 0, immediate child nodes of the root element have level 1, and so on.

flags - 1 the element has text, 2 the element has children, 4 the element or xml declaration has attributes, 128 the attribute is in xml declaration. Note that an element cannot have both text and children.

 


 

ChildValue - gets value of a child node. Arguments are same as with Child. Can be used instead of Child("name").Value.

 


 

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

 

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

 


 

Add - 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).

 


 

Insert - the same as above, 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.

 


 

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

 


 

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 - 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 - gets all descendants (immediate children, their children, and so on).

 

flags: 1 include attributes, 2 get only immediate children.

 

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

 


 

ChildCount - gets number of immediate child nodes.

 

To get number of child nodes that have certain name or other properties, instead use Path with array. Then the number will be equal to the array lenth.

 


 

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 the predefined 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 needed 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.