Using Caché XML Tools
Writing XML Output from Caché Objects
[Back] [Next]
   
Server:docs1
Instance:LATEST
User:UnknownUser
 
-
Go to:
Search:    

This chapter describes how you can generate XML output from Caché objects. It discusses the following topics:

Also see Writing XML Output from a DOM in the chapter Representing an XML Document as a DOM.”
Overview of Creating an XML Writer
Caché provides tools for generating XML output for Caché objects. You specify the details of the XML projection, as described in Projecting Objects to XML. Then you create a writer method that specifies the overall structure of the XML output: the character encoding, the order in which objects are presented, whether processing instructions are included, and so on.
The essential requirements are as follows:
The following Terminal session shows a simple example, in which we access an XML-enabled object and generate output for it:
GXML>Set p=##class(GXML.Person).%OpenId(1)
 
GXML>Set w=##class(%XML.Writer).%New()
 
GXML>Set w.Indent=1
 
GXML>Set status=w.RootObject(p)
<?xml version="1.0" encoding="UTF-8"?>
<Person GroupID="R9685">
  <Name>Zimmerman,Jeff R.</Name>
  <DOB>1961-10-03</DOB>
  <Address>
    <City>Islip</City>
    <Zip>15020</Zip>
  </Address>
  <Doctors>
    <Doctor>
      <Name>Sorenson,Chad A.</Name>
    </Doctor>
    <Doctor>
      <Name>Sorenson,Chad A.</Name>
    </Doctor>
    <Doctor>
      <Name>Uberoth,Roger C.</Name>
    </Doctor>
  </Doctors>
</Person>
The following sections provide details.
Creating an Output Method
Your output method constructs an XML document piece by piece, in the order you specify. The overall structure of your output method depends on whether you need to output a complete XML document or simply a fragment. This section discusses the following topics:
Overall Method Structure
Your method should do some or all of the following, in this order:
  1. If you are working with an object that might be invalid, call the %ValidateObject() method of that object and check the returned status. If the object is not valid, the XML would also not be valid.
    %XML.Writer does not validate the object before exporting it. This means that if you have just created an object and have not yet validated it, the object (and hence the XML) could be invalid (because, for example, a required property is missing).
  2. Create an instance of the %XML.Writer class and optionally set its properties.
    In particular, you might want to set the following properties:
    For readability, the examples in this documentation use Indent equal to 1.
    Also see the sections Properties That Affect the Prolog and Other Properties of the Writer later in this chapter.
  3. Specify an output destination.
    By default, output is written to the current device. To specify the output destination, call one of the following methods before starting to write the document:
  4. Start the document. You can use the StartDocument() method. Note that the following methods implicitly start a document if you have not started a document via StartDocument(): Write(), WriteDocType(), RootElement(), WriteComment(), and WriteProcessingInstruction().
  5. Optionally write lines of the prolog of the document. You can use the following methods:
  6. Optionally specify the default namespace. The writer uses this for classes that do not have a defined XML namespace. See Specify the Default Namespace.”
  7. Optionally add namespace declarations to the root element. To do so, you can invoke several utility methods before starting the root element. See Adding Namespace Declarations.”
  8. Start the root element of the document. The details depend on whether the root element of that document corresponds to a Caché object. There are two possibilities:
  9. If you used the RootElement() method, call methods to generate the output for one or more elements inside the root element. You can write any elements within the root element, with any order or logic you choose. There are several ways that you can write an individual element, and you can combine these techniques:
    See Constructing an Element Manually for further details.
  10. If you used the RootElement() method, call the EndRootElement() method. This method closes the root element of the document and decreases the indentation (if any), as appropriate.
  11. If you started the document with StartDocument(), call the EndDocument() method to close the document.
  12. If you directed the output to a string, use the GetXMLString() method to retrieve that string.
There are many other possible organizations, but note that some methods can be called only in some contexts. Specifically, once you start a document, you cannot start another document until you end the first document. If you try to do so, the writer method returns the following status:
#6275: Cannot output a new XML document or change %XML.Writer properties 
until the current document is completed.
The StartDocument() method explicitly starts a document. As noted earlier, other methods implicitly start a document: Write(), WriteDocType(), RootElement(), WriteComment(), and WriteProcessingInstruction().
Note:
The methods described here are designed to enable you to write specific units to the XML document, but in some cases, you may need more control. The %XML.Writer class provides an additional method, Write(), which you can use to write an arbitrary string into any place in the output.
Also, you can use the Reset() method to reinitialize the writer properties and output method. This is useful if you have generated an XML document and want to generate another one without creating a new writer instance.
Error Checking
Most of the methods of %XML.Writer return a status. You should check the status after each step and quit if appropriate.
Inserting Comment Lines
As noted earlier, you use the WriteComment() method to insert a comment line. You can use this method anywhere in your document. If you have not yet started the XML document, this method implicitly starts the document.
Example 1
The following example method generates XML output for a given XML-enabled object.
Class MyWriters.BasicWriter Extends %RegisteredObject
{

/// write given object to current device
ClassMethod Write(obj) As %Status
{
    set writer=##class(%XML.Writer).%New()
    set writer.Indent=1

    //these steps are not really needed because 
    //this is the default destination
    set status=writer.OutputToDevice()
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}

    set status=writer.RootObject(obj)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
        
    quit status
}

}
Notice that this method uses the OutputToDevice() method to direct output to the current device, which is the default destination. This is not necessary but is shown for demonstration purposes.
Suppose that we execute this method as follows:
 set obj=##class(GXML.Person4).%OpenId(1)
 do ##class(MyWriters.BasicWriter).Write(obj)
The output depends on what class is used, of course, but could look something like this:
<?xml version="1.0" encoding="UTF-8"?>
<Person4>
  <Name>Tesla,Alexandra L.</Name>
  <DOB>1983-11-16</DOB>
  <GroupID>Y9910</GroupID>
  <Address>
    <City>Albany</City>
    <Zip>24450</Zip>
  </Address>
  <Doctors>
    <Doc>
      <Name>Schulte,Frances T.</Name>
    </Doc>
    <Doc>
      <Name>Smith,Albert M.</Name>
    </Doc>
  </Doctors>
</Person4>
Example 2
The following example method generates XML output for a single, randomly chosen, stored object of any given XML-enabled class. First, it uses a utility method, which is shown here:
Class Utils.Utils
{

/// method to go through extent for the given class & return all the IDs
ClassMethod GetList(cls) As %Collection.ListOfDT [ SqlProc ]
{
    set extent=cls_":Extent"
    set returnlist=##class(%Collection.ListOfDT).%New()
    set resultSet=##class(%ResultSet).%New(extent)
    set status=resultSet.Execute()
    while (resultSet.Next() '=0)
    {
        set tempid=resultSet.Get("ID")
        do returnlist.Insert(tempid)
    }
    do resultSet.Close()
    quit returnlist
}
}
This method gets all the IDs from the current extent for any given class and returns them in a list.
The class that defines the writer method is shown below:
Class MyWriters.BasicWriter Extends %RegisteredObject
{

/// write one (random) object to current device
ClassMethod WriteOneToDevice(cls) As %Status
{
    set writer=##class(%XML.Writer).%New()
    set writer.Indent=1

    set status=writer.OutputToDevice()
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}

    set IDlist=##class(Utils.Utils).GetList(cls)
    set rand=$random(IDlist.Count())+1
    set objid=IDlist.GetAt(rand)
    set obj=$CLASSMETHOD(cls,"%OpenId",objid)

    set status=writer.RootObject(obj)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
        
    quit status
}

}
This example may be useful if you only want to see the effects of XML-enabling your classes and you do not care which object you look at.
Details on Indent Option
As noted earlier, you can use the Indent property of your writer to get output that contains additional line breaks, for greater readability. There is no formal specification for this formatting. This section describes the rules used by %XML.Writer if Indent equals 1:
Specifying the Character Set of the Output
To specify the character set to use in the output document, you can set the Charset property of your writer instance. The options include "UTF-8", "UTF-16", and other character sets supported by Caché.
For information on the default encoding, see Character Encoding of Input and Output,” earlier in this book.
Writing the Prolog
The prolog of an XML file (the section before the root element) can contain a documentation type declaration, processing instructions, and comments. This section discusses the following:
Properties That Affect the Prolog
In your writer instance, the following properties affect the prolog:
Charset
Controls two things: the character set declaration in the XML declaration and (correspondingly) the character set encoding used in the output. See Specifying the Character Set of the Output.”
NoXmlDeclaration
Controls whether the output contains the XML declaration. In most cases, the default is 0, which means that the declaration is written. If the character set is not specified and if the output is directed to a string or character stream, the default is 1 and no declaration is written.
Generating a Document Type Declaration
Before the root element, you can include a document type declaration, which declares the tags that are used in the document. To generate a document type declaration, you use the WriteDocType() method, which has one required argument and three optional arguments. For the purpose of this documentation, a document type declaration consists of the following possible parts:
<!DOCTYPE doc_type_name external_subset [internal_subset]>
As shown here, the document type has a name, which must be the name of the root element, according to the rules of XML. The declaration can include an external subset, an internal subset, or both.
The external_subset section points to DTD files elsewhere. The structure of this section is any of the following:
PUBLIC public_literal_identifier
PUBLIC public_literal_identifier system_literal_identifier
SYSTEM system_literal_identifier
Here public_literal_identifier and system_literal_identifier are strings that contain the URIs of a DTD. Note that a DTD can have both a public identifier and a system identifier. The following shows an example document type declaration that contains an external subset that uses both a public and a system identifier:
<!DOCTYPE hatches <!ENTITY open-hatch
         PUBLIC "-//Textuality//TEXT Standard open-hatch boilerplate//EN"
         "http://www.textuality.com/boilerplate/OpenHatch.xml">>
The internal_subset section is a set of entity declarations. The following shows an example document type declaration that contains only an internal set of declarations:
<!DOCTYPE teams [ <!ENTITY baseball team (name,city,player*)>
                   !ENTITY name (#PCDATA)>
                   !ENTITY city (#PCDATA)>
                   !ENTITY player (#PCDATA)>
  ] >
The WriteDocType() method has four arguments:
Writing Processing Instructions
To write a processing instruction into the XML, you use the WriteProcessingInstruction() method, which takes two arguments:
  1. The name of the processing instruction (also known as the target).
  2. The instructions themselves as a string.
The method writes the following into the XML:
<?name instructions?>
For example, suppose that you wanted to write the following processing instruction:
<?xml-stylesheet type="text/css" href="mystyles.css"?>
You could do this by calling the WriteProcessingInstruction() method as follows:
 set instructions="type=""text/css"" href=""mystyles.css"""
 set status=writer.WriteProcessingInstruction("xml-stylesheet", instructions)
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
Specifying a Default Namespace
In your writer instance, you can specify a default namespace, which is applied only to classes that do not have a setting for the NAMESPACE parameter. You have a couple of options:
Example
Consider the following class:
Class Writers.BasicDemoPerson Extends (%RegisteredObject, %XML.Adaptor)
{

Parameter XMLNAME="Person";

Property Name As %Name;

Property DOB As %Date;

}
By default, if we simply export an object of this class, we see output like the following:
<?xml version="1.0" encoding="UTF-8"?>
<Person>
  <Name>Persephone MacMillan</Name>
  <DOB>1976-02-20</DOB>
</Person>
In contrast, if we set DefaultNamespace equal to "http://www.person.org" in our writer instance and then export an object, we receive output like the following:
<?xml version="1.0" encoding="UTF-8"?>
<Person xmlns="www.person.org">
  <Name>Persephone MacMillan</Name>
  <DOB>1976-02-20</DOB>
</Person>
In this case, the default namespace is used for the <Person> element, which otherwise would not be assigned to a namespace.
Adding Namespace Declarations
Default Behavior
The %XML.Writer class automatically inserts the namespace declarations, generates namespace prefixes, and applies the prefixes where appropriate. For example, consider the following class definition:
Class ResearchWriters.PersonNS Extends (%Persistent, %XML.Adaptor)
{

Parameter NAMESPACE = "http://www.person.com";

Parameter XMLNAME = "Person";

Property Name As %Name;

Property DOB As %Date;

}
If you export multiple objects of this class, you see something like the following:
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <Person xmlns="http://www.person.com">
    <Name>Persephone MacMillan</Name>
    <DOB>1975-09-15</DOB>
  </Person>
  <Person xmlns="http://www.person.com">
    <Name>Joseph White</Name>
    <DOB>2003-01-31</DOB>
  </Person>
...
The namespace declaration is added to each <Person> element automatically. You might prefer to add it only to the root of the document.
Manually Adding the Declarations
You can control when a namespace is introduced into the XML output. The following methods all affect the next element that is written (but do not affect any elements after that one). For convenience, several of these methods add standard W3 namespaces.
Typically you use these methods to add namespace declarations to the root element of the document; that is, you call one or more of these methods before calling RootObject() or RootElement().
Note:
None of these methods assign any elements to namespaces, and these namespaces are never added as the default namespace. When you generate a specific element, you indicate the namespace that it uses, as noted later in Writing the Root Element and Generating an XML Element.”
AddNamespace()
method AddNamespace(namespace As %String, 
                    prefix As %String, 
                    schemaLocation As %String) as %Status
Adds the specified namespace. Here namespace is the namespace to add, prefix is an optional prefix for this namespace, and schemaLocation is the optional URI that indicates the location of the corresponding schema.
If you do not specify a prefix, a prefix is automatically generated (of the form s01, s02, and so on).
The following example shows the effect of this method. First, suppose that the Person class is assigned to a namespace (by means of the NAMESPACE class parameter). If you generate output for an instance of this class without first calling the AddNamespace() method, you might receive output like the following:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
  <Person xmlns="http://www.person.org">
    <Name>Love,Bart Y.</Name>
...
Alternatively, suppose that you call the AddNamespace() method as follows before you write the root element:
 set status=writer.AddNamespace("http:///www.person.org","p")
If you then generate the root element, the output is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns:p="http:///www.person.org">
  <Person xmlns="http://www.person.org">
...
Or suppose that when you call the AddNamespace() method, you specify the third argument, which gives the location of the associated schema:
 set status=writer.AddNamespace("http:///www.person.org","p","http://www.MyCompany.com/schemas/person.xsd")
In this case, if you then generate the Root element, the output is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns:p="http:///www.person.org" 
xsi:schemaLocation="http:///www.person.org http://www.MyCompany.com/schemas/person.xsd" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Person xmlns="http://www.person.org">
...
AddInstanceNamespace()
method AddInstanceNamespace(prefix As %String) as %Status
Adds the W3 schema instance namespace. Here prefix is the optional prefix to use for this namespace. The default prefix is xsi.
For example:
<Root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
AddSchemaNamespace()
method AddSchemaNamespace(prefix As %String) as %Status
Adds the W3 schema namespace. Here prefix is the optional prefix to use for this namespace. The default prefix is s.
For example:
<Root xmlns:s="http://www.w3.org/2001/XMLSchema">
...
AddSOAPNamespace()
method AddSOAPNamespace(soapPrefix As %String, 
                        schemaPrefix As %String, 
                        xsiPrefix As %String) as %Status
Adds the W3 SOAP encoding namespace, SOAP schema namespace, and SOAP schema instance namespace. This method has three optional arguments: the prefixes to use for these namespaces. The default prefixes are SOAP-ENC, s, and xsi, respectively.
For example:
<Root xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" 
xmlns:s="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
Also see Generating SOAP-Encoded XML,” later in this chapter.
AddSOAP12Namespace()
method AddSOAP12Namespace(soapPrefix As %String, 
                          schemaPrefix As %String, 
                          xsiPrefix As %String) as %Status
Adds the W3 SOAP 1.2 encoding namespace, SOAP schema namespace, and SOAP schema instance namespace.
For example:
<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns:SOAP-ENC="http://www.w3.org/2003/05/soap-encoding" 
xmlns:s="http://www.w3.org/2001/XMLSchema" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
...
You can use more than one of these methods. If you use more than one of them, the affected element contains the declarations for all the specified namespaces.
For an example, see More Complex Schema Example,”’ in the chapter Generating XML Schemas from Classes.”
Writing the Root Element
Every XML document must contain exactly one root element. There are two ways you can create this element:
In either case, you can specify a namespace to use for the root element, which is applied only if the XML-enabled class does not have a value for the NAMESPACE parameter. See Summary of Namespace Assignment.”
Remember that if the document includes a document type declaration, the name of that DTD must be the same as the name of the root element.
Generating an XML Element
If you use RootElement() to start the root element of a document, you are responsible for generating each element inside that root element. You have three choices:
For information on assigning the exported objects to namespaces, see Controlling the Use of Namespaces,” later in this chapter.
Generating an Object as an Element
You can generate output from a Caché object as the element. In this case, you use the Object() method, which writes an XML-enabled object. The output includes all object references contained in that object. You specify the name of this element, or you can use the default, which is defined within the object.
You can use the Object() method only between the RootElement() and EndRootElement() methods.
Example
This example generates output for all saved instances of a given XML-enabled class:
Class MyWriters.WriteAll Extends %RegisteredObject
{

ClassMethod WriteAll(cls As %String, directory As %String = "c:\")
{
    //check that cls extends %XML.Adaptor
    Set check=1
    Try {
       Set check=$classmethod(cls,"%Extends","%XML.Adaptor")
       } Catch {
           Set check=0
           }

    If (check'=1) {
        Write "Class does not extend %XML.Adaptor or is not compiled"
        Quit 
        }
        
    Set filename=directory_cls_".xml"
        
    Set writer=##class(%XML.Writer).%New()
    Set writer.Indent=1
    Set status=writer.OutputToFile(filename)
    If $$$ISERR(status) {Do $System.Status.DisplayError(status) Quit $$$ERROR()}
    
    Set status=writer.RootElement("SampleOutput")
    If $$$ISERR(status) {Do $System.Status.DisplayError(status) Quit $$$ERROR()}

    //Get IDs of objects in the extent for the given class
    Set rset = ##class(%ResultSet).%New(cls_":Extent")
    Set status=rset.Execute()
    If $$$ISERR(status) {Do $System.Status.DisplayError(status) Quit $$$ERROR()}

    While (rset.Next()) {
        //for each ID, write that object
        Set objid=rset.Data("ID")
        Set obj=$CLASSMETHOD(cls,"%OpenId",objid)
        Set status=writer.Object(obj)
        If $$$ISERR(status) {Do $System.Status.DisplayError(status) Quit}}
        
    Do writer.EndRootElement()
    Do writer.EndDocument()
}

}
The output from this method contains all saved objects of the given class, nested within the root element. For Sample.Person, the output is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<SampleOutput>
  <Person>
    <Name>Tillem,Robert Y.</Name>
    <SSN>967-54-9687</SSN>
    <DOB>1961-11-27</DOB>
    <Home>
      <Street>3355 First Court</Street>
      <City>Reston</City>
      <State>WY</State>
      <Zip>11090</Zip>
    </Home>
    <Office>
      <Street>4922 Main Drive</Street>
      <City>Newton</City>
      <State>NM</State>
      <Zip>98073</Zip>
    </Office>
    <FavoriteColors>
      <FavoriteColorsItem>Red</FavoriteColorsItem>
    </FavoriteColors>
    <Age>47</Age>
  </Person>
  <Person>
    <Name>Waters,Ed X.</Name>
    <SSN>361-66-2801</SSN>
    <DOB>1957-05-29</DOB>
    <Home>
      <Street>5947 Madison Drive</Street>
...
</SampleOutput>
Constructing an Element Manually
You can construct an XML element manually. In this case, you use the Element() method, which writes the start tag for an element, with the name you provide. Then you can write content, attributes, and child elements. You use the EndElement() method to indicate the end of the element.
The relevant methods are as follows:
Element()
method Element(tag, namespace As %String) as %Status
Writes the start tag. You can provide a namespace for the element, which is applied only if the XML-enabled class does not have a value for the NAMESPACE parameter. See Summary of Namespace Assignment.”
WriteAttribute()
method WriteAttribute(name As %String, 
                      value As %String  = "", 
                      namespace As %String, 
                      valueNamespace As %String  = "", 
                      global As %Boolean  = 0) as %Status
Writes an attribute. You must specify the attribute name and value. The argument namespace is the namespace for the attribute name. The argument valueNamespace is the namespace for the attribute value; this is used when the values are defined in an XML schema namespace.
For global, specify true if the attribute is global in the associated XML schema and thus should have a prefix.
If you use this method, you must use it directly after Element() (or after RootElement()).
WriteChars()
method WriteChars(text) as %Status
Writes a string, performing any necessary escaping needed to make the string suitable as the content of an element. The argument must be of type %String or %CharacterStream.
WriteCData()
method WriteCData(text) as %Status
Writes a CDATA section. The argument must be of type %String or %CharacterStream.
WriteBase64()
method WriteBase64(binary) as %Status
Encodes the specified binary bytes as base-64 and writes the resulting text as the content of the element. The argument must be of type %Binary or %BinaryStream.
WriteBinHex()
method WriteBinHex(binary) as %Status
Encodes the specified binary bytes as binhex and writes the resulting text as the content of the element. The argument must be of type %Binary or %BinaryStream.
EndElement()
method EndElement() as %Status
Ends the element to which it can be matched.
You can use these methods only between the RootElement() and EndRootElement() methods.
Note:
The methods described here are designed to enable you to write specific, logical pieces to the XML document, but in some cases, you may need more control. The %XML.Writer class provides an additional method, Write(), which you can use to write an arbitrary string. It is your responsibility to ensure that the result is a well-formed XML document; no validation is provided.
Example
An example routine follows:
 //write output to current device
 //simple demo of Element & related methods
 
 set writer=##class(%XML.Writer).%New()
 set writer.Indent=1
 
 set status=writer.OutputToDevice()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.StartDocument()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.RootElement("root")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.Element("SampleElement")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.WriteAttribute("Attribute","12345")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.Element("subelement")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.WriteChars("Content")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.EndElement()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.Element("subelement")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.WriteChars("Content")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.EndElement()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.EndElement()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.EndRootElement()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
 
 set status=writer.EndDocument()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
The output for this routine is as follows:
<?xml version="1.0" encoding="UTF-8"?>
<root>
  <SampleElement Attribute="12345">
    <subelement>Content</subelement>
    <subelement>Content</subelement>
  </SampleElement>
</root>
Using %XML.Element
In the previous section, we used the Element() and specified the element to generate; we could have also specified a namespace. In some cases, you might want to use an instance of the %XML.Element class instead of an element name. This class has the following properties:
Here you can also use the WriteAttribute() method, described earlier.
Controlling the Use of Namespaces
As described in Projecting Objects to XML, you can assign a class to a namespace so that the corresponding XML element belongs in that namespace, and you can control whether the properties of the class belong to that namespace as well.
When you export objects in the class to XML, the %XML.Writer class provides additional options such as specifying whether an element is local to its parent. This section includes the following topics:
Note:
In Caché XML support, you specify namespaces on a class-by-class basis. Typically each class has its own namespace declaration; however, usually only one or a small number of namespaces are needed. You specify related information on a class-by-class basis as well (rather than in some global manner). This includes settings that control whether an element is local to its parent and whether subelements are qualified. It is recommended that you use a consistent approach, for simplicity.
Default Handling of Namespaces
To assign an XML-enabled class to a namespace, set the NAMESPACE parameter of the class, as described in Projecting Objects to XML. The %XML.Writer class automatically inserts the namespace declarations, generates namespace prefixes, and applies the prefixes where appropriate. For example, consider the following class definition:
Class GXML.Objects.WithNamespaces.Person Extends (%Persistent, %Populate, %XML.Adaptor)
{
Parameter NAMESPACE = "http://www.person.com";
Property Name As %Name [ Required ];
Property DOB As %Date(FORMAT = 5, MAXVAL = "+$h") [ Required ];
Property GroupID As %Integer(MAXVAL=10,MINVAL=1,XMLPROJECTION="ATTRIBUTE");
}
If you export an object of this class, you see something like the following:
<Person xmlns="http://www.person.com" GroupID="4">
  <Name>Uberoth,Amanda Q.</Name>
  <DOB>1952-01-13</DOB>
</Person>
Note the following:
The Effect of Context of Namespace Assignment
The namespace to which an XML-enabled object is assigned depends upon whether the object is exported at the top level or as a property of another object.
Consider a class named Address. Suppose that you use the NAMESPACE parameter to assign the Address class to the namespace "http://www.address.org". If you exported an object of the Address class directly, you might receive output like the following:
<Address xmlns="http://www.address.org">
  <Street>8280 Main Avenue</Street>
  <City>Washington</City>
  <State>VT</State>
  <Zip>15355</Zip>
</Address>
Notice that the <Address> element and all its elements are in the same namespace ("http://www.address.org").
In contrast, suppose that the Person class that has a property that is an Address object. Also suppose that you use the NAMESPACE parameter to assign the Person class to the namespace "http://www.person.org". If you exported an object of the Person class, you would receive output like the following:
<Person xmlns="http://www.person.org">
  <Name>Zevon,Samantha H.</Name>
  <DOB>1964-05-24</DOB>
  <Address xmlns="http://www.address.org">
     <Street>8280 Main Avenue</Street>
     <City>Washington</City>
     <State>VT</State>
     <Zip>15355</Zip>
  </Address>
</Person>
Notice that the <Address> element is in the namespace of its parent object ("http://www.person.org"). The elements of <Address>, however, are within the namespace "http://www.address.org".
Controlling Whether Local Elements Are Qualified
When you export an object at the top level, it is usually treated as a global element. Then its local elements are handled according to the setting of the ELEMENTQUALIFIED parameter of the XML-enabled object. If this class parameter is not set, the value of the writer property ElementQualified is used instead; by default, this is 1 for literal format or 0 for encoded format.
The following example shows an object with the default setting of ElementQualified, which is 1:
<?xml version="1.0" encoding="UTF-8"?>
<PersonNS xmlns="http://www.person.com" GroupID="M9301">
  <Name>Pybus,Gertrude X.</Name>
  <DOB>1986-10-19</DOB>
</PersonNS>
The namespace is added to the <PersonNS> element as the default namespace and thus applies to the elements <Name> and <DOB> subelements.
Suppose that we altered the writer definition and set the ElementQualified property to 0. In this case, the same object would appear as follows:
<?xml version="1.0" encoding="UTF-8"?>
<s01:PersonNS xmlns:s01="http://www.person.com" GroupID="M9301">
  <Name>Pybus,Gertrude X.</Name>
  <DOB>1986-10-19</DOB>
</s01:PersonNS>
In this case, the namespace is added to the <PersonNS> element with a prefix, which is used for the <PersonNS> element but not for its subelements.
Controlling Whether an Element Is Local to Its Parent
By default, when you use the Object() method to generate an element and that element has a namespace, the element is not local to its parent. You can instead force the element to belong to the namespace of its parent. To do so, you use the optional local argument of the Object() method; this is the fourth argument.
Local Argument As 0 (Default)
In the examples here, consider a Person class that specifies the NAMESPACE class parameter as "http://www.person.com".
If you open the root element and then use Object() to generate a Person, the <Person> element is in the "http://www.person.com" namespace. Consider the following example:
<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns="http://www.rootns.org">
   <Person xmlns="http://www.person.com">
      <Name>Adam,George L.</Name>
      <DOB>1947-06-29</DOB>
   </Person>
</Root>
A similar result would occur if we nested the <Person> element more deeply inside other elements.
<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns="www.rootns.org">
   <GlobalEl xmlns="globalns">
      <Inner xmlns="innerns">
         <Person xmlns="http://www.person.com">
            <Name>Adam,George L.</Name>
            <DOB>1947-06-29</DOB>
         </Person>
      </Inner>
   </GlobalEl>
</Root>
Local Argument Set to 1
To force the <Person> element to be local to its parent, we set the local argument equal to 1. If we do so and generate the previous output again, we would receive the following for the less nested version:
<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns="http://www.rootns.org">
   <s01:Person xmlns="http://www.person.com" 
xmlns:s01="http://www.rootns.org">
      <Name>Adam,George L.</Name>
      <DOB>1947-06-29</DOB>
   </s01:Person>
</Root>
Note that now the <Person> element is in the "http://www.rootns.org" namespace, the namespace of its parent element. Similarly, the more highly nested version would look like this:
<?xml version="1.0" encoding="UTF-8"?>
<Root xmlns="www.rootns.org">
   <GlobalEl xmlns="globalns">
      <Inner xmlns="innerns">
         <s01:Person xmlns="http://www.person.com" xmlns:s01="innerns">
            <Name>Adam,George L.</Name>
            <DOB>1947-06-29</DOB>
         </s01:Person>
      </Inner>
   </GlobalEl>
</Root>
Controlling Whether Attributes Are Qualified
When you export an object, by default its attributes are not qualified. To make them qualified, set the writer property AttributeQualified equal to 1. The following example shows output generated with a writer for which AttributeQualified equals 0 (or has not been set):
<?xml version="1.0" encoding="UTF-8"?>
<Root>
   <s01:Person xmlns:s01="http://www.person.com" GroupID="E8401">
      <Name>Leiberman,Amanda E.</Name>
      <DOB>1988-10-28</DOB>
   </s01:Person>
</Root>
In contrast, the following example shows the same object generated with a writer for which AttributeQualified equals 1:
<?xml version="1.0" encoding="UTF-8"?>
<Root>
   <s01:Person xmlns:s01="http://www.person.com" s01:GroupID="E8401">
      <Name>Leiberman,Amanda E.</Name>
      <DOB>1988-10-28</DOB>
   </s01:Person>
</Root>
In both cases, the elements are unqualified.
Summary of Namespace Assignment
This section describes how the namespace is determined for any given element in your XML output.
Top-Level Elements
For an element that corresponds to a Caché class that is exported at the top level, the following rules apply:
  1. If you specified the NAMESPACE parameter for the class, the element is in that namespace.
  2. If that parameter is not specified, the element is in the namespace you specified in the output method that generated the element (RootObject(), RootElement(), Object(), or Element()).
  3. If you did not specify a namespace in the output method, the element is in the namespace specified by the DefaultNamespace property of the writer.
  4. If the DefaultNamespace property is null, the element is not in any namespace.
Lower-Level Elements
The subelements of the class that you are exporting are affected by the ELEMENTQUALIFIED parameter of that class. If ELEMENTQUALIFIED is not set, the value of the writer property ElementQualified is used instead; by default, this is 1 for literal format or 0 for encoded format.
If elements are qualified for a given class, the subelements of that class are assigned to namespaces as follows:
  1. If you specified the NAMESPACE parameter for the parent object, the subelements are explicitly assigned to that namespace.
  2. If that parameter is not specified, the subelements are explicitly assigned to the namespace you specified in the output method that generated the element (RootObject(), RootElement(), Object(), or Element()).
  3. If you did not specify a namespace in the output method, the subelements are explicitly assigned to the namespace given by the DefaultNamespace property of the writer.
  4. If the DefaultNamespace property is null, the subelements are not explicitly assigned to any namespace.
Controlling the Appearance of Namespace Assignments
In addition to controlling namespace assignments, you can control how the namespace assignments appear in the XML output. Specifically you can control the following:
Explicit versus Implicit Namespace Assignment
When you assign tags to a namespace, you can present the tags in two equivalent ways in XML; this style is controlled by the SuppressXmlns property of your writer instance.
Suppose that we generate XML output for an object named Person which is assigned to the namespace "http://www.person.org" (by means of the NAMESPACE class parameter, discussed earlier). An example of the default output (with SuppressXmlns equal to 0) is as follows:
<Person xmlns="http://www.person.com" GroupID="4">
  <Name>Uberoth,Amanda Q.</Name>
  <DOB>1952-01-13</DOB>
</Person>
Another possible form, which is entirely equivalent, is as follows. This was generated with SuppressXmlns equal to 1, which ensures that every element that is assigned explicitly to a namespace is shown with the prefix for that namespace.
<s01:Person xmlns:s01="http://www.person.com" GroupID="4">
  <s01:Name>Uberoth,Amanda Q.</s01:Name>
  <s01:DOB>1952-01-13</s01:DOB>
</s01:Person>
Note that this property affects only the way in which the namespace assignment is shown; it does not control how any namespace is assigned. This parameter has no effect if you do not use namespaces.
Specifying Custom Prefixes for Namespaces
When you generate XML output for an object, Caché generates namespace prefixes as needed. The first namespace prefix is s01, the next is s02, and so on. You can specify different prefixes. To do so, set the XMLPREFIX parameter in the class definitions for the XML-enabled objects themselves. This parameter has two effects:
For details, see Projecting Objects to XML.
Controlling How Empty Strings ("") Are Exported
When you XML-enable an object, you specify how null values and empty strings are projected to XML (see Projecting Objects to XML).
One of the options is to set XMLIGNORENULL equal to "RUNTIME" (not case-sensitive) in the XML-enabled class. In this case, when you use %XML.Writer to generate output, Caché uses the value of the RuntimeIgnoreNull property of the writer to determine how to handle any property that equals "", as follows:
The RuntimeIgnoreNull property of the writer has no effect unless the XMLIGNORENULL is "RUNTIME" in the XML-enabled class.
Example: RuntimeIgnoreNull Is 0 (Default)
First, consider the following class:
Class EmptyStrings.Export Extends (%Persistent, %XML.Adaptor)
{

Parameter XMLNAME="Test";


Parameter XMLIGNORENULL = "RUNTIME";


///project this one as an element 
///XMLNIL is 0, the default
Property Property1 As %String;


///project this one as an attribute 
///XMLNIL is 0, the default
Property Property2 As %String(XMLPROJECTION = "ATTRIBUTE");


///project this one as an element with XMLNIL=1
Property Property3 As %String(XMLNIL = 1);


///project this one as an attribute with XMLNIL=1
Property Property4 As %String(XMLNIL=1,XMLPROJECTION="ATTRIBUTE");

}
If you create a new instance of this class (and do not set the values of any properties), and you then use %XML.Writer to generate output for it, you see the following:
<?xml version="1.0" encoding="UTF-8"?>
<Test>
  <Property3 xsi:nil="true" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
</Test>
Example: RuntimeIgnoreNull Is 1
In this example, we set the RuntimeIgnoreNull property of the writer equal to 1. When we generate output for the same object that we used in the last example, we see the following:
<?xml version="1.0" encoding="UTF-8"?>
<Test Property2="" Property4="">
  <Property1></Property1>
  <Property3></Property3>
</Test>
In this case, because RuntimeIgnoreNull is 1, the XMLNIL parameter is not used. Instead, "" is exported as an empty attribute or an empty element.
Exporting Type Information
An XML writer does not write type information by default. There are two options you can use to include type information in the output:
Generating SOAP-Encoded XML
For the %XML.Writer class, the Format property controls the overall format of the output. This is one of the following:
Creating Inline References
In encoded format, any object-valued property is included as a reference, and the referenced object is exported as a separate element.
To export such properties inline instead of as separate elements, set the ReferencesInline property (of the writer) equal to 1.
The ReferencesInline property has no effect if Format is "literal".
Controlling Unswizzling After Export
When you export a persistent XML-enabled object, Caché automatically swizzles all needed information into memory as usual; this information includes object-valued properties. After exporting the object, Caché unswizzles any lists of objects but does not (by default) unswizzle single object references. In the case of large objects, this can result in <STORE> errors.
To cause any single object references to be unswizzled in this scenario, set the XMLUNSWIZZLE parameter in your XML-enabled class as follows:
Parameter XMLUNSWIZZLE = 1;
The default for this parameter is 0.
Controlling the Closing of Elements
An element that contains only attributes can be represented in either of the following ways:
<myelementname attribute="value" attribute="value" attribute="value"></myelementname>
<myelementname attribute="value" attribute="value" attribute="value"/>
The Object() method always exports elements with the first syntax. If you need to close an element with the second syntax shown here, write the object manually, as described in Constructing an Element Manually,” earlier in this chapter.
Other Options of the Writer
This section discusses other options provided by %XML.Writer:
Canonicalize() Method
The Canonicalize() method writes an XML node in canonicalized form. This method has the following signature:
method Canonicalize(node As %XML.Node, ByRef PrefixList, formatXML As %Boolean = 0) as %Status
Where
Shallow Property
The Shallow property of your writer instance affects the output of properties that have object values. This class property lets you force all such output to be shallow, that is, to force the output to contain IDs of the referenced objects, rather than the details of the objects. This property interacts with the XMLDEFAULTREFERENCE class parameter and XMLREFERENCE property parameter of the XML-enabled object, as shown in the following table. This table shows the resulting output, for each case:
Effect of Shallow = 1
Values of XMLREFERENCE and XMLDEFAULTREFERENCE Output if Shallow=1
Property parameter XMLREFERENCE is "SUMMARY" or "COMPLETE" No output is generated for this property
Property parameter XMLREFERENCE is "ID", "OID", or "GUID" Output is generated for this property and is of the type ID, OID, or GUID, as appropriate
Property parameter XMLREFERENCE is not set, but class parameter XMLDEFAULTREFERENCE is "SUMMARY" or "COMPLETE" No output is generated for this property
Property parameter XMLREFERENCE is not set, but class parameter XMLDEFAULTREFERENCE is "ID", "OID", or "GUID" Output is generated for this property and is of the type ID, OID, or GUID, as appropriate
Neither the property parameter XMLREFERENCE nor the class parameter XMLDEFAULTREFERENCE is set No output is generated for this property
The Shallow property does not affect properties whose values are serial objects or properties that have non-object values.
Also see Controlling the Form of the Projection for Object-valued Properties in Projecting Objects to XML.
Summary Property
The Summary property of your writer instance controls whether to export the entire XML-enabled object or just its summary; this can have one of the following values:
As noted in Projecting Objects to XML, the summary of an object is specified by its XMLSUMMARY class parameter; it is a comma-separated list of properties.
For example, suppose that you are generating output for the Person class, which is XML-enabled and suppose that your default output looks like this:
<Persons>
 <Person>
  <Name>Xenia,Yan T.</Name>
  <DOB>1986-10-21</DOB>
 </Person>
 <Person>
  <Name>Vivaldi,Ashley K.</Name>
  <DOB>1981-01-25</DOB>
 </Person>
</Persons>
Suppose you have set XMLSUMMARY equal to "Name" for the Person class. In this case, if your writer sets the Summary property to 1, the output would look like the following:
<Persons>
 <Person>
  <Name>Xenia,Yan T.</Name>
 </Person>
 <Person>
  <Name>Vivaldi,Ashley K.</Name>
 </Person>
</Persons>
Base64LineBreaks Property
You can include automatic line breaks for properties of type %Binary or %xsd.base64Binary. To do so, set the Base64LineBreaks property to 1 for your writer instance. In this case, the writer inserts an automatic line feed/carriage return after every 76 characters. The default value for this property is 0.
CycleCheck Property
The CycleCheck property of your writer instance controls whether the writer checks for any cycles (endless loops) within the referenced objects that could result in errors. The default is 1, which means that the writer does check for cycles.
If you are sure that there are no cycles, set CycleCheck to 0 for a slight improvement in performance.
Additional Example: Writer with Choice of Settings
The following method may be useful for people who want to experiment with properties of %XML.Writer. It accepts an input argument, which is a string that names a writer “version.” Each writer version corresponds to specific settings of the properties of the writer instance.
Class Utils.Writer
{

/// given a "name", return a writer with those properties
ClassMethod CreateWriter(wname) As %XML.Writer
{
 set w=##class(%XML.Writer).%New()
 set w.Indent=1
 set w.IndentChars="   "
 if wname="DefaultWriter" {
  set w.Indent=0  ; set back to default
  }
 elseif wname="EncodedWriter" {
  set w.Format="encoded"
  }
 elseif wname="EncodedWriterRefInline" {
  set w.Format="encoded"
  set w.ReferencesInline=1
  }
 elseif wname="AttQualWriter" {
  set w.AttributeQualified=1
  }
 elseif wname="AttUnqualWriter" {
  set w.AttributeQualified=0 ; default
  }
 elseif wname="ElQualWriter" {
  set w.ElementQualified=1 ; default
  }
 elseif wname="ElUnqualWriter" {
  set w.ElementQualified=0
  }
 elseif wname="ShallowWriter" {
  set w.Shallow=1
  }
 elseif wname="SOAPWriter1.1" {
  set w.Format="encoded"
  set w.ReferencesInline=1
  }
 elseif wname="SOAPWriter1.2" {
  set w.Format="encoded12"
  set w.ReferencesInline=1
  }
 elseif wname="SummaryWriter" {
  set w.Summary=1
  }
 elseif wname="WriterNoXmlDecl" {
  set w.NoXMLDeclaration=1
  }
 elseif wname="WriterRefInline" {
  set w.ReferencesInline=1
  }
 elseif wname="WriterRuntimeIgnoreNull" {
  set w.RuntimeIgnoreNull=1
  }
 elseif wname="WriterSuppressXmlns" {
  set w.SuppressXmlns=1
  }
 elseif wname="WriterUTF16" {
  set w.Charset="UTF-16"
  }
 elseif wname="WriterWithDefNS" {
  set w.DefaultNamespace="www.Def.org"
  }
 elseif wname="WriterWithDefNSSuppressXmlns" {
  set w.DefaultNamespace="www.Def.org"
  set w.SuppressXmlns=1
  }
 elseif wname="WriterWithDtdSettings" {
  set w.DocType ="MyDocType"
  set w.SystemID = "http://www.mysite.com/mydoc.dtd"
  set w.PublicID = "-//W3C//DTD XHTML 1.0 Transitional//EN"
  set w.InternalSubset = "" 
  }
 elseif wname="WriterXsiTypes" {
  set w.OutputTypeAttribute=1
  }
 quit w
}

}
The following fragment shows how this method was used to help generate examples for the documentation:
/// method to write one to a file
ClassMethod WriteOne(myfile,cls,element,wname,ns,local,rootns) As %Status
{
    set writer=..CreateWriter(wname)
    set mydir=..#MyDir
    set comment="Output for the class: "_cls
    set comment2="Writer settings: "_wname
    
    if $extract(mydir,$length(mydir))'="/" {set mydir=mydir_"/"}
    set file=mydir_myfile
    set status=writer.OutputToFile(file)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}

    set status=writer.WriteComment(comment)
    if $$$ISERR(status) {do $System.Status.DisplayError(status) quit $$$ERROR()}
    set status=writer.WriteComment(comment2)
...
}
Notice that the output will include two comment lines. One indicates the name of the XML-enabled class shown in the file. The other indicates the name of the writer settings used to generate the file. The output directory is controlled centrally (via a parameter), and this generic method includes arguments that are passed to both the RootElement() method and the Object() method.