Using PowerBuilder XML Services

About this chapter

This chapter presents an overview of XML services in PowerBuilder. It describes the PowerBuilder Document Object Model (PBDOM), and describes how to use it in a PowerBuilder application.

About XML and PowerBuilder

PowerBuilder provides several features that enable you to work with the Extensible Markup Language (XML). You can:

  • Export the data in a DataWindow object to XML, and import data in an XML document or string into a DataWindow object

  • Determine whether an XML document or string is well-formed or conforms to a schema or DTD using the XMLParseFile and XMLParseString PowerScript functions

  • Build applications and components that can produce and process XML documents

For an overview of XML and information about the export and import capabilities in the DataWindow, see the section called “Exporting and Importing XML Data” in Users Guide.

For information about the XML parsing functions, see the section called “XMLParseFile” in PowerScript Reference and the section called “XMLParseString” in PowerScript Reference.

This chapter describes how you can produce and process XML documents using the PowerBuilder Document Object Model.

About PBDOM

PBDOM is the PowerBuilder implementation of the Document Object Model (DOM), a programming interface defining the means by which XML documents can be accessed and manipulated.

Although PBDOM is not an implementation of the World Wide Web Consortium (W3C) DOM API, it is very similar. The PBDOM PowerBuilder API can be used for reading, writing, and manipulating standard-format XML from within PowerScript code. PBDOM portrays an XML document as a collection of interconnected objects and provides intuitive methods indicating the use and functionality of each object.

PBDOM is also similar to JDOM, which is a Java-based document object model for XML files.

For information on the W3C DOM and JDOM objects and hierarchies, refer to their respective specifications. The W3C DOM specification is available at http://www.w3.org/DOM/. The JDOM specification, or a link to it, is available at http://www.jdom.org/docs/.

With PBDOM, your applications can parse existing XML documents and extract the information contained as part of a business process or in response to an external request. Applications can also produce XML documents that conform to the type or schema required by other applications, processes, or systems. Existing XML documents can be read and modified by manipulating or transforming the PBDOM tree of objects instead of having to edit XML strings directly.

You can also build components that can produce or process XML documents for use in multitier applications or as part of a Web service.

Node trees

PBDOM interacts with XML documents according to a tree-view model consisting of parent and child nodes. A document element represents the top-level node of an XML document. Each child node of the document element has one or many child nodes that represent the branches of the tree. Nodes in the tree are accessible through PBDOM class methods.

XML parser

The PBDOM XML parser is used to load and parse an XML document, and also to generate XML documents based on user-specified DOM nodes.

PBDOM provides all the methods you need to traverse the node tree, access the nodes and attribute values (if any), insert and delete nodes, and convert the node tree to an XML document so that it can be used by other systems.

PBDOM object hierarchy

The following figure shows the PBDOM object hierarchy:

Figure: The PBDOM object hierarchy

PBDOM_OBJECT and its descendants

The base class for PBDOM objects that represent XML nodes, PBDOM_OBJECT, inherits from the PowerBuilder NonVisualObject class. Each of the node types is represented by a PBDOM class whose methods you use to access objects in a PBDOM node tree. PBDOM_OBJECT and its descendants are described in PBDOM node objects. You can also find some information about XML node types in the section called “Exporting and Importing XML Data” in Users Guide.

PBDOM_BUILDER

The PBDOM_BUILDER class also inherits from NonVisualObject. It serves as a factory class that creates a PBDOM_DOCUMENT from various XML input sources including a string, a file, and a DataStore.

Building a PBDOM_DOCUMENT from scratch

To build a PBDOM_DOCUMENT without a source that contains existing XML, use the PBDOM_DOCUMENT NewDocument methods.

PBDOM_EXCEPTION

The PBDOM_EXCEPTION class inherits from the PowerBuilder Exception class. It extends the Exception class with a method that returns a predefined exception code when an exception is raised in a PBDOM application. For more information about this class, see Handling PBDOM exceptions.

PBDOM node objects

This section describes the PBDOM_OBJECT class and all of the classes that descend from it:

For detailed descriptions of PBDOM class methods, see PowerBuilder Extension Reference.

PBDOM_OBJECT

The PBDOM_OBJECT class represents any node in an XML node tree and serves as the base class for specialized PBDOM classes that represent specific node types. The DOM class that corresponds to PBDOM_OBJECT is the Node object. PBDOM_OBJECT contains all the basic features required by derived classes. A node can be an element node, a document node, or any of the node types listed above that derive from PBDOM_OBJECT.

Methods

The PBDOM_OBJECT base class has the following methods:

  • AddContent, GetContent, InsertContent, RemoveContent, and SetContent to allow you to manipulate the children of the PBDOM_OBJECT

  • Clone to allow you to make shallow or deep clones of the PBDOM_OBJECT

  • Detach to detach the PBDOM_OBJECT from its parent

  • Equals to test for equality with another PBDOM_OBJECT

  • GetName and SetName to get and set the name of the PBDOM_OBJECT

  • GetObjectClass and GetObjectClassString to identify the class of the PBDOM_OBJECT

  • GetOwnerDocumentObject to identify the owner PBDOM_DOCUMENT of the current PBDOM_OBJECT

  • GetParentObject and SetParentObject to get and set the parent of the PBDOM_OBJECT

  • GetText, GetTextNormalize, and GetTextTrim to obtain the text data of the PBDOM_OBJECT

  • HasChildren to determine whether the PBDOM_OBJECT has any children

  • IsAncestorObjectOf to determine whether the PBDOM_OBJECT is the ancestor of another PBDOM_OBJECT

PBDOM_OBJECT inheritance

The PBDOM_OBJECT class is similar to a virtual class in C++ in that it is not expected to be directly instantiated and used. For example, although a PBDOM_OBJECT can be created using the PowerScript CREATE statement, its methods cannot be used directly:

PBDOM_OBJECT pbdom_obj
pbdom_obj = CREATE PBDOM_OBJECT
pbdom_obj.SetName("VIRTUAL_PBDOM_OBJ") //exception!

The third line of code above throws an exception because it attempts to directly access the SetName method for the base class PBDOM_OBJECT. A similar implementation is valid, however, when the SetName method is accessed from a derived class, such as PBDOM_ELEMENT:

PBDOM_OBJECT pbdom_obj
pbdom_obj = CREATE PBDOM_ELEMENT
pbdom_obj.SetName ("VIRTUAL_PBDOM_OBJ")

Using the base PBDOM_OBJECT as a placeholder

The PBDOM_OBJECT class can be used as a placeholder for an object of a derived class:

PBDOM_DOCUMENT pbdom_doc
PBDOM_OBJECT pbdom_obj

pbdom_doc = CREATE PBDOM_DOCUMENT
pbdom_doc.NewDocument ("", "", &
   "Root_Element_From_Doc_1", "", "")
pbdom_obj = pbdom_doc.GetRootElement
pbdom_obj.SetName &
   ("Root_Element_From_Doc_1_Now_Changed")

The instantiated PBDOM_OBJECT pbdom_obj is assigned to a PBDOM_DOCUMENT object, which holds the return value of the GetRootElement method. Here, pbdom_obj holds a reference to a PBDOM_ELEMENT and can be operated on legally like any object of a class derived from PBDOM_OBJECT.

Standalone objects

A PBDOM_OBJECT can be created as a self-contained object independent of any document or parent PBDOM_OBJECT. Such a PBDOM_OBJECT is known as a standalone object. For example:

PBDOM_ELEMENT pbdom_elem_1
pbdom_elem_1 = Create PBDOM_ELEMENT
pbdom_elem_1.SetName("pbdom_elem_1")

pbdom_elem_1 is instantiated in the derived class PBDOM_ELEMENT using the Create keyword. The SetName method can then be invoked from the pbdom_elem_1 object, which is a standalone object not contained within any document.

Standalone objects can perform any legal PBDOM operations, but standalone status does not give the object any special advantages or disadvantages.

Parent-owned and document-owned objects

A PBDOM_OBJECT can be assigned a parent by appending it to another standalone PBDOM_OBJECT, as in the following example:

PBDOM_ELEMENT pbdom_elem_1
PBDOM_ELEMENT pbdom_elem_2

pbdom_elem_1 = Create PBDOM_ELEMENT
pbdom_elem_2 = Create PBDOM_ELEMENT

pbdom_elem_1.SetName("pbdom_elem_1")
pbdom_elem_2.SetName("pbdom_elem_2")
pbdom_elem_1.AddContent(pbdom_elem_2)

Two PBDOM_ELEMENT objects, pbdom_elem_1 and pbdom_elem_2, are instantiated. The pbdom_elem_2 object is appended as a child object of pbdom_elem_1 using the AddContent method.

In this example, neither pbdom_elem_1 nor pbdom_elem_2 is owned by any document, and the pbdom_elem_1 object is still standalone. If pbdom_elem_1 were assigned to a parent PBDOM_OBJECT owned by a document, pbdom_elem_1 would cease to be a standalone object.

PBDOM_DOCUMENT

The PBDOM_DOCUMENT class derives from PBDOM_OBJECT and represents an XML DOM document. The PBDOM_DOCUMENT methods allow access to the root element, processing instructions, and other document-level information.

Methods

In addition to the methods inherited from PBDOM_OBJECT, the PBDOM_DOCUMENT class has the following methods:

  • DetachRootElement, GetRootElement, HasRootElement, and SetRootElement to manipulate the root element of the PBDOM_DOCUMENT

  • GetDocType and SetDocType to get and set the DOCTYPE declaration of the XML document

  • NewDocument to build a new PBDOM_DOCUMENT from scratch

  • SaveDocument to save the content of the DOM tree in the PBDOM_DOCUMENT to a file

PBDOM_DOCTYPE

The PBDOM_DOCTYPE class represents the document type declaration object of an XML DOM document. The PBDOM_DOCTYPE methods allow access to the root element name, the internal subset, and the system and public IDs.

Methods

In addition to the methods inherited from PBDOM_OBJECT, the PBDOM_DOCTYPE class has the following methods:

  • GetPublicID, SetPublicID, GetSystemID, and SetSystemID to get and set the public and system IDs of an externally-referenced ID declared in the PBDOM_DOCTYPE

  • GetInternalSubset and SetInternalSubset to get and set the internal subset data of the PBDOM_DOCTYPE

PBDOM_ELEMENT

The PBDOM_ELEMENT represents an XML element modeled in PowerScript. The PBDOM_ELEMENT methods allow access to element attributes, children, and text.

Methods

In addition to the methods inherited from PBDOM_OBJECT, the PBDOM_ELEMENT class has the following methods:

  • AddNamespaceDeclaration and RemoveNamespaceDeclaration to add namespace declarations to and remove them from the PBDOM_ELEMENT

  • GetAttribute, GetAttributes, GetAttributeValue, HasAttributes, RemoveAttribute, SetAttribute, and SetAttributes to manipulate the attributes of the PBDOM_ELEMENT

  • GetChildElement, GetChildElements, HasChildElements, RemoveChildElement, and RemoveChildElements to manipulate the children of the PBDOM_ELEMENT

  • GetNamespacePrefix and GetNamespaceURI to get the prefix and URI of the namespace associated with the PBDOM_ELEMENT

  • GetQualifiedName to get the full name of the PBDOM_ELEMENT including the prefix (if any)

  • SetDocument to set a PBDOM_DOCUMENT as the parent of the PBDOM_ELEMENT

  • SetNamespace to set the namespace of the PBDOM_ELEMENT

  • SetText to set the text content of the PBDOM_ELEMENT

The relationship between PBDOM_ELEMENT and PBDOM_ATTRIBUTE

In PBDOM, an XML element's attributes are not its children. They are properties of elements rather than having a separate identity from the elements they are associated with.

Consider the following simple XML document:

<root attr="value1">
   <child attr_1="value1" attr_2="value2"/>
</root>

The equivalent PBDOM tree is shown in the following figure:

Figure: Relationship between PBDOM_ELEMENTs and PBDOM_ATTRIBUTEs

The solid line joining root with child represents a parent-child relationship. The dashed lines represent a "property-of" relationship between an attribute and its owner element.

The PBDOM_ELEMENT content management methods do not apply to PBDOM_ATTRIBUTE objects. There are separate get, set, and remove methods for attributes.

Because they are not children of their owner elements, PBDOM does not consider attributes as part of the overall PBDOM document tree, but they are linked to it through their owner elements.

An attribute can contain child objects (XML text and entity reference nodes), so an attribute forms a subtree of its own.

Because an element's attributes are not considered its children, they have no sibling relationship among themselves as child objects do. In the sample XML document and in the following figure, attr_1 and attr_2 are not siblings. The order of appearance of attributes inside its owner element has no significance.

Attribute setting and creation

In PBDOM, an XML element's attribute is set using the PBDOM_ELEMENT SetAttribute and SetAttributes methods. These methods always attempt to create new attributes for the PBDOM_ELEMENT and attempt to replace existing attributes with the same name and namespace URI.

If the PBDOM_ELEMENT already contains an existing attribute with the same name and namespace URI, these methods first remove the existing attribute and then insert a new attribute into the PBDOM_ELEMENT. Calling the SetAttribute method can cause a PBDOM_ATTRIBUTE (representing an existing attribute of the PBDOM_ELEMENT) to become detached from its owner PBDOM_ELEMENT.

For example, consider the following element:

<an_element an_attr="some_value"/>

If a PBDOM_ELEMENT object pbdom_an_elem represents the element an_element and the following statement is issued, the method first attempts to create a new attribute for the an_element element:

pbdom_an_elem.SetAttribute("an_attr", "some_other_value")

Then, because an_element already contains an attribute with the name an_attr, the attribute is removed. If there is an existing PBDOM_ATTRIBUTE object that represents the original an_attr attribute, this PBDOM_ATTRIBUTE is detached from its owner element (an_element).

For more information about attributes and namespaces, see XML namespaces.

PBDOM_ATTRIBUTE

The PBDOM_ATTRIBUTE class represents an XML attribute modeled in PowerScript. The PBDOM_ATTRIBUTE methods allow access to element attributes and namespace information.

Methods

In addition to the methods inherited from PBDOM_OBJECT, the PBDOM_ATTRIBUTE class has the following methods:

  • GetBooleanValue, SetBooleanValue, GetDateValue, SetDateValue, GetDateTimeValue, SetDateTimeValue, GetDoubleValue, SetDoubleValue, GetIntValue, SetIntValue, GetLongValue, SetLongValue, GetRealValue, SetRealValue, GetTimeValue, SetTimeValue, GetUIntValue, SetUintValue, GetULongValue,and SetULongValue to get and set the value of the PBDOM_ATTRIBUTE as the specified datatype

  • GetNamespacePrefix and GetNamespaceURI to get the prefix and URI of the namespace associated with the PBDOM_ATTRIBUTE

  • GetOwnerElementObject and SetOwnerElementObject to get and set the owner PBDOM_ELEMENT of the PBDOM_ATTRIBUTE

  • GetQualifiedName to get the full name of the PBDOM_ATTRIBUTE including the prefix, if any

  • SetNamespace to set the namespace of the PBDOM_ATTRIBUTE

  • SetText to set the text content of the PBDOM_ATTRIBUTE

Child PBDOM_OBJECTs

A PBDOM_ATTRIBUTE contains a subtree of child PBDOM_OBJECTs. The child objects can be a combination of PBDOM_TEXT and PBDOM_ENTITYREFERENCE objects.

The following example produces a PBDOM_ELEMENT named elem that contains a PBDOM_ATTRIBUTE named attr:

PBDOM_ATTRIBUTE pbdom_attr
PBDOM_TEXT pbdom_txt
PBDOM_ENTITYREFERENCE pbdom_er
PBDOM_ELEMENT pbdom_elem

pbdom_elem  = Create PBDOM_ELEMENT 
pbdom_elem.SetName ("elem")

pbdom_attr = Create PBDOM_ATTRIBUTE
pbdom_attr.SetName("attr")
pbdom_attr.SetText("Part 1 ")

pbdom_txt = Create PBDOM_TEXT
pbdom_txt.SetText (" End.")

pbdom_er = Create PBDOM_ENTITYREFERENCE 
pbdom_er.SetName("ER")

pbdom_attr.AddContent(pbdom_er)
pbdom_attr.AddContent(pbdom_txt)

pbdom_elem.SetAttribute(pbdom_attr)

The element tag in the XML looks like this:

<elem attr="Part 1 &ER; End.">

In the following figure, the arrows indicate a parent-child relationship between the PBDOM_ATTRIBUTE and the other PBDOM_OBJECTs:

Figure: PBDOM_ATTRIBUTE subtree example

The Default PBDOM_TEXT child

A PBDOM_ATTRIBUTE generally always contains at least one PBDOM_TEXT child that might contain an empty string. This is the case unless the RemoveContent method has been called to remove all contents of the PBDOM_ATTRIBUTE.

The following examples show how a PBDOM_TEXT object with an empty string can become the child of a PBDOM_ATTRIBUTE.

Example 1

The following example uses the PBDOM_ELEMENT SetAttribute method. The name of the PBDOM_ATTRIBUTE is set to attr but the text value is an empty string. The PBDOM_ATTRIBUTE will have one child PBDOM_TEXT that will contain an empty string:

PBDOM_DOCUMENT  pbdom_doc
PBDOM_ATTRIBUTE  pbdom_attr
PBDOM_OBJECT    pbdom_obj_array[]

try

  pbdom_doc = Create PBDOM_DOCUMENT
  pbdom_doc.NewDocument("root")

  // Note that the name of the attribute is set to
  // "attr" and its text value is the empty string ""
  pbdom_doc.GetRootElement().SetAttribute("attr", "")
  
  pbdom_attr = &
     pbdom_doc.GetRootElement().GetAttribute("attr")

  MessageBox ("HasChildren", &
     string(pbdom_attr.HasChildren()))
  
catch(PBDOM_EXCEPTION pbdom_except)
  MessageBox ("PBDOM_EXCEPTION", &
     pbdom_except.GetMessage())
end try

When you use the SaveDocument method to render pbdom_doc as XML, it looks like this:

<root attr="" />

Example 2

The following example creates a PBDOM_ATTRIBUTE and sets its name to attr. No text value is set, but a PBDOM_TEXT object is automatically created and attached to the PBDOM_ATTRIBUTE. This is the default behavior for every PBDOM_ATTRIBUTE created in this way:

PBDOM_DOCUMENT  pbdom_doc
PBDOM_ATTRIBUTE  pbdom_attr

try
  pbdom_doc = Create PBDOM_DOCUMENT
  pbdom_doc.NewDocument("root")
  
  // Create a PBDOM_ATTRIBUTE and set its name to "attr"
  pbdom_attr = Create PBDOM_ATTRIBUTE
  pbdom_attr.SetName("attr")

  pbdom_doc.GetRootElement().SetAttribute(pbdom_attr)
  
  MessageBox ("HasChildren", &
     string(pbdom_attr.HasChildren()))
  
catch(PBDOM_EXCEPTION pbdom_except)
  MessageBox ("PBDOM_EXCEPTION", &
     pbdom_except.GetMessage())
end try

When you call the SetText method (or any of the other Set* methods except SetNamespace), the default PBDOM_TEXT is replaced by a new PBDOM_TEXT. If you call the SetContent method, you can replace the default PBDOM_TEXT by a combination of PBDOM_TEXT and PBDOM_ENTITYREFERENCE objects.

PBDOM_ENTITYREFERENCE

The PBDOM_ENTITYREFERENCE class defines behavior for an XML entity reference node. It is a simple class intended primarily to help you insert entity references within element nodes as well as attribute nodes.

When the PBDOM_BUILDER class parses an XML document and builds up the DOM tree, it completely expands entities as they are encountered in the DTD. Therefore, immediately after a PBDOM_DOCUMENT object is built using any of the PBDOM_BUILDER build methods, there are no entity reference nodes in the resulting document tree.

A PBDOM_ENTITYREFERENCE object can be created at any time and inserted into any document whether or not there is any corresponding DOM entity node representing the referenced entity in the document.

Methods

The PBDOM_ENTITYREFERENCE class has only methods that are inherited from PBDOM_OBJECT.

PBDOM_CHARACTERDATA

The PBDOM_CHARACTERDATA class derives from PBDOM_OBJECT and represents character-based content (not markup) within an XML document. The PBDOM_CHARACTERDATA class extends PBDOM_OBJECT with methods specifically designed for manipulating character data.

Methods

In addition to the methods inherited from PBDOM_OBJECT, the PBDOM_CHARACTERDATA class has the following methods:

  • Append to append a text string or the text data of a PBDOM_CHARACTERDATA object to the text in the current object

  • SetText to set the text content of the PBDOM_CHARACTERDATA object

Parent of three classes

The PBDOM_CHARACTERDATA class is the parent class of three other PBDOM classes:

  • PBDOM_TEXT

  • PBDOM_CDATA

  • PBDOM_COMMENT

The PBDOM_CHARACTERDATA class, like its parent class PBDOM_OBJECT, is a "virtual" class (similar to a virtual C++ class) in that it is not expected to be directly instantiated and used. For example, creating a PBDOM_CHARACTERDATA with the CREATE statement is legal in PowerScript, but operating on it directly by calling its SetText method is not. The last line in this code raises an exception:

PBDOM_CHARACTERDATA pbdom_chrdata
pbdom_chrdata = CREATE PBDOM_CHARACTERDATA

pbdom_chrdata.SetText("character string") //exception!

In this example, pbdom_chrdata is declared as a PBDOM_CHARACTERDATA but is instantiated as a PBDOM_TEXT. Calling SetText on pbdom_chrdata is equivalent to calling the PBDOM_TEXT SetText method:

PBDOM_CHARACTERDATA pbdom_chrdata
pbdom_chrdata = CREATE PBDOM_TEXT

pbdom_chrdata.SetText("character string")

PBDOM_TEXT

The PBDOM_TEXT class derives from PBDOM_CHARACTERDATA and represents a DOM text node in an XML document.

Methods

The PBDOM_TEXT class has no methods that are not inherited from PBDOM_OBJECT or PBDOM_CHARACTERDATA.

Using PBDOM_TEXT objects

PBDOM_TEXT objects are commonly used to represent the textual content of a PBDOM_ELEMENT or a PBDOM_ATTRIBUTE. Although PBDOM_TEXT objects are not delimited by angle brackets, they are objects and do not form the value of a parent PBDOM_ELEMENT.

A PBDOM_TEXT object represented in graphical form in a PBDOM tree is a leaf node and contains no child objects. For example, the following figure represents the following PBDOM_ELEMENT:

<parent_element>some text</parent_element>

Figure: PBDOM_TEXT parent-child relationship

The arrow indicates a parent-child relationship.

Occurrence of PBDOM_TEXTs

When an XML document is first parsed, if there is no markup inside an element's content, the text within the element is represented as a single PBDOM_TEXT object. This PBDOM_TEXT object is the only child of the element. If there is markup, it is parsed into a list of PBDOM_ELEMENT objects and PBDOM_TEXT objects that form the list of children of the element.

For example, parsing the following XML produces one PBDOM_ELEMENT that represents <element_1> and one PBDOM_TEXT that represents the textual content Some Text:

<root>
  <element_1>Some Text</element_1>
</root>

The <element_1> PBDOM_ELEMENT has the PBDOM_TEXT object as its only child.

Consider this document:

<root>
  <element_1>
  Some Text
    <element_1_1>Sub Element Text</element_1_1>
    More Text
    <element_1_2/>
  Yet More Text
  </element_1>
</root>

Parsing this XML produces a PBDOM_ELEMENT that represents <element_1> and its five children:

  • A PBDOM_TEXT representing Some Text

  • A PBDOM_ELEMENT representing <element_1_1/>

  • A PBDOM_TEXT representing More Text

  • A PBDOM_ELEMENT representing <element_1_2/>

  • A PBDOM_TEXT representing Yet More Text

Adjacent PBDOM_TEXT objects

You can create adjacent PBDOM_TEXT objects that represent the contents of a given element without any intervening markup. For example, suppose you start with this document:

<root>
  <element_1>Some Text</element_1>
</root>

Calling AddContent("More Text") on the element_1 PBDOM_ELEMENT produces the following result:

<root>
  <element_1>Some TextMore Text</element_1>
</root>

There are now two PBDOM_TEXT objects representing "Some Text" and "More Text" that are adjacent to each other. There is nothing between them, and there is no way to represent the separation between them.

Persistence of PBDOM_TEXT objects

The separation of adjacent PBDOM_TEXT objects does not usually persist between DOM editing sessions. When the document produced by adding "More Text" shown in the preceding example is reopened and reparsed, only one PBDOM_TEXT object represents "Some TextMore Text".

PBDOM_CDATA

The PBDOM_CDATA class derives from PBDOM_TEXT and represents an XML DOM CDATA section.

Methods

The PBDOM_CDATA class has no methods that are not inherited from PBDOM_OBJECT or PBDOM_CHARACTERDATA.

Using CDATA objects

You can think of a PBDOM_CDATA object as an extended PBDOM_TEXT object. A PBDOM_CDATA object is used to hold text that can contain characters that are prohibited in XML, such as < and &. Their primary purpose is to allow you to include these special characters inside a large block of text without using entity references.

This example contains a PBDOM_CDATA object:

<some_text>
<![CDATA[ (x < y) & (y < z) => x < z ]]>
</some_text>

To express the same textual content as a PBDOM_TEXT object, you would need to write this:

<some_text>
(x &lt; y) &amp; (y &lt; z) =&gt; x &lt; z
</some_text>

Although the PBDOM_CDATA class is derived from PBDOM_TEXT, a PBDOM_CDATA object cannot always be inserted where a PBDOM_TEXT can be inserted. For example, a PBDOM_TEXT object can be added as a child of a PBDOM_ATTRIBUTE, but a PBDOM_CDATA object cannot.

PBDOM_COMMENT

The PBDOM_COMMENT class represents a DOM comment node within an XML document. The PBDOM_COMMENT class is derived from the PBDOM_CHARACTERDATA class.

Methods

The PBDOM_COMMENT class has no methods that are not inherited from PBDOM_OBJECT or PBDOM_CHARACTERDATA.

Using comments

Comments are useful for annotating parts of an XML document with user-readable information.

When a document is parsed, any comments found within the document persist in memory as part of the DOM tree. A PBDOM_COMMENT created at runtime also becomes part of the DOM tree.

An XML comment does not usually form part of the content model of a document. The presence or absence of comments has no effect on a document's validity, and there is no requirement that comments be declared in a DTD.

PBDOM_PROCESSINGINSTRUCTION

The PBDOM_PROCESSINGINSTRUCTION class represents an XML processing instruction (PI). The PBDOM_PROCESSINGINSTRUCTION methods allow access to the processing instruction target and its data. The data can be accessed as a string or, where appropriate, as name/value pairs.

The actual processing instruction of a PI is a string. This is so even if the instruction is cut up into separate name="value" pairs. PBDOM, however, does support such a PI format. If the PI data does contain these pairs, as is commonly the case, then PBDOM_PROCESSINGINSTRUCTION parses them into an internal list of name/value pairs.

Methods

In addition to the methods inherited from PBDOM_OBJECT, the PBDOM_PROCESSINGINSTRUCTION class has the following methods:

  • GetData and SetData to get and set the raw data of the PBDOM_PROCESSINGINSTRUCTION object

  • GetNames to get a list of names taken from the part of the PBDOM_PROCESSINGINSTRUCTION data that is separated into name="value" pairs

  • GetValue, RemoveValue, and SetValue to get, remove, and set the value of a specified name/value pair in the PBDOM_PROCESSINGINSTRUCTION object

  • GetTarget to get the target of a PBDOM_PROCESSINGINSTRUCTION. For example, the target of the XML declaration, which is a special processing instruction, is the string xml.

Adding pbdom.pbx to your application

The PBDOM classes are implemented in a DLL file with the suffix PBX (for PowerBuilder extension). The simplest way to add the PBDOM classes to a PowerBuilder target is to import the object descriptions in the pbdom.pbx PBX file into a library in the PowerBuilder System Tree. You can also the add pbdom.pbd file, which acts as a wrapper for the classes, to the target's library search path.

pbdom.pbx is installed to %AppeonInstallPath%\Common\PowerBuilder\Runtime [version]\ and pbdom.pbd is installed in %AppeonInstallPath%\PowerBuilder [version]\IDE\. When you are building a PBDOM application, you do not need to copy pbdom.pbx to another location, but you do need to deploy it with the application in a directory in the application's search path.

To import the descriptions in an extension into a library:

  1. In the System Tree, expand the target in which you want to use the extension, right-click a library, and select Import PB Extension from the pop-up menu.

  2. Navigate to the location of the PBX file and click Open.

    Each class in the PBX displays in the System Tree so that you can expand it, view its properties, events, and methods, and drag and drop to add them to your scripts.

After you import pbdom.pbx, the PBDOM objects display in the System Tree:

Using PBDOM

This section describes how to accomplish basic tasks using PBDOM classes and methods. To check for complete code samples that you can download and test, select Programs>Appeon>PowerBuilder 2019 R3>Code Samples from the Windows Start menu.

Validating the XML

Before you try to build a document from a file or string, you can test whether the XML is well formed or, optionally, whether it conforms to a DTD or Schema using the XMLParseFile or XMLParseString PowerScript functions. For example, this code tests whether the XML in a file is well formed:

long ll_ret
ll_ret = XMLParseFile("c:\temp\mydoc.xml", ValNever!)

By default, these functions display a message box if errors occur. You can also provide a parsingerrors string argument to handle them yourself. For more information about these functions, see the section called “XMLParseFile” in PowerScript Reference and the section called “XMLParseString” in PowerScript Reference.

Creating an XML document from XML

The PBDOM_BUILDER class provides three methods for creating a PBDOM_DOCUMENT from an existing XML source. It also provides the GetParseErrors method to get a list of any parsing errors that occur.

Using BuildFromString

The following example uses an XML string and the PBDOM_BUILDER class to create a PBDOM_DOCUMENT. First the objects are declared:

PBDOM_BUILDER pbdom_builder_new
PBDOM_DOCUMENT pbdom_doc

The objects are then instantiated using the constructor and the PBDOM_BUILDER BuildFromString method:

pbdombuilder_new = Create PBDOM_Builder
pbdom_doc = pbdombuilder_new.BuildFromString(Xml_doc)

XML can also be loaded directly into a string variable, as in the following example:

string Xml_str
Xml_str = "<?xml version="1.0" ?>"
Xml_str += "<WHITEPAPER>"
Xml_str += "<TITLE>Document Title</TITLE>"
Xml_str += "<AUTHOR>Author Name</AUTHOR>"
Xml_str += "<PARAGRAPH>Document text.</PARAGRAPH>"
Xml_str += "</WHITEPAPER>"

Using BuildFromFile

You can create an XML file using the BuildFromFile method and a string containing the path to a file from which to create a PBDOM_DOCUMENT:

PBDOM_BUILDER     pbdombuilder_new
PBDOM_DOCUMENT     pbdom_doc
pbdombuilder_new = Create PBDOM_Builder
pbdom_doc = pbdombuilder_new.BuildFromFile &
   ("c:\pbdom_doc_1.xml")

Using BuildFromDataStore

The following PowerScript code fragment demonstrates how to use the BuildFromDataStore method with a referenced DataStore object.

PBDOM_Builder pbdom_bldr
pbdom_document pbdom_doc
datastore ds

ds = Create datastore
ds.DataObject = "d_customer"
ds.SetTransObject (SQLCA)
ds.Retrieve

pbdom_doc = pbdom_bldr.BuildFromDataStore(ds)

Using GetParseErrors

After a call to any of the Build methods, you can obtain a list of parsing and validating errors encountered by the Build methods with the GetParseErrors method:

PBDOM_Builder pbdom_bldr
pbdom_document pbdom_doc
string strParseErrors[]
BOOLEAN bRetTemp = FALSE

pbdom_buildr = Create PBDOM_BUILDER
pbdom_doc = pbdom_buildr.BuildFromFile("D:\temp.xml")
bRetTemp = pbdom_buildr.GetParseErrors(strParseErrors)
if bRetTemp = true then
   for l = 1 to UpperBound(strParseErrors)
      MessageBox ("Parse Error", strParseErrors[l])
   next
end if

Parsing errors

If parsing errors are found and GetParseErrors returns true, a complete PBDOM node tree that can be inspected might still be created.

Creating an XML document from scratch

You can create an XML document in a script using the appropriate PBDOM_OBJECT subclasses and methods. The following code uses the PBDOM_ELEMENT and PBDOM_DOCUMENT classes and some of their methods to create a simple XML document.

First, the objects are declared and instantiated:

PBDOM_ELEMENT pbdom_elem_1
PBDOM_ELEMENT pbdom_elem_2
PBDOM_ELEMENT pbdom_elem_3
PBDOM_ELEMENT pbdom_elem_root
PBDOM_DOCUMENT pbdom_doc1

pbdom_elem_1 = Create PBDOM_ELEMENT
pbdom_elem_2 = Create PBDOM_ELEMENT
pbdom_elem_3 = Create PBDOM_ELEMENT

The instantiated objects are assigned names. Note that the PBDOM_DOCUMENT object pbdom_doc1 is not named:

pbdom_elem_1.SetName("pbdom_elem_1")
pbdom_elem_2.SetName("pbdom_elem_2")
pbdom_elem_3.SetName("pbdom_elem_3")

The objects are arranged into a node tree using the AddContent method. The AddContent method adds the referenced object as a child node under the object from which AddContent is invoked:

pbdom_elem_1.AddContent(pbdom_elem_2)
pbdom_elem_2.AddContent(pbdom_elem_3)

Use the NewDocument method to create a new XML document. The parameter value supplied to the NewDocument method becomes the name of the root element. This name is then accessed from the PBDOM_DOCUMENT object pbdom_doc1 and assigned to the PBDOM_ELEMENT object pbdom_elem_root using the GetRootElement method:

pbdom_doc1.NewDocument("Root_Element_From_Doc_1")
pbdom_elem_root = pbdom_doc1.GetRootElement()

The ELEMENT object pbdom_elem_1 and all its child nodes are placed in the new XML document node tree under the root element using the AddContent method. Note that as the ancestor node pbdom_elem_1 is placed in the node tree, all its child nodes move as well:

pbdom_elem_root.AddContent(pbdom_elem_1)

The XML document created looks like this:

<!DOCTYPE Root_Element_From_Doc_1> 
<Root_Element_From_Doc_1>
    <pbdom_elem_1>
        <pbdom_elem_2>
            <pbdom_elem_3/> 
        </pbdom_elem_2>
    </pbdom_elem_1>
</Root_Element_From_Doc_1>

Accessing node data

An XML document can be read by accessing the elements of its node tree using the appropriate PBDOM_OBJECT subclasses and methods. The following code uses an array, the PBDOM_OBJECT, and its descendant class PBDOM_DOCUMENT, and the GetContent and GetRootElement methods of the PBDOM_DOCUMENT class to access node data on an XML document.

A PBDOM_DOCUMENT object named pbdom_doc contains the following XML document:

<Root>
    <Element_1>
        <Element_1_1/>
        <Element_1_2/>
        <Element_1_3/>
    </Element_1>
    <Element_2/>
    <Element_3/>
</Root>

The following code declares an array to hold the elements returned from the GetContent method, which reads the PBDOM_DOCUMENT object named pbdom_doc:

PBDOM_OBJECT pbdom_obj_array[]
...
pbdom_doc.GetContent(ref pbdom_obj_array)

The pbdom_obj_array array now contains one value representing the root element of pbdom_doc: <Root>.

To access the other nodes in pbdom_doc, the GetRootElement method is used with the GetContent method.

pbdom_doc.GetRootElement().GetContent &
   (ref pbdom_obj_array)

The pbdom_obj_array array now contains three values corresponding to the three child nodes of the root element of pbdom_doc: <Element_1>, <Element_2>, and <Element_3>.

PBDOM provides other methods for accessing data, including InsertContent, AddContent, RemoveContent, and SetContent.

Changing node content with arrays

You can use the AddContent method to change node content:

pbdom_obj_array[3].AddContent("This is Element 3.")

This line of code changes the node tree as follows:

<Root>
    <Element_1>
        <Element_1_1/>
        <Element_1_2/>
        <Element_1_3/>
    </Element_1>
    <Element_2/>
    <Element_3>This is Element 3.</Element_3>
</Root>

Arrays and object references

When you use a method such as the GetContent method of the PBDOM_DOCUMENT class to return an array of PBDOM_OBJECT references, the references are to instantiated PBDOM objects. If you modify any of these objects through its array item, the changes are permanent and are reflected in any other arrays that hold the same object reference.

Manipulating the node-tree hierarchy

You can restructure an XML node tree by rearranging its nodes. One means of manipulating nodes involves detaching a child node from its parent node. This can be accomplished with the Detach method, as in the following example.

The root element of a PBDOM_DOCUMENT object named pbdom_doc is obtained using the GetRootElement method:

pbdom_obj = pbdom_doc.GetRootElement()

The root element is detached from the PBDOM_DOCUMENT object, which is the parent node of the root element:

pbdom_obj.Detach()

PBDOM provides the SetParentObject method to make an object a child of another object.

Checking for parent node

The GetParentObject method can be used to determine whether an element has a parent object, as in the following example:

pbdom_parent_obj = pbdom_obj.GetParentObject()
if not IsValid(pbdom_parent_obj) then
   MessageBox ("Invalid", "Root Element has no Parent")
end if

If the object on which GetParentObject is called has no parent object, the function returns NULL.

PBDOM provides similar methods that return information about an element's place in an XML node tree. These methods include HasChildren, which returns a boolean indicating whether an object has child objects, and IsAncestorObjectOf, which indicates whether an object is the ancestor of another object.

Handling PBDOM exceptions

PBDOM defines an exception class, PBDOM_EXCEPTION, derived from the standard PowerBuilder Exception class. The standard Text property of the Exception class can be used to obtain more detail on the nature of the exception being thrown. The class extends the PowerBuilder Exception class with one method, GetExceptionCode, that returns the unique code that identifies the exception being thrown.

For a list of exception codes, see the section called “PBDOM exceptions” in PowerBuilder Extension Reference or the section called “Handling PBDOM exceptions”.

PBDOM is a PowerBuilder extension, built using PBNI. The extension itself might throw a PBXRuntimeError exception. In the following example, the try-catch block checks first for a PBDOM exception, then for a PBXRuntimeError.

The example builds a PBDOM_DOCUMENT from a passed-in file name and uses a user-defined function called ProcessData to handle the DOM nodes. ProcessData could be a recursive function that extracts information from the DOM elements for further processing:

Long ll_ret

ll_ret = XMLParseFile(filename, ValNever!)
if ll_ret < 0 then return

PBDOM_Builder   domBuilder

TRY
   domBuilder = CREATE PBDOM_Builder
   PBDOM_Document domDoc
   PBDOM_Element   root
   domDoc = domBuilder.BuildFromFile( filename )
   IF IsValid( domDoc ) THEN
      IF domDoc.HasChildren() THEN
         PBDOM_Object data[]
         IF domDoc.GetContent( data ) THEN
            Long   ll_index, ll_count
            ll_count = UpperBound( data )
            FOR ll_index = 1 TO ll_count
               ProcessData( data[ll_index], 0 )
            NEXT
         END IF
      END IF
   END IF

CATCH ( PBDOM_Exception pbde )
   MessageBox( "PBDOM Exception", pbde.getMessage() )
CATCH ( PBXRuntimeError re )
   MessageBox( "PBNI Exception", re.getMessage() )
END TRY

XML namespaces

XML namespaces provide a way to create globally unique names to distinguish between elements and attributes with the same name but of different terminologies. For example, in an XML invoice document for a bookstore, the name "date" could be used by accounting for the date of the order and by order fulfillment for the date of publication.

An XML namespace is identified by a Uniform Resource Identifier (URI), a short string that uniquely identifies resources on the Web. The elements and attributes in each namespace can be uniquely identified by prefixing the element or attribute name (the local name) with the URI of the namespace.

Associating a prefix with a namespace

You declare an XML namespace using xmlns as part of a namespace declaration attribute. With the namespace declaration attribute, you can associate a prefix with the namespace.

For example, the following namespace declaration attribute declares the http://www.pre.com namespace and associates the prefix pre with this namespace:

xmlns:pre="http://www.pre.com"

Default XML namespace

If an XML namespace declaration does not specify a prefix, the namespace becomes a default XML namespace. For example, the following element, digicom, declares the namespace http://www.digital_software.com:

<digicom xmlns="http://www.digital_software.com" />

The namespace http://www.digital_software.com is the in-scope default namespace for the element digicom and any child elements that digicom might contain. The child elements of digicom will automatically be in this namespace.

The NONAMESPACE declaration

The following namespace declaration is known as the NONAMESPACE declaration:

xmlns=""

The containing element and its child elements are declared to be in no namespace. An element that is in the NONAMESPACE namespace has its namespace prefix and URI set to empty strings.

Initial state

When a PBDOM_ELEMENT or a PBDOM_ATTRIBUTE is first created, it has no name, and the namespace information is by default set to the NONAMESPACE namespace (that is, its namespace prefix and URI are both empty strings). The SetName method is used to set the local name and the SetNamespace method is used to set the namespace prefix and URI.

The name is required

The name is a required property of a PBDOM_ELEMENT and PBDOM_ATTRIBUTE, but the namespace information is not.

Retrieving from a parsed document

If a PBDOM_ELEMENT or PBDOM_ATTRIBUTE is retrieved programmatically from a parsed document, then its name and namespace information are inherited from the Element or Attribute contained in the parsed document. However, even after parsing, the name and namespace information of the PBDOM_ELEMENT and PBDOM_ATTRIBUTE can be further modified with the SetName and SetNamespace methods.

The name and namespace information are stored separately internally. Changing the name of a PBDOM_ELEMENT or PBDOM_ATTRIBUTE does not affect its namespace information, and changing its namespace information has no effect on its name.

Setting the name and namespace of a PBDOM_ATTRIBUTE

The W3C "Namespaces in XML" specification (in section 5.3) places restrictions on setting the name and namespace of a PBDOM_ATTRIBUTE. No tag can contain two attributes with identical names, or with qualified names that have the same local name and have prefixes that are bound to identical namespace names.

The specification provides the following examples of illegal and legal attributes:

<!-- http://www.w3.org is bound to n1 and n2 -->
<x xmlns:n1="http://www.w3.org" 
   xmlns:n2="http://www.w3.org" >
   <bad a="1"  a="2" />
   <bad n1:a="1"  n2:a="2" />
</x> 
<!-- http://www.w3.org is bound to n1 and is the default -->
<x xmlns:n1="http://www.w3.org" 
   xmlns="http://www.w3.org" >
   <good a="1"  b="2" />
   <good a="1"  n1:a="2" />
</x> 

In the first example, <bad a="1" a="2" /> violates the rule that no tag can contain two attributes with identical names. In the second tag, the attributes have the same local name but different prefixes, so that their names are different. However, their prefixes point to the same namespace URI, http://www.w3.org, so it is illegal to place them inside the same owner element.

PBDOM scenarios

The following scenarios illustrate how PBDOM conforms to these requirements.

  • When the PBDOM_ATTRIBUTE SetName method is invoked:

    If the PBDOM_ATTRIBUTE pbdom_attr1 has an owner PBDOM_ELEMENT that contains an existing PBDOM_ATTRIBUTE with the same name that is to be set for pbdom_attr1 and has the same namespace URI as pbdom_attr1, the EXCEPTION_INVALID_NAME exception is thrown.

  • When the PBDOM_ATTRIBUTE SetNamespace method is invoked:

    If the PBDOM_ATTRIBUTE pbdom_attr1 has an owner PBDOM_ELEMENT that contains an existing PBDOM_ATTRIBUTE with the same name as pbdom_attr1 and the same namespace URI that is to be set for pbdom_attr1, the EXCEPTION_INVALID_NAME exception is thrown.

  • When the PBDOM_ELEMENT SetAttribute(pbdom_attribute pbdom_attribute_ref) method is invoked:

    If the PBDOM_ELEMENT already contains an attribute that has the same name and namespace URI as the input PBDOM_ATTRIBUTE, the existing attribute is replaced by the input PBDOM_ATTRIBUTE. The existing attribute is thus removed (detached) from the owner element.

  • When the PBDOM_ELEMENT SetAttributes(pbdom_attribute pbdom_attribute_array[]) method is invoked:

    If any two PBDOM_ATTRIBUTE objects in the array have the same name and namespace URI, the EXCEPTION_INVALID_NAME exception is thrown. If there is no name or namespace conflict within the array, all the existing attributes of the PBDOM_ELEMENT are replaced by the PBDOM_ATTRIBUTE objects in the array.

    Note

    All the above scenarios apply to PBDOM_ATTRIBUTE objects that are contained in the NONAMESPACE namespace.

  • When the PBDOM_ELEMENT SetAttribute(string strName, string strValue) method is invoked:

    A new PBDOM_ATTRIBUTE with the specified name and value is created and set into the PBDOM_ELEMENT. If the PBDOM_ELEMENT already contains an attribute that has the same name and that is contained within the NONAMESPACE namespace, it is removed (detached) from the PBDOM_ELEMENT.

  • When the PBDOM_ELEMENT SetAttribute(string strName, string strValue, string strNamespacePrefix, string strNamespaceUri, boolean bVerifyNamespace) method is invoked:

    A new PBDOM_ATTRIBUTE with the specified name, value, and namespace information is created and set into the PBDOM_ELEMENT. If the PBDOM_ELEMENT already contains a PBDOM_ATTRIBUTE that has the same name and namespace URI as the input namespace URI, it is removed (detached) from the PBDOM_ELEMENT.

Example

The following example demonstrates the impact of setting a PBDOM_ATTRIBUTE for a PBDOM_ELEMENT where the PBDOM_ELEMENT already contains an attribute of the same name and namespace URI as the input PBDOM_ATTRIBUTE.

The example creates a PBDOM_DOCUMENT based on the following document:

<root xmlns:pre1="http://www.pre.com" xmlns:pre2="http://www.pre.com">
   <child1 pre1:a="123"/>
</root>

Then it creates a PBDOM_ATTRIBUTE object and set its name to a and its prefix and URI to pre2 and http://www.pre.com. The bVerifyNamespace argument is set to FALSE because this PBDOM_ATTRIBUTE has not been assigned an owner PBDOM_ELEMENT yet, so that the verification for a predeclared namespace would fail. The text value is set to 456.

The child1 element already contains an attribute named a that belongs to the namespace http://www.pre.com, as indicated by the prefix pre1. The new PBDOM_ATTRIBUTE uses the prefix pre2, but it represents the same namespace URI, so setting the new PBDOM_ATTRIBUTE to child1 successfully replaces the existing pre1:a with the new PBDOM_ATTRIBUTE pre2:a.

PBDOM_BUILDER pbdom_buildr
PBDOM_DOCUMENT pbdom_doc
PBDOM_ATTRIBUTE pbdom_attr

string strXML = "<root xmlns:pre1=~"http://www.pre.com~" xmlns:pre2=~"http://www.pre.com~"><child1 pre1:a=~"123~"/></root>"



try

  pbdom_buildr = Create PBDOM_BUILDER
  pbdom_doc = pbdom_buildr.BuildFromString (strXML)
  
  // Create a PBDOM_ATTRIBUTE and set its properties
  pbdom_attr = Create PBDOM_ATTRIBUTE
  pbdom_attr.SetName ("a")
  pbdom_attr.SetNamespace ("pre2", &
     "http://www.pre.com", false)
  pbdom_attr.SetText("456")
  
  // Attempt to obtain the child1 element and 
  // set the new attribute to it
  pbdom_doc.GetRootElement(). &
    GetChildElement("child1").SetAttribute(pbdom_attr)
  
  pbdom_doc.SaveDocument &
     ("pbdom_elem_set_attribute_1.xml")

catch (PBDOM_EXCEPTION except)
  MessageBox ("PBDOM_EXCEPTION", except.GetMessage())
end try

The XML output from SaveDocument looks like the following:

<root xmlns:pre1="http://www.pre.com" xmlns:pre2="http://www.pre.com">
   <child1 pre2:a="456"/>
</root>