Writing XML Output from Caché Objects
This topic describes how you can generate XML output from Caché objects. It discusses the following items:
-
How to specify a default namespace for classes without a namespace
-
How to control the output for string properties that equal ""
-
Additional example, for use when experimenting with %XML.WriterOpens in a new tab
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.AdaptorOpens in a new tab. With a few exceptions, the classes to which that object refers must also extend %XML.AdaptorOpens in a new tab. See Projecting Objects to XML.
-
The output method must create an instance of %XML.WriterOpens in a new tab 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>
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:
-
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.WriterOpens in a new tab 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).
-
Create an instance of the %XML.WriterOpens in a new tab 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.
-
-
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.
-
-
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().
-
Optionally write lines of the prolog of the document. You can use the following methods:
-
WriteDocType() — Writes the DOCTYPE declaration. For details, see “Generating a Document Type Declaration.”
-
WriteProcessingInstruction() — Writes a processing instruction. For details, see “Writing Processing Instructions.”
-
-
Optionally specify the default namespace. The writer uses this for classes that do not have a defined XML namespace. See “Specify the Default Namespace.”
-
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.”
-
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.”
-
-
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.ElementOpens in a new tab class and construct an element manually.
See “Constructing an Element Manually” for further details.
-
-
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.
-
If you started the document with StartDocument(), call the EndDocument() method to close the document.
-
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().
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.WriterOpens in a new tab 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.WriterOpens in a new tab 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
}
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 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
}
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.WriterOpens in a new tab 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:
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.”
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]>
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:
-
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 ArgumentsSecond 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:
-
The name of the processing instruction (also known as the target).
-
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)
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;
}
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.WriterOpens in a new tab 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().
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.”
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 NAMESPACEOpens in a new tab 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">
...
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">
...
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">
...
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 topic.
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 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:
-
Construct an element manually with the Element() method
-
Construct an element manually with %XML.ElementOpens in a new tab
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()
}
The output from this method contains all saved objects of the given class, nested within the root element. For Sample.PersonOpens in a new tab, 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:
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.”
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()).
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 %StringOpens in a new tab or %CharacterStreamOpens in a new tab.
method WriteCData(text) as %Status
Writes a CDATA section. The argument must be of type %StringOpens in a new tab or %CharacterStreamOpens in a new tab.
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 %BinaryOpens in a new tab or %BinaryStreamOpens in a new tab.
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 %BinaryOpens in a new tab or %BinaryStreamOpens in a new tab.
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.
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 Opens in a new tabclass 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}
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.ElementOpens in a new tab 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.WriterOpens in a new tab class provides additional options such as specifying whether an element is local to its parent. This section includes the following items:
-
How the %XML.WriterOpens in a new tab class handles namespaces by default
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.WriterOpens in a new tab 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 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>
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:
-
If you specified the NAMESPACE parameter for the class, the element is in that namespace.
-
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()).
-
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.
-
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:
-
If you specified the NAMESPACE parameter for the parent object, the subelements are explicitly assigned to that namespace.
-
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()).
-
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.
-
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>
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, 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.WriterOpens in a new tab 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"/>
-
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");
}
If you create a new instance of this class (and do not set the values of any properties), and you then use %XML.WriterOpens in a new tab 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:
-
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>
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>
For the complete argument list for the Object() and RootObject() methods, see the class reference.
Generating SOAP-Encoded XML
For the %XML.WriterOpens in a new tab 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;
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.WriterOpens in a new tab:
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
-
node is a subtree of the document, as an instance of %XML.NodeOpens in a new tab, 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-c14nOpens in a new tab.
-
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/Opens in a new tab.
-
-
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 XMLDEFAULTREFERENCEOpens in a new tab class parameter and XMLREFERENCEOpens in a new tab property parameter of the XML-enabled object, as shown in the following table. This table shows the resulting output, for each case:
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 XMLSUMMARYOpens in a new tab 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 %BinaryOpens in a new tab or %xsd.base64BinaryOpens in a new tab. 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.WriterOpens in a new tab. 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)
{
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)
...
}
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.