Representing an XML Document as a DOM
The %XML.DocumentOpens in a new tab and %XML.NodeOpens in a new tab classes enable you to represent an arbitrary XML document as a DOM (document object model). You can then navigate this object and modify it. You can also create a new DOM and add to it.
The XML declaration of any XML document that you use should indicate the character encoding of that document, and the document should be encoded as declared. If the character encoding is not declared, InterSystems IRIS® data platform uses the defaults described in Character Encoding of Input and Output. If these defaults are not correct, modify the XML declaration so that it specifies the character set actually used.
Opening an XML Document as a DOM
To open an existing XML document for use as a DOM, do the following:
-
Create an instance of the %XML.ReaderOpens in a new tab class.
-
Optionally specify the Format property of this instance, to specify the format of the file that you are importing.
By default, InterSystems IRIS assumes that XML files are in literal format. If your file is in SOAP-encoded format, you must indicate this so that the file can be read correctly.
See Reader Properties in Importing XML into Objects.
This property has no effect unless you use Correlate() and Next().
-
Open the source. To do so, use one of the following methods of %XML.ReaderOpens in a new tab:
-
OpenFile() — Opens a file.
-
OpenStream() — Opens a stream.
-
OpenString() — Opens a string.
-
OpenURL() — Opens a URL.
In each case, you can optionally specify a second argument for the method to override the value of the Format property.
-
-
Access the Document property, which is a DOM. This property is an instance of %XML.DocumentOpens in a new tab and it provides methods that you can use to find information about the document as a whole. For example, CountNamespace() returns the total number of namespaces used by the DOM.
Or, if you have a stream that contains an XML document, call the GetDocumentFromStream() method of %XML.DocumentOpens in a new tab. This returns an instance of %XML.DocumentOpens in a new tab.
Example 1: Converting a File to a DOM
For example, the following method reads an XML file and returns an instance of %XML.DocumentOpens in a new tab that represents that document:
ClassMethod GetXMLDocFromFile(file) As %XML.Document
{
set reader=##class(%XML.Reader).%New()
set status=reader.OpenFile(file)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
set document=reader.Document
quit document
}
Example 2: Converting an Object to a DOM
The following method accepts an OREF and returns an instance of %XML.DocumentOpens in a new tab that represents that object. The method assumes that the OREF is an instance of an XML-enabled class:
ClassMethod GetXMLDoc(object) As %XML.Document
{
//make sure this is an instance of an XML-enabled class
if '$IsObject(object){
write "Argument is not an object"
quit $$$NULLOREF
}
set classname=$CLASSNAME(object)
set isxml=$CLASSMETHOD(classname,"%Extends","%XML.Adaptor")
if 'isxml {
write "Argument is not an instance of an XML-enabled class"
quit $$$NULLOREF
}
//step 1 - write object as XML to a stream
set writer=##class(%XML.Writer).%New()
set stream=##class(%GlobalCharacterStream).%New()
set status=writer.OutputToStream(stream)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
set status=writer.RootObject(object)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
//step 2 - extract the %XML.Document from the stream
set status=##class(%XML.Document).GetDocumentFromStream(stream,.document)
if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$NULLOREF}
quit document
}
Getting the Namespaces of the DOM
When InterSystems IRIS reads an XML document and creates a DOM, it identifies all the namespaces used in the document and assigns an index number to each.
Your instance of %XML.DocumentOpens in a new tab class provides the following methods that you can use to find information about the namespaces in the document:
Returns the number of namespaces in the document.
Returns the index that corresponds to the given namespace.
Returns the XML namespace URI for the given index.
The following example method displays a report showing the namespaces used in a document:
ClassMethod ShowNamespaces(doc As %XML.Document)
{
Set count=doc.CountNamespace()
Write !, "Number of namespaces in document: "_count
For i=1:1:count {
Write !, "Namespace "_i_" is "_doc.GetNamespace(i)
}
}
DOM Node Types
The %XML.DocumentOpens in a new tab and %XML.NodeOpens in a new tab classes recognize the following DOM node types:
-
Element ($$$xmlELEMENTNODE)
Note that these macros are defined in the %xml.DOM.inc include file.
-
Text ($$$xmlTEXTNODE)
-
Whitespace ($$$xmlWHITESPACENODE)
Other types of DOM nodes are simply ignored.
Consider the following XML document:
<?xml version="1.0"?>
<team>
<member id="alpha">Jack O'Neill</member>
<member id="beta">Samantha Carter</member>
<member id="gamma">Daniel Jackson</member>
</team>
When viewed as a DOM, this document consists of the following nodes:
NodeID | NodeType | LocalName | Notes |
---|---|---|---|
0,29 | $$$xmlELEMENTNODE | team | |
1,29 | $$$xmlWHITESPACENODE | This node is a child of the <team> node | |
1,23 | $$$xmlELEMENTNODE | member | This node is a child of the <team> node |
2,45 | $$$xmlTEXTNODE | Jack O'Neill | This node is a child of the first <member> node |
1,37 | $$$xmlWHITESPACENODE | This node is a child of the <team> node | |
1,41 | $$$xmlELEMENTNODE | member | This node is a child of the <team> node |
3,45 | $$$xmlTEXTNODE | Samantha Carter | This node is a child of the second <member> node |
1,45 | $$$xmlWHITESPACENODE | This node is a child of the <team> node | |
1,49 | $$$xmlELEMENTNODE | member | This node is a child of the <team> node |
4,45 | $$$xmlTEXTNODE | Daniel Jackson | This node is a child of the third <member> node |
1,53 | $$$xmlWHITESPACENODE | This node is a child of the <team> node |
For information on accessing the node type, localname, and other details, see the next section.
Getting Information about the Current Node
The following string properties of %XML.NodeOpens in a new tab provide information about the current node. In all cases, an error is thrown if there is no current node.
Local name of the current element node. An error is thrown if you try to access this property for another type of node.
Namespace URI of the current element node. An error is thrown if you try to access this property for another type of node.
Index of the namespace of the current element node.
When InterSystems IRIS reads an XML document and creates a DOM, it identifies all the namespaces used in the document and assigns an index number to each.
An error is thrown if you try to access this property for another type of node.
Equals true if xsi:nil or xsi:null is true or 1 for this element node. Otherwise, this property equals false.
Value of a character node.
ID of the current node. You can set this property in order to navigate to another node.
Type of the current node, as discussed in the previous section.
Qname of the element node. Only used for output as XML when the prefix is valid for the document.
The following methods provide additional information about the current node:
method GetText(ByRef text) as %Boolean {}
Gets the text contents of an element node. This method returns true if text is returned; in this case, the actual text is appended to the first argument, which is returned by reference.
method HasChildNodes(skipWhitespace As %Boolean = 0) as %Boolean {}
Returns true if the current node has child nodes; otherwise it returns false.
method GetNumberAttributes() as %Integer {}
Returns the number of the attributes of the current element.
Example
The following example method writes a report that gives information about the current node:
ClassMethod ShowNode(node as %XML.Node)
{
Write !,"LocalName="_node.LocalName
If node.NodeType=$$$xmlELEMENTNODE {
Write !,"Namespace="_node.Namespace
}
If node.NodeType=$$$xmlELEMENTNODE {
Write !,"NamespaceIndex="_node.NamespaceIndex
}
Write !,"Nil="_node.Nil
Write !,"NodeData="_node.NodeData
Write !,"NodeId="_node.NodeId
Write !,"NodeType="_node.NodeType
Write !,"QName="_node.QName
Write !,"HasChildNodes returns "_node.HasChildNodes()
Write !,"GetNumberAttributes returns "_node.GetNumberAttributes()
Set status=node.GetText(.text)
If status {
Write !, "Text of the node is "_text
} else {
Write !, "GetText does not return text"
}
}
Example output might be as follows:
LocalName=staff
Namespace=
NamespaceIndex=
Nil=0
NodeData=staff
NodeId=1
NodeType=e
QName=staff
HasChildNodes returns 1
GetNumberAttributes returns 5
GetText does not return text
Basic Methods for Examining Attributes
You can use the following methods of %XML.NodeOpens in a new tab to examine the attributes of the current node. Also see Additional Methods for Examining Attributes.
-
AttributeDefined() — Returns nonzero (true) if the current element has an attribute with the given name.
-
FirstAttributeName() — Returns the attribute name for the first attribute of the current element.
-
GetAttributeValue() — Returns the value of the given attribute. If the element does not have the attribute, the method returns null.
-
GetNumberAttributes() — Returns the number of the attributes of the current element.
-
LastAttributeName() — Returns the attribute name of the last attribute of the current element.
-
NextAttributeName() — Given an attribute name, this method returns the name of the next attribute in collation order, whether the specified attribute is valid or not.
-
PreviousAttributeName() — Given an attribute name, this method returns the name of the previous attribute in collation order, whether the specified attribute is valid or not.
The following example walks through the attributes in a given node and writes a simple report:
ClassMethod ShowAttributes(node as %XML.Node)
{
Set count=node.GetNumberAttributes()
Write !, "Number of attributes: ", count
Set first=node.FirstAttributeName()
Write !, "First attribute is: ", first
Write !, " Its value is: ",node.GetAttributeValue(first)
Set next=node.NextAttributeName(first)
For i=1:1:count-2 {
Write !, "Next attribute is: ", next
Write !, " Its value is: ",node.GetAttributeValue(next)
Set next=node.NextAttributeName(next)
}
Set last=node.LastAttributeName()
Write !, "Last attribute is: ", last
Write !, " Its value is: ",node.GetAttributeValue(last)
}
Consider the following sample XML document:
<?xml version="1.0"?>
<staff attr1="first" attr2="second" attr3="third" attr4="fourth" attr5="fifth">
<doc>
<name>David Marston</name>
</doc>
</staff>
If you pass the first node of this document to the example method, you see the following output:
Number of attributes: 5
First attribute is: attr1
Its value is: first
Next attribute is: attr2
Its value is: second
Next attribute is: attr3
Its value is: third
Next attribute is: attr4
Its value is: fourth
Last attribute is: attr5
Its value is: fifth
Additional Methods for Examining Attributes
This section discusses methods that you can use to get the name, value, namespace, QName, and value namespace for any attribute. These methods are grouped as follows:
-
Methods that use the attribute name and a namespace
Note:In the XML standard, an element can include multiple attributes with the same name, each in a different namespace. In InterSystems IRIS XML, however, this is not supported.
Also see Basic Methods for Examining Attributes.
Methods That Use Only the Attribute Name
Use the following methods to obtain information about attributes.
method GetAttribute(attributeName As %String,
ByRef namespace As %String,
ByRef value As %String,
ByRef valueNamespace As %String) {}
Returns data for the given attribute. This method returns the following values by reference:
-
namespace is the namespace URI from the QName of the attribute.
-
value is the attribute value.
-
valueNamespace is the namespace URI to which the value belongs. For example, consider the following attribute:
xsi:type="s:string"
The value of this attribute is string, and this value is in the namespace that is declared elsewhere with the prefix s. Suppose that an earlier part of this document included the following namespace declaration:
xmlns:s="http://www.w3.org/2001/XMLSchema"
In this case, valueNamespace would be "http://www.w3.org/2001/XMLSchema".
method GetAttributeNamespace(attributeName As %String) as %String {}
Returns the namespace URI from QName of the attribute named attributeName for the current element.
method GetAttributeQName(attributeName As %String) as %String {}
Returns the QName of the given attribute.
method GetAttributeValue(attributeName As %String) as %String {}
Returns the value of the given attribute.
method GetAttributeValueNamespace(attributeName As %String) as %String {}
Returns the namespace of the value of the given attribute.
Methods That Use the Attribute Name and Namespace
To get information about attributes by using both their names and their namespaces, use the following methods:
method GetAttributeNS(attributeName As %String,
namespace As %String,
ByRef value As %String,
ByRef valueNamespace As %String) {}
Returns data for the given attribute, where attributeName and namespace specify the attribute of interest. This method returns the following data by reference:
-
value is the attribute value.
-
valueNamespace is the namespace URI to which the value belongs. For example, consider the following attribute:
xsi:type="s:string"
The value of this attribute is string, and this value is in the namespace that is declared elsewhere with the prefix s. Suppose that an earlier part of this document included the following namespace declaration:
xmlns:s="http://www.w3.org/2001/XMLSchema"
In this case, valueNamespace would be "http://www.w3.org/2001/XMLSchema".
method GetAttributeQNameNS(attributeName As %String,
namespace As %String)
as %String {}
Returns the QName of the given attribute, where attributeName and namespace specify the attribute of interest.
method GetAttributeValueNS(attributeName As %String,
namespace As %String)
as %String {}
Returns the value of the given attribute, where attributeName and namespace specify the attribute of interest.
method GetAttributeValueNamespaceNS(attributeName As %String,
namespace As %String)
as %String {}
Returns the namespace of the value of the given attribute, where attributeName and namespace specify the attribute of interest.
Creating or Editing a DOM
To create a DOM or to modify an existing one, you use the following methods of %XML.DocumentOpens in a new tab:
classmethod CreateDocument(localName As %String,
namespace As %String)
as %XML.Document {}
Returns a new instance of %XML.DocumentOpens in a new tab that consists of only a root element.
method AppendCharacter(text As %String) {}
Appends new character data node to the list of children of this element node. The current node pointer does not change; this node is still the parent of the appended child.
method AppendElement(localName As %String,
namespace As %String,
text As %String) {}
Appends a new element node to the list of children of this node. If the text argument is specified, then character data is added as the child of the new element. The current node pointer does not change; this node is still the parent of the appended child.
method AppendNode(sourceNode As %XML.Node) as %Status {}
Appends a copy of the specified node as a child of this node. The node to copy may be from any document. The current node pointer does not change. This node is still the parent of the appended child.
method AppendTree(sourceNode As %XML.Node) as %Status {}
Appends a copy of the specified node, including all its children, as a child of this node. The tree to copy may be from any document, but this node may not be a descendant of the source node. The current node pointer does not change. This node is still the parent of the appended child.
method InsertNamespace(namespace As %String) {}
Adds the given namespace URI to the document.
method InsertCharacter(text as %String, ByRef child As %String) as %String {}
Inserts a new character data node as a child of this node. The new character data is inserted just before the specified child node. The child argument is the node ID of the child node; it is passed by reference so that it may be updated after the insert. The nodeId of the inserted node is returned. The current node pointer does not change.
method InsertNode(sourceNode As %XML.Node, ByRef child As %String) as %String {}
Inserts a copy of the specified node as a child of this node. The node to copy may be from any document. The new node is inserted just before the specified child node. The child argument is the node ID of the child node; it is passed by reference so that it may be updated after the insert. The nodeId of the inserted node is returned. The current node pointer does not change.
method InsertTree(sourceNode As %XML.Node, ByRef child As %String) as %String {}
Inserts a copy of the specified node, including its children, as a child of this node. The tree to copy may be from any document, but this node may not be a descendant of the source node. The new node is inserted just before the specified child node. The child argument is the node ID of the child node; it is passed by reference so that it may be updated after the insert. The nodeId of the inserted node is returned. The current node pointer does not change.
method RemoveAttribute(attributeName As %String) {}
Removes the given attribute.
method RemoveAttributeNS(attributeName As %String,
namespace As %String) {}
Removes the given attribute, where attributeName and namespace specify the attribute of interest.
method ReplaceNode(sourceNode As %XML.Node) as %Status {}
Replaces the node with a copy of the specified node. The node to copy may be from any document. The current node pointer does not change.
method ReplaceTree(sourceNode As %XML.Node) as %Status {}
Replaces the node with a copy of the specified node, including all its children. The tree to copy may be from any document, but may not be a descendant of the source node. The current node pointer does not change.
method SetAttribute(attributeName As %String,
namespace As %String = "",
value As %String = "",
valueNamespace As %String = "") {}
Sets data for an attribute of the current element. Here:
-
attributeName is the name of the attribute.
-
namespace is the namespace URI from QName of the attribute named attributeName for this element.
-
value is the attribute value.
-
valueNamespace is the namespace URI corresponding to the prefix when the attribute value is of the form "prefix:value".
Examples of Creating or Editing a DOM
The following examples illustrate of how to use each of the DOM creation and editing methods. The examples build off of each other and are paired with sample output XML.
Set newdom = ##class(%XML.Document).CreateDocument("team", "t:http://example.com/example")
Set writer = ##class(%XML.Writer).%New()
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example"/>
set node = newdom.GetDocumentElement()
do node.AppendElement("member", "t:http://example.com/example", "Jack O'Neill")
do node.AppendElement("member", "t:http://example.com/example", "Samantha Carter")
do node.AppendElement("member", "t:http://example.com/example", "Daniel Jackson")
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
<member> Jack O'Neill</member>
<member> Samantha Carter</member>
<member> Daniel Jackson</member>
</team>
set node = newdom.GetDocumentElement()
do node.MoveToFirstChild()
do node.AppendElement("status", "t:http://example.com/example")
do node.MoveToLastChild()
do node.AppendCharacter("Playable")
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
<member> Jack O'Neill
<status> Playable </status>
</member>
<member> Samantha Carter</member>
<member> Daniel Jackson</member>
</team>
do node.MoveToParent() // step back to parent member node
do node.MoveToNextSibling() // move to next member
set statusnode = newdom.GetDocumentElement() // make a new node
do statusnode.MoveToFirstChild() // get first member
do statusnode.MoveToLastChild() // get that status node
do node.AppendNode(statusnode) // give the next member the status node (note: but not its children!)
do node.MoveToLastChild()
do node.AppendCharacter("Playable")
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
<member> Jack O'Neill
<status> Playable </status>
</member>
<member> Samantha Carter
<status> Playable </status>
</member>
<member> Daniel Jackson</member>
</team>
do node.MoveToParent()
do node.MoveToNextSibling()
do node.AppendTree(statusnode)
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
<member> Jack O'Neill
<status> Playable </status>
</member>
<member> Samantha Carter
<status> Playable </status>
</member>
<member> Daniel Jackson
<status> Playable </status>
</member>
</team>
set nscount = newdom.CountNamespace()
w nscount
Output:
2
do newdom.InsertNamespace("y:http://example2.com/example2")
set nscount = newdom.CountNamespace()
w nscount
Output:
3
set rootnode = newdom.GetDocumentElement()
set childnode = newdom.GetDocumentElement()
do childnode.MoveToFirstChild()
set childnodeid = childnode.NodeId
do rootnode.InsertCharacter("Team Go Getters", .childnodeid)
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
Team Go Getters
<member> Jack O'Neill
<status> Playable </status>
</member>
<member> Samantha Carter
<status> Playable </status>
</member>
<member> Daniel Jackson
<status> Playable </status>
</member>
</team>
do rootnode.InsertNode(childnode,.childnodeid) // empty member node
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
Team Go Getters
<member/>
<member> Jack O'Neill
<status> Playable </status>
</member>
<member> Samantha Carter
<status> Playable </status>
</member>
<member> Daniel Jackson
<status> Playable </status>
</member>
</team>
Set newdom2 = ##class(%XML.Document).CreateDocument("team2", "t2:http://example2.com/example2") // make a second dom
set root2 = newdom2.GetDocumentElement()
set firstchild2 = newdom2.GetDocumentElement()
set firstchild2id = firstchild2.NodeId
set lastchild = newdom.GetDocumentElement() // get the last member of the first dom
do lastchild.MoveToLastChild()
do root2.InsertTree(lastchild, .firstchild2id)
do writer.Document(newdom2)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<member xmlns="t:http://example.com/example">Daniel Jackson
<status>Playable</status>
</member>
set node = newdom.GetDocumentElement()
do node.MoveToFirstChild()
do node.MoveToNextSibling()
do node.Remove() // removing the empty member node
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
Team Go Getters
<member> Jack O'Neill
<status> Playable </status>
</member>
<member> Samantha Carter
<status> Playable </status>
</member>
<member> Daniel Jackson
<status> Playable </status>
</member>
</team>
set node = newdom.GetDocumentElement()
do node.MoveToLastChild()
do node.SetAttribute("gender", , "man",)
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
Team Go Getters
<member> Jack O'Neill
<status> Playable </status>
</member>
<member> Samantha Carter
<status> Playable </status>
</member>
<member gender="man"> Daniel Jackson
<status> Playable </status>
</member>
</team>
do node.RemoveAttribute("gender")
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
Team Go Getters
<member> Jack O'Neill
<status> Playable </status>
</member>
<member> Samantha Carter
<status> Playable </status>
</member>
<member> Daniel Jackson
<status> Playable </status>
</member>
</team>
Add attribute:
set node = newdom.GetDocumentElement()
do node.MoveToLastChild()
do node.SetAttribute("gender", "t:http://example.com/example", "man", "http://example.com/example")
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
Team Go Getters
<member>Jack O'Neill
<status>Playable</status>
</member>
<member>Samantha Carter
<status>Playable</status>
</member>
<member gender="s01:man" xmlns:s01="http://example.com/example">Daniel Jackson
<status>Playable</status>
</member>
</team>
Remove attribute:
do node.RemoveAttributeNS("gender", "t:http://example.com/example")
do writer.Document(newdom)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<team xmlns="t:http://example.com/example">
Team Go Getters
<member>Jack O'Neill
<status>Playable</status>
</member>
<member>Samantha Carter
<status>Playable</status>
</member>
<member>Daniel Jackson
<status>Playable</status>
</member>
</team>
Replace the member element in the second DOM with a member node:
do firstchild2.ReplaceNode(node)
do writer.Document(newdom2)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<member xmlns="t:http://example.com/example"/>
Replace the empty member node with a populated tree:
do firstchild2.ReplaceTree(node)
do writer.Document(newdom2)
Output:
<?xml version="1.0" encoding="UTF-8"?>
<member xmlns="t:http://example.com/example">Daniel Jackson
<status>Playable</status>
</member>
Writing XML Output from a DOM
You can serialize a DOM or a node of a DOM and generate XML output. To do this, you use the following methods of %XML.WriterOpens in a new tab:
method Document(document As %XML.Document) as %Status {}
Given an instance of %XML.DocumentOpens in a new tab, this method writes the document to the currently specified output destination.
method DocumentNode(document As %XML.Node) as %Status {}
Given an instance of %XML.NodeOpens in a new tab, this method writes the node to the currently specified output destination.
method Tree(node As %XML.Node) as %Status {}
Given an instance of %XML.NodeOpens in a new tab, this method writes the node and its tree of descendants to the currently specified output destination.
For information on specifying the output destination and setting properties of %XML.WriterOpens in a new tab, see Writing XML Output from Objects.