Modifying a ColdFusion XML object

As with all ColdFusion structured objects, you can often use a number of methods to change the contents of an XML document object. For example, you often have the choice of using an assignment statement or a function to update the contents of a structure or an array. The following section describes the array and structure functions that you can use to modify an XML document object. The section "XML document object management reference" provides a quick reference to modifying XML document object contents. Later sections describe these methods for changing document content in detail.

Functions for XML object management

The following table lists the ColdFusion array and structure functions that you can use to manage XML document objects and their functions, and describes their common uses. In several cases you can use either an array function or a structure function for a purpose, such as for deleting all of an element's attributes or children.
Function
Use
ArrayLen
Determines the number of child elements in an element, that is, the number of elements in an element's XmlChildren array.
ArrayIsEmpty
Determines whether an element has any elements in its XmlChildren array.
StructCount
Determines the number of attributes in an element's XmlAttributes structure.
StructIsEmpty
Determines whether an element has any attributes in its XmlAttributes structure.
Returns True if the specified structure, including the XML document object or an element, exists and is empty.
StructKeyArray
StructKeyList
Gets an array or list with the names of all of the attributes in an element's XmlAttributes structure. Returns the names of the children of an XML element.
ArrayInsertAt
Adds a new element at a specific location in an element's XmlChildren array.
ArrayAppend
ArrayPrepend
Adds a new element at the end or beginning of an element's XmlChildren array.
ArraySwap
Swaps the children in the XmlChildren array at the specified position.
ArraySet
Sets a range of entries in an XmlChildren array to equal the contents of a specified element structure. Each entry in the array range will be a copy of the structure. Can be used to set a single element by specifying the same index as the beginning and end of the range.
ArrayDeleteAt
Deletes a specific element from an element's XmlChildren array.
ArrayClear
Deletes all child elements from an element's XmlChildren array.
StructDelete
Deletes a selected attribute from an element's XMLAttributes structure.
Deletes all children with a specific element name from an element's XmlChildren array.
Deletes all attributes of an element.
Deletes all children of an element.
Deletes a selected property value.
StructClear
Deletes all attributes from an element's XMLAttributes structure.
Duplicate
Copies an XML document object, element, or node structure.
IsArray
Returns True for the XmlChildren array. Returns false if you specify an element name, such as mydoc.XmlRoot.name, even if there are multiple name elements in XmlRoot.
IsStruct
Returns False for XML document objects, elements, and nodes. Returns True for XmlAttributes structures.
StructGet
Returns the specified structure, including XML document objects, elements, nodes, and XmlAttributes structures.
StructAppend
Appends a document fragment XML document object to another XML document object.
StructInsert
Adds a new entry to an XmlAttributes structure.
StructUpdate
Sets or replaces the value of a document object property such as XmlName, or of a specified attribute in an XmlAttributes structure.

Note:   Array and structure functions not in the preceding or table or the table in the next section, do not work with XML document objects, XML elements, or XML node structures.

Treating elements with the same name as an array

In many cases an XML element has multiple children with the same name. For example, the example document used in this chapter has multiple name elements in the employee elements. In many cases, you can treat the child elements with identical names as an array. For example, to reference the second name element in mydoc.employee, you can specify mydoc.employee.name[2]. However, you can only use a limited set of Array functions when you use this notation. The following table lists the array functions that are valid for such references.
Array function
Result
IsArray(elemPath.elemName)
Always returns False.
ArrayClear(elemPath.elemName)
Removes all the elements with name elemName from the elemPath element.
ArrayLen(elemPath.elemName)
Returns the number of elements named elemName in the elemPath element.
ArrayDeleteAt(elemPath.elemName, n)
Deletes the nth child named elemlName from the elemPath element.
IsEmpty(elemPath.elemName)
Always Returns False.
ArrayToList(elemPath.elemName, n)
Returns a comma separated list of all the XmlText properties of all the children of elemPath named elemName.

XML document object management reference

The following tables provide a quick reference to the ways you can modify the contents of an XML document object. The sections that follow describe in detail how to modify XML contents.

Adding

Use the following techniques to add new information to an element:
Type
Using a function
Using an assignment statement
Attribute
StructInsert(xmlElemPath.XmlAttributes, 
  "key", "value")
xmlElemPath.XmlAttributes.key =
  "value"

xmlElemPath.XmlAttributes["key"] 
  = "value"
Child element
To append:
ArrayAppend(xmlElempath.XmlChildren,
  newElem)

To insert:
ArrayInsertAt(xmlElempath.
  XmlChildren, position, newElem)
To append:
xmlElemPath.XmlChildren[i] =
  newElem

xmlElemPath.newChildName =
  newElem
(where newChildName must be the same as newElem.XmlName and cannot be an indexed name such as name[3])

Deleting

Use the following techniques to delete information from an element:
Type
Using a function
Using an assignment statement
Property
StructDelete(xmlElemPath, propertyName)
xmlElemPath.propertyName=""
Attribute
All attributes:
StructDelete(xmlElemPath, XmlAttributes)

A specific attribute:
StructDelete(xmlElemPath.XmlAttributes,
"attributeName")
Not available
Child element
All children of an element:
StructDelete(xmlElemPath, "XmlChildren")
or
ArrayClear(xmlElemPath.XmlChildren)

All children with a specific name:
StructDelete(xmlElementpath,
  "elemName")
ArrayClear(xmlElemPath.elemName)

A specific child:
ArrayDeleteAt(xmlElemPath.XmlChildren,
  position)
ArrayDeleteAt(xmlElemPath.elemName,
  position)
Not available

Changing

Use the following techniques to change the contents of an element:
Type
Using a function
Using an assignment statement
Property
StructUpdate(xmlElemPath,
  "propertyName", "value")
xmlElemPath.propertyName =
  "value"

xmlElemPath["propertyName"] =
  "value"
Attribute
StructUpdate(xmlElemPath.XmlAttributes,
  "attributeName", "value")

xmlElemPath.XmlAttributes.
  attributeName="value"

xmlElemPath.XmlAttributes
  ["attributeName"] = "value"
Child element
(replace)
ArraySet(xmlElemPath.XmlChildren, index,
  index, newElement)

(use the same value for both index entries to change one element)
Replace first or only child named elementName:
parentElemPath.elementName =
  newElement

parentElemPath["elementName"]
  = newElement

Replace a specific child named elementName:
parentElemPath.elementName
  [index] = newElement
or
parentElemPath["elementName"]
  [index] = newElement

Adding, deleting, and modifying XML elements

The following sections describe the basic techniques for adding, deleting, and modifying XML elements. The example code uses the XML document described in "A simple XML document".

Counting and finding the position of child elements

Often, an XML element has several children with the same name. For example, in the XML document defined in the simple XML document, the employee root element has multiple name elements.

To manipulate such an object, you often need to know the number of children of the same name, and you might need to know the position in the XmlChildren array of a specific child name that is used for multiple children. The following sections describe how to get this information.

Counting child elements

The following user-defined function determines the number of child elements with a specific name in an element:

<cfscript>
function NodeCount (xmlElement, nodeName)
{
  nodesFound = 0;
  for ( i = 1; i LTE ArrayLen(xmlElement.XmlChildren); i = i+1)
  {
    if (xmlElement.XmlChildren[i].XmlName IS nodeName)
      nodesFound = nodesFound + 1;
  }
  return nodesFound;
}
</cfscript>

The following lines use this function to display the number of nodes named "name" in the mydoc.employee element:

<cfoutput>
Nodes Found: #NodeCount(mydoc.employee, "name")#
</cfoutput>

Determining the position of a child element with a common name

The XmlChildPos function determines the location in the XmlChildren array of a specific element with a common name. You use this index when you need to tell ColdFusion where to insert or delete child elements. For example, if there are several name elements in mydoc.employee, use the following code to locate name[2] in the XmlChildren array:

<cfset nameIndex = XmlChildPos(mydoc.employee, "name", 2)>

Adding an element

You can add an element by creating a new element or by using an existing element.

Use the XmlElemNew function to create a new, empty element. This function has the following form:

XmlElemNew(docObject, elementName)

where docObject is the name of the XML document object in which you are creating the element, and elementName is the name you are giving the new element.

Use an assignment statement with an existing element on the right side to create a new element using an existing element. See "Copying an existing element" for more information on adding elements using existing elements.

Adding an element using a function

You can use the ArrayInsertAt or ArrayAppend functions to add an element to an XML document object. For example, the following line adds a phoneNumber element after the last element for employee.name[2]:

<cfset ArrayAppend(mydoc.employee.name[2].XmlChildren, XmlElemNew(mydoc,
      "phoneNumber"))>

The following line adds a new department element as the first element in employee. The name elements become the second and third elements.

<cfset ArrayInsertAt(mydoc.employee.XmlChildren, 1, XmlElemNew(mydoc,
      "department"))>

You must use the format parentElement.XmlChildren to specify the array of elements to which you are adding the new element. For example, the following line causes an error:

<cfset ArrayInsertAt(mydoc.employee.name, 2, XmlElemNew(mydoc, "PhoneNumber")>

If you have multiple child elements with the same name, and you want to insert a new element in a specific position, use the XmlChildPos function to determine the location in the XmlChildren array where you want to insert the new element. For example, the following code determines the location of mydoc.employee.name[1] and inserts a new name element as the second name element:

<cfscript>
nameIndex = XmlChildPos(mydoc.employee, "name", 1);
ArrayInsertAt(mydoc.employee.XmlChildren, nameIndex + 1, XmlElemNew(mydoc,
          "name"));
</cfscript>

Adding an element using direct assignment

You can use direct assignment to append a new element to an array of elements. You cannot use direct assignment to insert an element into an array of elements.

When you use direct assignment, you can specify on the left side an index into the XmlChildren array greater than the last child in the array. For example, if there are two elements in mydoc.employee, you can specify any number greater than two, such as mydoc.employee.XmlChildren[6]. The element is always added as the last (in this case, third) child.

For example, the following line appends a name element to the end of the child elements of mydoc.employee:

<cfset mydoc.employee.XmlChildren[9] = XmlElemNew(mydoc, "name")>

If the parent element does not have any children with the same name as the new child, you can specify the name of the new node or the left side of the assignment. For example, the following line appends a phoneNumber element to the children of the first name element in mydoc.employee:

<cfset mydoc.employee.name[1].phoneNumber = XmlElemNew(mydoc, "phoneNumber")>

You cannot use the node name on the left to add an element with the same name as an existing element in the parent. For example, if mydoc.employee has two name nodes, the following line causes an error:

<cfset mydoc.employee.name[3] = XmlElemNew(mydoc, "name")>

However, the following line does work:

<cfset mydoc.employee.XmlChilren[3] = XmlElemNew(mydoc, "name")>

Copying an existing element

You can add a copy of an existing element elsewhere in the document. For example, if there is a mydoc.employee.name[1].phoneNumber element, but no mydoc.employee. name[2].phoneNumber, the following line creates a new mydoc.employee. name[2]. phoneNumber element with the same value as the original element. This assignment copies the original element. Unlike with standard ColdFusion structures, you get a true copy, not a reference to the original structure. You can change the copy without changing the original.

<cfset mydoc.employee.name[2].phoneNumber = mydoc.employee.name[1].phoneNumber>

When you copy an element, the new element must have the same name as the existing element. If you specify the new element by name on the left side of an assignment, the element name must be the same as the name on the right side. For example, the following expression causes an error:

<cfset mydoc.employee.name[2].telephne = mydoc.employee.name[1].phoneNumber>

Deleting elements

There are many ways to delete individual or multiple elements.

Deleting individual elements

Use the ArrayDeleteAt function to delete a specific element from an XML document object. For example, the following line deletes the second child element in the mydoc.employee element:

<cfset ArrayDeleteAt(mydoc.employee.XmlChildren, 2)>

If an element has only one child element with a specific name, you can also use the StructDelete function to delete the child element. For example, the following line deletes the phoneNumber element named in the second employee.name element:

<cfset StructDelete(mydoc.employee.name[2], "phoneNumber")> 

When there are multiple child elements of the same name, you must specify the element position, either among the elements of the same name, or among all child elements. Fore example, you can use the following line to delete the second name element in mydoc.employee:

<cfset ArrayDeleteAt(mydoc.employee.name, 2)>

You can also determine the position in the XmlChildren array of the element you want to delete and use that position. To do so, use the XmlChildPos function. For example, the following lines determine the location of mydoc.employee.name[2] and delete the element:

<cfset idx = XmlChildPos(mydoc.employee, "name", 2)>
<cfset ArrayDeleteAt(mydoc.employee.XmlChildren, idx)>

Deleting multiple elements

If an element has multiple children with the same name, use the StructDelete function or ArrayClear function with an element name to delete all of an element's child elements with that name. For example, both of the following lines delete all name elements from the employee structure:

<cfset StructDelete(mydoc.employee, "name")>
<cfset ArrayClear(mydoc.employee.name)>

Use the StructDelete or ArrayClear function with XmlChildren to delete all of an element's child elements. For example, each of the following lines deletes all child elements of the mydoc.employee.name[2] element:

<cfset StructDelete(mydoc.employee.name[2], "XmlChildren")>
<cfset ArrayClear(mydoc.employee.name[2].XmlChildren)>

Adding, changing, and deleting element attributes

You modify an element's attributes the same way you change the contents of any structure. For example, each of the following lines adds a Status attribute the second mydoc.employee.name element:

<cfset mydoc.employee.name[2].XmlAttributes.Status="Inactive">
<cfset StructInsert(mydoc.employee.name[2].XmlAttributes, "Status", "Inactive")>

To change an attribute, use a standard assignment statement; for example:

<cfset mydoc.employee.name[2].XmlAttributs.Status="Active">

To delete an attribute, use StructDelete; for example:

<cfset StructDelete(mydoc.employee.name[1].XmlAttributes, "Status")>

Changing element properties

To change an element's properties, including its text and comment, use a standard assignment expression. For example, use the following line to add "in the MyCompany Documentation Department" to the mydoc.employee XML comment:

<cfset mydoc.employee.XmlComment = mydoc.employee.XmlComment & "in the
MyCompany Documentation Department">

Changing an element name

The XML DOM does not support changing an element name directly. To change the name of an element, you must create a new element with the new name, insert it into the XML document object before or after the original element, copy all the original element's contents to the new element, and then delete the original element.

Clearing an element property value

To clear an element property value, either assign the empty string to the property or use the StructDelete function. For example, each of the following lines clears the comment string from mydoc.employee:

<cfset mydoc.employee.XmlComment = "">
<cfset StructDelete(mydoc.employee, "XmlComment")>

Replacing or moving an element

To replace an element with a new element, use a standard replacement expression. For example, to replace the mydoc.employee.department element with a new element named organization, use either of the following lines:

<cfset mydoc.employee.department = XmlElemNew(mydoc, "Organization")>
<cfset mydoc.employee.XmlChildren[1] = XmlElemNew(mydoc, "Organization")>

To replace an element with a copy of an existing element, use the existing element on the right side of an expression. For example, the following line replaces the phoneNumber element for mydoc.employee.name[2] with the phoneNumber element from mydoc.employee.name[1]:

<cfset mydoc.employee.name[2].phoneNumber=mydoc.employee.name[1].phoneNumber>

This creates a true copy of the name[1].phoneNumber element as name[2].phoneNumber.

To move an element, you must assign it to its new location, then delete it from its old location. For example, the following lines move the phoneNumber element from mydoc.employee.name[1] to mydoc.employee.name[2]:

<cfset mydoc.employee.name[2].phoneNumber=mydoc.employee.name[1].phoneNumber>
<cfset StructDelete(mydoc.employee.name[1], "phoneNumber")>

Using XML and ColdFusion queries

You can convert XML documents into ColdFusion query objects and manipulate them using queries of queries. This technique does not require the use of XPath and provides a method of searching XML documents and extracting data that is natural to ColdFusion programmers.

Converting XML to a ColdFusion query

The following example reads an XML document, converts it to a query object, and then performs a query of queries on the object to extract selected data:

<!--- Read the file and convert it to an XML document object ---> 
<cffile action="read" file="C:\Neo\wwwroot\myexamples\employees.xml" variable="myxml">
<cfset mydoc = XmlParse(myxml)>

<!--- get an array of employees --->
<cfset emp = mydoc.employee.XmlChildren>
<cfset size = ArrayLen(emp)>

<cfoutput>
Number of employees = #size#
<br>
</cfoutput>
<br>
<!--- create a query object with the employee data --->
<cfset myquery = QueryNew("fname, lname") >
<cfset temp = QueryAddRow(myquery, #size#)>
<cfloop index="i" from = "1" to = #size#>
  <cfset temp = QuerySetCell(myquery, "fname", 
    #mydoc.employee.name[i].first.XmlText#, #i#)>
  <cfset temp = QuerySetCell(myquery, "lname", 
    #mydoc.employee.name[i].last.XmlText#, #i#)>
</cfloop>

<!--- Dump the query object --->
Contents of the myquery Query object: <br>
<cfdump var=#myquery#>
<br><br>

<!--- Select entries with the last name starting with A and dump the result --->
<cfquery name="ImqTest" dbType="query">
  SELECT lname, fname 
  FROM myquery 
  WHERE lname LIKE 'A%'
</cfquery>
<cfdump var=#imqtest#>

Converting a query object to XML

The following example shows how to convert a query object to XML. It uses cfquery to get a list of employees from the CompanyInfo database and saves the information as an XML document.

<!--- Query the database and get the names in the employee table --->
<cfquery name="myQuery" datasource="CompanyInfo">
  SELECT FirstName, LastName
  FROM employee 
</cfquery>

<!--- Create an XML document object containing the data --->
<cfxml variable="mydoc">
  <employee>
    <cfoutput query="myQuery">
       <name>
        <first>#FirstName#</first>
        <last>#LastName#</last>
      </name>
    </cfoutput>
  </employee>
</cfxml>

<!--- dump the resulting XML document object --->
<cfdump var=#mydoc#>
<!--- Write the XML to a file --->
<cffile action="write" file="C:\inetpub\wwwroot\xml\employee.xml"
    output=#toString(mydoc)#>

Comments