Skip to main content
Previous section   Next section

Writing XML Output from Caché Objects

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

Also see “Writing XML Output from a DOM” in the topic “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:

  • If you need output for a particular object, the class definition for that object must extend %XML.Adaptor. With a few exceptions, the classes to which that object refers must also extend %XML.Adaptor. See Projecting Objects to XML.

  • The output method must create an instance of %XML.Writer and then use methods of that instance.

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>
Copy code to clipboard

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 items:

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:

    • Indent — Controls whether the output is generated within indentation and line wrapping (if Indent equals 1) or as a single long line (if Indent equals 0). The latter is the default.

      See the later subsection “Details on Indent Option.”

    • IndentChars — Specifies the characters used for indentation. The default is a string of two spaces. This property has no effect if Indent is 0.

    • Charset — Specifies the character set to use. See “Specifying the Character Set of the Output.”

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

  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:

    • OutputToDevice() — Directs the output to the current device.

    • OutputToFile() — Directs the output to the specified file. You can specify an absolute path or a relative path. Note that the directory path must already exist.

    • OutputToString() — Directs the output to a string. Later you can use another method to retrieve this string.

    • OutputToStream() — Directs the output to the specified stream.

  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:

    • The root element might correspond directly to a Caché object. This is typically the case if you are generating output for a single object.

      In this case, you use the RootObject() method, which writes the specified XML-enabled object as the root element.

    • The root element might be merely a wrapper for a set of elements, and those elements are Caché objects.

      In this case, you use the RootElement() method, which inserts the root-level element with the name you specify.

    Also see “Writing the Root Element.”

  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:

    • You can use the Object() method, which writes an XML-enabled object. You specify the name of this element, or you can use the default, which is defined by the object.

    • You can 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, by using methods such as WriteAttribute(), WriteChars(), WriteCData(), and others. A child element could be another Element() or could be an Object(). You use the EndElement() method to indicate the end of the element.

    • You can use the %XML.Element class and construct an element manually.

    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.
Copy code to clipboard

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.

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($$$GeneralError, "Output destination not valid")
    }

    set status=writer.RootObject(obj)
    if $$$ISERR(status) {
        do $System.Status.DisplayError(status) 
        quit $$$ERROR($$$GeneralError, "Error writing root object")
    }
        
    quit status
}
Copy code to clipboard

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)
Copy code to clipboard

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>
Copy code to clipboard

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
}
}
Copy code to clipboard

This method gets all the IDs from the current extent for any given class and returns them in a list.

The writer method is shown below:

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($$$GeneralError, "Output destination not valid")
    }

    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($$$GeneralError,"Error writing root object")}
        
    quit status
}
Copy code to clipboard

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:

  • Any element that contains only whitespace characters is converted to an empty element.

  • Each element is placed on its own line.

  • If an element is a child of a preceding element, the element is indented relative to that parent element. The indentation is determined by the IndentChars property, which defaults to two spaces.

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 schema 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]>
Copy code to clipboard

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
Copy code to clipboard

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">>
Copy code to clipboard

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)>
  ] >
Copy code to clipboard

The WriteDocType() method has four arguments:

  • The first argument specifies the name of the document type, for use within this XML document. This is required and must be a valid XML identifier. You also must use this same name as the name of root level element in this document.

  • The optional second and third arguments specify the external part of the declaration, as follows:

    WriteDocType Arguments
    Second Argument Third Argument Resulting External Part
    publicURI null PUBLIC “publicURI
    publicURI systemURI PUBLIC “publicURI” “systemURI
    null systemURI SYSTEM “systemURI
  • The optional fourth argument specifies the internal part of the declaration. If this argument is non-null, then it is wrapped in square brackets [] and placed appropriately at the end of the declaration. No other characters are added.

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?>
Copy code to clipboard

For example, suppose that you wanted to write the following processing instruction:

<?xml-stylesheet type="text/css" href="mystyles.css"?>
Copy code to clipboard

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)
Copy code to clipboard

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:

  • You can specify a default namespace within an output method. The four primary output methods (RootObject(), RootElement(), Object(), or Element()) all accept a namespace as an argument. The relevant element is assigned to the namespace only if the NAMESPACE parameter is not set in the class definition.

  • You can specify an overall default namespace for the writer instance. To do so, specify a value for the DefaultNamespace property of your writer instance.

Example

Consider the following class:

Class Writers.BasicDemoPerson Extends (%RegisteredObject, %XML.Adaptor)
{

Parameter XMLNAME="Person";

Property Name As %Name;

Property DOB As %Date;

}
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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;

}
Copy code to clipboard

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>
...
Copy code to clipboard

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
Copy code to clipboard

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>
...
Copy code to clipboard

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")
Copy code to clipboard

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">
...
Copy code to clipboard

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")
Copy code to clipboard

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">
...
Copy code to clipboard
AddInstanceNamespace()
method AddInstanceNamespace(prefix As %String) as %Status
Copy code to clipboard

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">
...
Copy code to clipboard
AddSchemaNamespace()
method AddSchemaNamespace(prefix As %String) as %Status
Copy code to clipboard

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">
...
Copy code to clipboard
AddSOAPNamespace()
method AddSOAPNamespace(soapPrefix As %String, 
                        schemaPrefix As %String, 
                        xsiPrefix As %String) as %Status
Copy code to clipboard

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">
...
Copy code to clipboard

Also see “Generating SOAP-Encoded XML,” later in this topic.

AddSOAP12Namespace()
method AddSOAP12Namespace(soapPrefix As %String, 
                          schemaPrefix As %String, 
                          xsiPrefix As %String) as %Status
Copy code to clipboard

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">
...
Copy code to clipboard

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 topic “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:

  • The root element might correspond directly to a single Caché XML-enabled object.

    In this case, you use the RootObject() method, which writes the specified XML-enabled object as the root element. The output includes all object references contained in that object. The root element acquires the structure of that object, and you cannot insert additional elements. You can specify a name for the root element, or you use the default, which is defined by the XML-enabled object.

    Previous examples used this technique.

  • The root element might be merely a wrapper for a set of elements (perhaps for a set of XML-enabled objects).

    In this case, you use the RootElement() method, which inserts the root-level element with the name you specify. This method also increases the indentation level for succeeding operations, if this document is indented.

    Then you call additional methods to generate the output for one or more elements inside the root element. Inside the root, you can include elements you need, with any order or logic you choose. See “Constructing an Element Manually” for further details. After this, you call the EndRootElement() method to close the root element.

    For an example, see the next section.

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

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:

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  }
    
    Set status=writer.RootElement("SampleOutput")
    if $$$ISERR(status) { do $System.Status.DisplayError(status) quit  }

    //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  }

    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()
}
Copy code to clipboard

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>
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

Writes a CDATA section. The argument must be of type %String or %CharacterStream.

WriteBase64()
method WriteBase64(binary) as %Status
Copy code to clipboard

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
Copy code to clipboard

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
Copy code to clipboard

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}
 
 set status=writer.StartDocument()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.RootElement("root")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.Element("SampleElement")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.WriteAttribute("Attribute","12345")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.Element("subelement")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.WriteChars("Content")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.EndElement()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.Element("subelement")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.WriteChars("Content")
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.EndElement()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.EndElement()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.EndRootElement()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
 
 set status=writer.EndDocument()
 if $$$ISERR(status) {do $System.Status.DisplayError(status) quit}
Copy code to clipboard

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>
Copy code to clipboard

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:

  • The Local property specifies whether this element is local to its parent element, which affects the control of namespaces. For details, see “Controlling Whether an Element Is Local to Its Parent, ”ahead.

  • The Namespace property specifies the namespace for this element.

  • The Tagname property specifies the name for this element.

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 items:

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");
}
Copy code to clipboard

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>
Copy code to clipboard

Note the following:

  • The namespace declaration is added to each <Person> element.

  • Local elements (<Name> and <DOB>) of the <Person> element are qualified by default. The namespace is added as the default namespace and thus applies to these elements.

  • The attribute (GroupID) of the <Person> element is not qualified by default. This attribute has no prefix and thus is considered unqualified.

  • The prefixes shown here were automatically generated. (Remember that when you assign an object to a namespace, you specify only the namespace, not the prefix.)

  • This output occurs without setting any namespace-related properties in the writer and without using any namespace-related methods in the writer.

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>
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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 elements and attributes to a namespace, there are two equivalent representations in XML, 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>
Copy code to clipboard

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>
Copy code to clipboard

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, the system 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:

  • It ensures that the prefix you specify is declared in the XML output. That is, it is declared even if doing so is not necessary.

  • It uses that prefix rather than the automatically generated prefix that you would otherwise see.

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:

  • If the RuntimeIgnoreNull property of the writer is 0 (the default), the XMLNIL parameter controls how to export the property. XMLNIL is a class parameter and a property parameter; the property parameter takes precedence.

    • If XMLNIL is 0 (the default), the property is not projected. That is, it is not included in the XML document.

    • If XMLNIL is 1 and if the property is used as an element, the property is exported as follows:

      <PropName xsi:nil="true" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"/>
      Copy code to clipboard
    • If XMLNIL is 1 and if the property is used as an attribute, the property is not exported.

  • If the RuntimeIgnoreNull property of the writer is 1, the property is exported as an empty element or an empty attribute (it is exported the same way as the value $char(0), which is always exported as an empty element or an empty exported).

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");

}
Copy code to clipboard

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>
Copy code to clipboard

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>
Copy code to clipboard

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:

  • The OutputTypeAttribute property of the writer. If this property is 1, the writer includes XML type information for all the elements within the objects that it writes (but not for the objects themselves). For example:

    <?xml version="1.0" encoding="UTF-8"?>
    <Root xmlns:s="http://www.w3.org/2001/XMLSchema" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <Person>
        <Name xsi:type="s:string">Petersburg,Greta U.</Name>
        <DOB xsi:type="s:date">1949-05-15</DOB>
      </Person>
    </Root>
    Copy code to clipboard

    Note that the appropriate namespace is added to the root of the XML document.

  • The className argument of the Object() and RootObject() methods. You use this argument to specify the expected ObjectScript type of the object (the name of the class).

    If the argument is the same as the actual type, the writer does not include type information for the object.

    If the argument is different from the actual type, the writer includes the actual XML type for the object (which defaults to the class name). For example, suppose that you write output for an instance of Test2.PersonWithAddress, and suppose that you specify the className argument as MyPackage.MyClass. Because MyPackage.MyClass is not the same as the actual class name, the writer generates the following output:

    <?xml version="1.0" encoding="UTF-8"?>
    <PersonWithAddress xsi:type="PersonWithAddress" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
      <Name>Avery,Robert H.</Name>
      <Address>
        <City>Ukiah</City>
        <Zip>82281</Zip>
      </Address>
    </PersonWithAddress>
    Copy code to clipboard

    For the complete argument list for the Object() and RootObject() methods, see the class reference.

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:

  • "literal", the default, which is used in most of the examples in this book.

  • "encoded", encoded as described in the SOAP 1.1 standard.

  • "encoded12", encoded as described in the SOAP 1.2 standard.

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, the system 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;
Copy code to clipboard

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

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
Copy code to clipboard

Where

  • node is a subtree of the document, as an instance of %XML.Node, which is discussed in the topic “Representing an XML Document as a DOM.”

  • PrefixList is one of the following:

    • For inclusive canonicalization, specify PrefixList as "c14n". In this case, the output is in the form given by XML Canonicalization Version 1.0, as specified by https://www.w3.org/TR/xml-c14n.

    • For exclusive canonicalization, specify PrefixList as a multidimensional array with the following nodes:

      Node Value
      PrefixList(prefix) where prefix is a namespace prefix Namespace used with this namespace prefix

      In this case, the output is in the form given by Exclusive XML Canonicalization Version 1.0, as specified by https://www.w3.org/TR/xml-exc-c14n/.

  • formatXML controls the format. If formatXML is true, then the writer uses the formatting specified for the writer instance rather than the formatting specified by the XML Canonicalization specification. The output is then not canonical XML, but has done the namespace processing for canonical XML. This option is useful for outputting a fragment of an XML document, such as the SOAP body in the ProcessBodyNode() callback from a web service, while still having some control of the format.

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:

  • The value 0 exports the entire object; this is the default.

  • The value 1 exports just the properties that are listed as the summary.

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>
Copy code to clipboard

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>
Copy code to clipboard

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
}

}
Copy code to clipboard

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)
{
    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 }

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

    set status=writer.WriteComment(comment2)
...
}
Copy code to clipboard

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.