Skip to content

XML in Dyalog APL Version 9

Vector 17:4, page 48

data and files system interfaces

Transcribed from the printed issue; not yet reviewed. Read it in the PDF of the issue, page 48

Introduction

There has been a tremendous amount of hype about XML being the "the ASCII of the future". Initially I was suspicious as all seemed rather pointless. Having seen how and where XML is used the penny has finally dropped. Clearly separating the data, using XML, from the display, using HTML, seems to be a very sensible idea. Another of the ways in which XML is used is to transfer of data between completely independent programs and machines. The transfer is normally on the Web but can be between any machines networked together. Having a clear, self-documenting, medium when one is trying to write a program to pick up data from another source if obviously a great help. I was interested in the Web site www.xmlrpc.com which contains a specification for using XML for remote procedure calls and which appears to be implemented on several platforms.

This article is about the way in which XML can be manipulated using Dyalog APL version 9 with the new-style object syntax.

XML Basics

XML is a simple way of structuring data. It looks very like HTML but the tag names are whatever you need to define the data. The general idea is to make it self documenting. Tag names should describe the data so that anyone looking at the XML can easily see what is going on.

XML is fussier than HTML in that all tags must be closed and the nesting must be correct. If you are familiar with HTML then XML is easy to create. For example, an APL array could be defined as follows:

<array>
  <rank>
    <dimension>2</dimension>
    <dimension>3</dimension>
  </rank>
  <data>
    <number length="3">0 1 2</number>
    <array>
      <rank>
        <dimension>2</dimension>
      </rank>
      <data>
        <char length="2">AB</char>
      </data>
    </array>
    <number length="2">4 5</number>
  </data>
</array>

The <array> element has two elements <rank> and <data>. The <rank> element contains <dimension> elements which specify the shape of the array. The <data> element contains elements which specify the data type of the value. A data type can be <number>, <char> or an <array> for a nested array. The <number> and <char> elements have an attribute length specifying the length of the data each contains.

Whilst this simple scheme shows how easy it is to define data structures in XML it is not adequate to fully describe an APL array. For example, the character data element may need to cater for any character out of ⎕AV. Also it might be necessary to precisely define empty arrays with full prototype information.

If you have never seen XML before the tutorial site www.xml101.com contains some good examples and clear explanations.

XML Document Object Model

Microsoft distribute an XML parser with Windows, MSXML.DLL. This has been upgraded for Windows 2000 to version 2.6 which is in MSXML2.DLL If you have installed .NET there is yet a further upgrade to MSXML3.DLL. I have used MSXML version 2.6 for all the examples because I am running under Windows 2000 but, hopefully, I have not used any of the new features.

The base object of the parser is the Document Object Model (DOM). The main thing that this contains is the Root Element of the XML. The Root Element contains collections of Nodes. A Node within the collection may be one of several types which include Attributes and Elements.

Dyalog APL and MSXML

The latest version of Dyalog APL, 9.0, includes a major improvement in the way COM objects such as MSXML can be accessed. In order to show off the power of the new version code will be developed in both 8.2 and 9.0 so that a direct comparison can be made between the two version.

Both versions access the basic DOM object in the same way:

      'xDOM'⎕WC 'OLEClient' 'MSXML2.DOMDocument.2.6'

The above is only valid if you are running under Windows 2000. Otherwise the second argument would be 'MSXML.DOMDocument'. I find that it is always best to check the exact spelling by looking at the array produced by

      a←⊃¨'.'⎕WG'OLEServers'

There are two methods for loading XML into the DOM; load which loads from a file and loadXML which loads a character vector. Again, both 9.0 and 8.2 have the same syntax:

      xDOM.load 'Example1.xml'
1

The 1 returned indicates that the XML has loaded without error. If an error occurs then the error information is held in the parseError property. In this case versions 9.0 and 8.2 are different:

Dyalog 8.2

      xDOM.loadXML xmlbad
0
      'xPer'⎕WC xDOM.parseError
      xPer.line,xPer.reason,xPer.srcText
7 The character '<' cannot be used in an attribute value.

    <number length="3>0 1 2</number>

Dyalog 9.0

      xDOM.loadXML xmlbad
0
      xPer←xDOM.parseError
      xPer.line,xPer.reason,xPer.srcText
7 The character '<' cannot be used in an attribute value.

    <number length="3>0 1 2</number>

In Dyalog 9.0 you can assign objects to variables, so you don’t have to use ⎕WC. This facility is fully extended throughout the language so that :with arguments can also be an object rather than a character vector giving a namespace path. It also allows the dot syntax that is standard in other programming languages, so it is possible to access the reason for the error with xDOM.parseError.reason.

The following functions illustrate the way in which in which XML might be loaded and the return code checked.

Dyalog 8.2

    ∇ r←LoadXML xml;xP
[1]   :If ~r←xDOM.loadXML xml
[2]       :With 'xP'⎕WC xDOM.parseError
[3]           #.MsgBox'XML Error'((⍕line),' ',reason,srcText)
[4]       :EndWith
[5]   :EndIf
    ∇

Dyalog 9.0

    ∇ r←xd LoadXML xml
[1]   :If ~r←xd.loadXML xml
[2]       :With xd.parseError
[3]           #.MsgBox'XML Error'((⍕line),' ',reason,srcText)
[4]       :EndWith
[5]   :EndIf
    ∇

In version 8.2 you have to refer to a global DOM object whereas in version 9.0 it can be passed into the function. Also, in version 9.0 the :with creates an anonymous namespace from the object.

Convert XML to an Array in Dyalog 9.0

Converting the example XML into an APL array is relatively straightforward in Dyalog 9.0. With help from the Editor, who made some very clever suggestions, I created a single function with all sub-functions defined using direct definition.

It needs to be emphasised that this function will only work using Version 9.0 release 2 which allows arrays of references. The version I used was dated 7 March 2001.

    ∇ r←XMLToArray xml;Array;Length;Load;Shape;Value;xDOM;⎕IO;⎕ML
[1]   ⎕IO←0 ⋄ ⎕ML←1
[2]   ⍝ Convert xml into an APL array.
[3]   ⍝ Returns 2 element vector (0 array) or (n errmessage)
[4]   Load←{     ⍝ Load xml (⍵) into DOM (⍺). Return 0 for OK
[5]       r←⍺.loadXML ⍵ ⍝ loadXML returns 1 for OK
[6]       0=r:1,⊂⍺.parseError.('XML Error on line ',(⍕line),'. ',reason,srcText)
[7]       'array'≢⍺.documentElement.nodeName:2,⊂'XML does not
            contain an <array> root element'
[8]       0}
[9]   Array←{     ⍝ Get array from root node ⍵
[10]      s←Shape ⍵
[11]      0≡vl←⍵.selectNodes'data/number|data/char|data/array' :⍬
[12]      0=vl.length:⍬
[13]      s⍴⊃Value/vl.(item¨⌽⍳length),⊂⍬}
[14]  Shape←{     ⍝ Get shape from root node ⍵
[15]      0≡dm←⍵.selectNodes'rank/dimension':⍬
[16]      0=dm.length:⍬
[17]      dm.(item¨⍳length).(⊃⊃(//)⎕VFI text)}
[18]  Value←{     ⍝ Value node ⍺ catenated to vector in ⍵
[19]      s←Length ⍺
[20]      'number'≡⍺.nodeName:⍵,s↑∊(//)⎕VFI ⍺.text
[21]      'char'≡⍺.nodeName:⍵,s↑⍺.text
[22]      'array'≡⍺.nodeName:⍵,⊂Array ⍺
[23]      ⍵}
[24]  Length←{     ⍝ Length attributed in value node ⍵
[25]      0≡lg←⍵.selectSingleNode'@length':0
[26]      ⊃0,⍨(//)⎕VFI lg.text}
[27]  'xDOM'⎕WC'OLEClient' 'MSXML2.DOMDocument.2.6'
[28]  :If 0≠⊃r←xDOM Load xml ⋄ :Return ⋄ :EndIf
[29]  r←0,⊂Array xDOM.documentElement
    ∇

The Load function, defined on line [4] and called on line [28], is much the same as defined above but I wanted a zero return code to indicate success. It checks that the XML has loaded correctly and also checks that the root node has the correct name. Line [6] shows off the way in which the new namespace syntax can now include arbitrarily complex statements enclosed in parentheses between the dots.

The DOM object actually contains a collection of child nodes, which will be explained later on. The documentElement property (referenced on line [7] and [29]) contains the root node. There can only be one of these otherwise the XML would not have loaded correctly. Array is therefore called with the root object as its right argument.

The Shape function, called on line [10] and defined on line [14], uses the selectNodes method on line [15] to get the dimension nodes. where the argument 'rank/dimension' is an XPath expression which specifies the path to the required nodes. If something goes wrong then the method returns NULL so the variable dm is tested to see if it contains a scalar zero. dm is not an array, it is a Selection Object containing the nodes found. Line [16] checks the length property to see if there are any dimensions and then line [17] uses yet more complex expressions to convert the text property of each node into numbers. Before the Editor pointed out this new feature I was much mystified why statements such as xRoot.childNodes.(item i).nodeName gave a VALUE ERROR. I correctly guessed that the variable i was being masked on the way down the object hierarchy, but did not realise that this was deliberate or how useful it could be.

Back to the Array function on line [11] the nodes containing data are selected with a set of paths separated by an OR (|) symbol. The XPath documentation in MSDN is listed under XSLT which I did not find until I had virtually finished this article; it is well worth careful study if you are going to manipulate XML. Line [13] is thanks to John Scholes where the reduction operator is used to place the Value function between each pair of nodes to catenate all the results together. Because reduction operates from right to left, but we want to work from left to right, the indices are reversed.

The Value function is therefore dyadic taking a node as its left argument and the data to be catenated as its right argument. The Length function selects the length attribute by putting an AT (@) sign in front of the name. Lines [20] to [22] convert the data and catenate it to the right argument. Line [23] is not really necessary as the function can only be called with nodes of the correct names.

Running XMLToArray produces the following results.

      ⎕SE.DISPLAY XMLToArray xml
.→---------------.
|   .→---------. |
| 0 ↓          | |
|   | 0     1 2| |
|   |          | |
|   | .→-.     | |
|   | |AB| 4 5 | |
|   | '--'     | |
|   '∊---------' |
'∊---------------'

      ⎕SE.DISPLAY 2⊃XMLToArray '<array></array>'
.⍬.
|0|
'~'
      x←'<array><data><number length="1">23.1'
      x,←'</number></data></array>'
      ⎕SE.DISPLAY 2⊃XMLToArray x
23.1

      x←'<array><rank><dimension>3</dimension></rank>'
      x,←'<data><array></array></data></array>'
      ⎕SE.DISPLAY 2⊃XMLToArray x
.→-----------.
| .⍬. .⍬. .⍬. |
| |0| |0| |0| |
| '~' '~' '~' |
'∊------------'

Whilst developing this code I came across another change in Version 9.0 that caused me some puzzlement. )FNS and )VARS no longer report the COM object’s methods and properties as they do in Version 8.2. Stopping the code on line [17], for example, to check what is going on gives the following:

      )cs dm
#.xDOM.[IXMLDOMNode].[IXMLDOMSelection]
      )FNS
      )VARS
      )METHODS
clone   getProperty     matches nextNode        peekNode
removeAll       removeNext
reset   setProperty
      )PROPS
context expr    item    length  AutoBrowse      ChildList
ClassID ClassName
Data    Event   EventList       Handle  HelpFile        Instance
Mode    KeepOnClose
LastError       Locale  MethodList      PropList        QueueEve
nts     Type    TypeList
      NODE_DOCUMENT
9
      NODE_DOCUMENT←32
SYNTAX ERROR
      NODE_DOCUMENT←32
     ∧
      ⎕NC'NODE_DOCUMENT'
0

As can be seen the enumeration variables such as NODE_DOCUMENT are not listed but are available and can’t be assigned. The values can be displayed in APL Explorer where one can find the name of the property that contains them.

Convert XML to Array in Dyalog 8.2

It is more difficult to organise the recursion in version 8.2 as a character argument of the namespace name has to be passed around and the recursion has to rely on the namespaces created by ⎕WC. Also, because all the namespaces have to be explicitly created it is not so easy to use direct definition for all the sub-functions as in version 9.0, and everything is much more verbose.

    ∇ r←XMLToArray xml;xDOM;ns;⎕IO;⎕ML
[1]   ⎕IO←0 ⋄ ⎕ML←1
[2]   ns←'xDOM'⎕WC'OLEClient' 'MSXML2.DOMDocument.2.6'
[3]   :If 0≠ns Load xml ⋄ :Return ⋄ :EndIf
[4]   :With ns
[5]       :With ns←'xRoot'⎕WC documentElement
[6]           :If 'array'≡nodeName
[7]               r←0,⊂#.GetArray ns
[8]           :EndIf
[9]       :EndWith
[10]  :EndWith
    ∇

The Load function does not check the name of the root array as a root namespace would have to be created twice. Otherwise it is the same as Version 9.0 with the addition of an explicit namespace to generate the error message.

    ∇ r←ns Load xml;xP;⎕IO;⎕ML
[1]   ⎕IO←0 ⋄ ⎕ML←1
[2]   :With ns
[3]       :If r←~loadXML xml
[4]           :With 'xP'⎕WC xDOM.parseError
[5]               r←1,⊂'XML Error ',(⍕line),' ',reason,srcText
[6]               :Return
[7]           :EndWith
[8]       :EndIf
[9]   :EndWith
    ∇

The GetArray function first gets the required shape and then gets the data. The processing is really exactly the same as in Version 9.0, but namespaces have to be created and explicit loops have to be coded.

    ∇ r←GetArray ns;i;len;shape;⎕IO;⎕ML
[1]   ⎕IO←0 ⋄ ⎕ML←1
[2]   r←⍬
[3]   shape←GetShape ns
[4]   :With ns
[5]       'vls'selectNodes'data/number|data/char|data/array'
[6]       :If 9≠⎕NC'vls' ⋄ :Return ⋄ :EndIf
[7]       :With 'vls'
[8]           :For i :In ⍳length
[9]               'vl'item i
[10]              :With 'vl'
[11]                  len←0
[12]                  'lg'selectSingleNode'@length'
[13]                  :If 9=⎕NC'lg'
[14]                      len←⊃∊0,⍨(//)⎕VFI lg.text
[15]                  :EndIf
[16]                  :Select nodeName
[17]                  :Case 'number'
[18]                      r,←len↑∊(//)⎕VFI text
[19]                  :Case 'char'
[20]                      r,←len↑text
[21]                  :Case 'array'
[22]                      r,←⊂#.GetArray''⎕NS''
[23]                  :EndSelect
[24]              :EndWith
[25]          :EndFor
[26]      :EndWith
[27]  :EndWith
[28]  r←shape⍴r
    ∇

To check if the select methods return a NULL I have used ⎕NC on lines [6] and [13] to test if an object was created.

The GetShape function contains very similar code.

    ∇ r←GetShape ns;i;⎕IO;⎕ML
[1]   ⎕IO←0 ⋄ ⎕ML←1
[2]   r←⍬
[3]   :With ns
[4]       'dms'selectNodes'rank/dimension'
[5]       :If 9≠⎕NC'dms' ⋄ :Return ⋄ :EndIf
[6]       :With 'dms'
[7]           :For i :In ⍳length
[8]               'dm'item i
[9]               r,←∊(//)⎕VFI dm.text
[10]          :EndFor
[11]      :EndWith
[12]  :EndWith
    ∇

Running the function produces the same result as in version 9.0.

      ⎕SE.DISPLAY XMLToArray xml
.→---------------.
|   .→---------. |
| 0 ↓          | |
|   | 0     1 2| |
|   |          | |
|   | .→-.     | |
|   | |AB| 4 5 | |
|   | '--'     | |
|   '∊---------' |
'∊---------------'

Processing Instruction and Document Type Definition (DTD)

In order to keep things simple I have left out the Processing Instruction and DTD in the example XML above. Ideally this should have had the following inserted before the first <array> tag:

<?xml version="1.0" ?>

<!DOCTYPE array [
<!ELEMENT array (rank?, data?)>
<!ELEMENT rank (dimension*)>
<!ELEMENT dimension (#PCDATA)>
<!ELEMENT data (number|char|array)*>
<!ELEMENT number (#PCDATA)>
<!ELEMENT char (#PCDATA)>
<!ATTLIST number length CDATA "1">
<!ATTLIST char length CDATA "1">
<!ATTLIST char xml:space (default|preserve) "preserve">
]>

The first line is the Processing Instruction which merely states that this section is XML version 1.0. The remaining lines are the DTD. These two items appear as children of the DOM object, so that is why the documentElement property was accessed in the above code to get at the root node, rather than looping through all the child nodes.

The name given as the argument to !DOCTYPE must be the name of the root node. Tag names in XML are case sensitive so given this DTD it is not necessary to ensure all the names are in the correct case.

The expression <!ELEMENT array (rank?, data?)> states that an <array> element can only contain <rank> and <data> child elements, in that sequence and no other data. The ? following the names means that there may be zero or one occurrences of each.

A child entity name may be followed by nothing, which means that it must occur exactly once, a query (?) if it is to occur zero or once only, a plus (+) if there must be one or more occurrences, or a star (*) if it can occur zero or more times. Putting the decoration after the enclosing parentheses means that it applies to all of them.

The declaration dimension (#PCDATA) means that there may be data between the start and end dimension tags.

Specifying child entities separated with vertical bars (|) means that any may occur in any sequence. The data element has been defined in this way.

Attributes are defined with !ATTLIST element-name attribute-name attribute-type default-value. The attribute types and attribute defaults may be as follows:

Attribute Type Meaning
CDATA Character data
(value|value|..) One of the given values must be included
ID An unique Id
IDREF The Id of another element
IDREFS A list of other Ids
NMTOKEN A valid XML name
NMTOKENS A list of valid XML names
ENTITY An entity
ENTITIES A list of entities
NOTATION A name of a notation
Xml: Predefined
Attribute Default Meaning
#DEFAULT value If the attribute is not present then the value is assumed
#REQUIRED An attribute value must be included
#IMPLIED The attribute is optional, but no default is available
#FIXED value The attribute always has the same value regardless of the value specified in the XML

The example DTD has defined the default attribute values in double quotes rather than using #DEFAULT. For example, the number attribute could have been defined as !ATTLIST number length CDATA #DEFAULT 1.

It is a good idea to include a DTD because otherwise you have to carry out much more checking in the code. For example, without a DTD it is permissible to include data between the array tags.

      xDOM.loadXML'<array>data</array>'
1

But, with a DTD the parser recognises that this is not permitted.

      xDOM.loadXML dtd,'<array>data</array>'
0
      xDOM.parseError.reason
Text is not allowed in this context according to DTD/Schema.

Expecting: rank, data.

Another useful facility is to control white space. The XML parser normally removes white space which means you don’t actually get what is in the XML. Defining character arrays can be quite difficult if spaces are removed in some arbitrary way. The xml:space attribute ensures that spaces are preserved for character data, but this can be overridden if you really don’t need it. The following shows the effect on spaces.

      x←'<array><rank><dimension>20</dimension></rank>'
      x,←'<data><char length="20">     A      B    </char>'
      x,←'</data></array>'
      ⎕SE.DISPLAY 2⊃XMLToArray x
.→-------------------.
|A      B            |
'--------------------'
      ⎕se.DISPLAY 2⊃XMLToArray dtd,x
.→-------------------.
|     A      B       |
'--------------------'

Another way of dealing with spaces is to use the CDATA definition which can be used anywhere PCDATA is expected.

      x←'<array><rank><dimension>20</dimension></rank><data>'
      x,←'<char length="20"><![CDATA[     A <>   B    ]]>'
      x,←'</char></data></array>'
      ⎕SE.DISPLAY 2⊃XMLToArray x
.→-------------------.
|     A <>   B       |
'--------------------'

Writing XML

I don’t actually like using the MSXML parser to create XML. The XML is not formatted and is spewed out on a single line. The object hierarchy tends to force one into loops which can be difficult to replace with APL arrays. As far as I can see the main advantage in using MSXML is where existing XML needs to be edited and then saved. MSXML is full of useful methods such as replaceChild, removeChild, insertBefore etc. etc.

First of all then is a function to convert an array into XML without using MSXML. The function is identical in versions 8.2 and 9.0.

    ∇ArrayToXML←{⎕IO←0 ⋄ ⎕ML←1 ⋄ ⎕PP←16
[1]       cr←2⊃⎕TC
[2]       array←{ ⍝ Generate array
[3]           ind←⍺⍴' ' ⍝ Indent
[4]           top←ind,'<array>',(ind rank⍴⍵),cr,ind,' <data>',cr
[5]           tail←ind,' </data>',cr,ind,'</array>',cr
[6]           (≡⍵)∊0 1:top,(ind values,⍵),tail
[7]             ⍝ Mark contiguous groups of scalars
[8]           b←{1,(1↓⍵)≠¯1↓⍵}a←0=≡¨,⍵
[9]           top,(∊((b/a),¨⍺)data¨b⊂,⍵),tail}
[10]      rank←{ ⍝ Generate rank information
[11]          ⍬≡⍵:''
[12]          cr,⍺,' <rank>',cr,⍺,'  ',(∊dim¨⍵),cr,⍺,' </rank>'}
[13]      dim←{ ⍝ Format a dimension
[14]          '<dimension>',(⍕⍵),'</dimension>'}
[15]      data←{ ⍝ Append values or array
[16]          r i←⍺ ⍝ Recurse flag and indent
[17]          r:(i⍴' ')values ⍵
[18]          (i+2)array¨⍵}
[19]      values←{ ⍝ Deal with heterogeneous data
[20]          {⍵∧.=⊃⍵}⎕DR¨⍵:⍺,'  ',(value ⍵),cr
[21]          ∊(⊂⍺){⍺,'  ',(value ⍵),cr}¨⍵}
[22]      value←{ ⍝ Format values
[23]          82=⎕DR ⍵:'<char length="',(⍕⍴,⍵),'">',(trans ⍵), '</char>'
[24]          '<number length="',(⍕⍴,⍵),'">',(⍕⍵),'</number>'}
[25]      trans←{ ⍝ Translate reserved characters
[26]          c←5≠b←'<>&"'''⍳a←,⍵
[27]          a[c/⍳⍴b]←'&lt;' '&gt;' '&amp;' '&quot;' '&apos;'[c/b]
[28]          ∊a}
[29]       ⍝ Return result. dtd is global
[30]      dtd,0 array ⍵
[31]  }
    ∇

I would not normally include dtd as a global, referenced on line [30], but it makes the function listing shorter and the DTD has already been listed above.

Running the function on our sample XML produces the following:

      (⍴dtd)↓ArrayToXML XMLToArray xml
<array>
 <rank>
  <dimension>2</dimension><dimension>3</dimension>
 </rank>
 <data>
  <number length="3">0 1 2</number>
  <array>
   <rank>
    <dimension>2</dimension>
   </rank>
   <data>
    <char length="2">AB</char>
   </data>
  </array>
  <number length="2">4 5</number>
 </data>
</array>

Creating XML using MSXML

If you need to use MSXML then the following functions show how to set about the task. Also, there are several characters in ⎕AV which seem to give MSXML a bit of a headache when reading XML so it is interesting to see what MSXML does when generating the XML.

The easiest way to start is to load a bit of XML into an empty DOM object, in fact this appears to be the only way to define a DTD.

      'xDOM'⎕WC'OLEClient' 'MSXML2.DOMDocument.2.6'
      xDOM.loadXML dtd,'<array></array>'
1

This actually loads 3 nodes, the first being the Processing Information, the second the DTD and the third the root node.

      xDOM.childNodes.length
3
      xDOM.childNodes.(item¨(⍳length)-⎕IO).(nodeName nodeType)
  xml  7   array  10   array  1

The nodeType numbers can be checked against the property that contains the enumerated variables.

      xDOM.tagDOMNodeType
NODE_INVALID                  0
NODE_ELEMENT                  1
NODE_ATTRIBUTE                2
NODE_TEXT                     3
NODE_CDATA_SECTION            4
NODE_ENTITY_REFERENCE         5
NODE_ENTITY                   6
NODE_PROCESSING_INSTRUCTION   7
NODE_COMMENT                  8
NODE_DOCUMENT                 9
NODE_DOCUMENT_TYPE           10
NODE_DOCUMENT_FRAGMENT       11
NODE_NOTATION                12

If you don’t load the initial XML in this way then you have to set the root node explicitly. This is easy in Dyalog version 9.0, but quite nasty in version 8.2.

Dyalog 9.0

      'xDOM'⎕WC'OLEClient' 'MSXM       xRoot←xDOM.createElement 'array'
      xDOM.documentElement←xRoot

Dyalog 8.2

      'xDOM'⎕WC'OLEClient' 'MSXML2.DOMDocument.2.6'
      'xRoot'⎕WC xDOM.createElement 'array'
      2 ⎕NQ'xDOM' 'SetPropertyInfo' 'documentElement' 'VT_DISPATCH'
      'xDOM'⎕WS'documentElement' 'xRoot'

Pete Donnelly kindly told me how to get this to work in version 8.2 and explained that 8.2 does not know anything about the IXMLDOMElement which the documentElement property is expecting, so you have to tell it using 2 ⎕NQ.

Creating XML using MSXML in Dyalog version 9.0

The following function creates XML using MSXML in Dyalog version 9.0. For a change of scenery the sub-functions are direct definition operators. In all of them the left function is the DOM object’s createElement method and the right function is the node to be processed.

    ∇ r←ArrayToXMLms a;x;rt;sink;Array;Data;DataMixed;Dim; Rank;Recurse;
            Value;Values;⎕IO;⎕ML;⎕PP
[1]   ⎕IO←0 ⋄ ⎕ML←1 ⋄ ⎕PP←16
[2]   Array←{      ⍝ Append array node to ⍵⍵
[3]       sink←(⍺⍺ Rank ⍵⍵)⍴⍵
[4]       x←⍺⍺'data'
[5]       sink←(⍺⍺ Data x),⍵
[6]       x←⍵⍵.appendChild x
[7]       0}
[8]   Rank←{       ⍝ Create rank node
[9]       ⍬≡⍴⍵:0 ⍝ not required for a scalar
[10]      x←⍺⍺'rank'
[11]      sink←(⍺⍺ Dim x)¨⍵
[12]      x←⍵⍵.appendChild x
[13]      0}
[14]  Dim←{        ⍝ Create dimension node
[15]      x←⍺⍺'dimension'
[16]      x.text←⍕⍵
[17]      x←⍵⍵.appendChild x
[18]      0}
[19]  Data←{       ⍝ Append values & arrays to data node
[20]      (≡⍵)∊0 1:⊃(⍺⍺ Values ⍵⍵)⍵
[21]      ⍝ Mark contiguous groups of scalars and arrays
[22]      b←{1,(1↓⍵)≠¯1↓⍵}a←0=≡¨⍵
[23]      ⊃(b/a)(⍺⍺ DataMixed ⍵⍵)¨b⊂⍵}
[24]  Values←{     ⍝ Deal with heterogeneous data
[25]      {⍵∧.=⊃⍵}82=⎕DR¨⍵:(⍺⍺ Value ⍵⍵)⍵
[26]      ⊃(⍺⍺ Value ⍵⍵)¨⍵}
[27]  Value←{      ⍝ Append values to data
[28]      x←⍺⍺(82=⎕DR⊃⍵)⊃'number' 'char'
[29]      sink←x.setAttribute'length'(⍕⍴,⍵)
[30]      x.text←⍕⍵
[31]      x←⍵⍵.appendChild x
[32]      0}
[33]  DataMixed←{  ⍝ Data is simple or array
[34]      ⍺:(⍺⍺ Values ⍵⍵)⍵     ⍝ simple
[35]      (⍺⍺ Recurse ⍵⍵)¨⍵}    ⍝ array
[36]  Recurse←{    ⍝ Recurse on an array
[37]      x←⍺⍺'array'
[38]      sink←(⍺⍺ Array x)⍵
[39]      x←⍵⍵.appendChild x
[40]      0}
[41]  ⍝ Create DOM and load initial node. dtd is global
[42]  'x'⎕WC'OLEClient' 'MSXML2.DOMDocument.2.6'
[43]  sink←x.loadXML dtd,'<array></array>'
[44]  sink←(x.createElement Array x.documentElement)a
[45]  r←x.xml
    ∇

I don’t want ArrayToXMLms itself be a direct definition function because on line [42] there is a call to ⎕WC which in direct definition mode results in a global variable being created. If there happened to be a variable x lying around in the Workspace then the call to ⎕WC would result in a DOMAIN ERROR. Also, you would have to remember to )ERASE x when the function finished.

The calls to the appendChild method (line [6] is the first) return the address of the child node, which has been updated to include a reference to its parent. The documentation states that if something goes wrong a NULL is returned rather than an address. Ideally one should check the result to see if the operation has been successful. Setting attribute values seems too easy, as line [29] demonstrates. The results are not particularly pleasing as the XML is not formatted into lines, but it all seems to work correctly. It is interesting to see how MSXML copes with the full ⎕AV and exactly which characters it finds difficult.

      XMLToArray a←ArrayToXMLms ⎕AV
0
      {(∨\'<array>'⍷⍵)/⍵}a
<array><rank><dimension>256</dimension></rank><data><char length="256"></ch
ar></data></array>

This shows that MSXML ignores everything after ⊃⎕AV. Using the character version shows that it is no good putting ⊃⎕AV into the XML either.

      XMLToArray ArrayToXML ⎕AV
1  XML Error on line 0. The following tags were not closed: array, data, ch
ar.

Removing the first element from ⎕AV at least processes everything but there are still some characters it finds difficult.

      ⊃b←XMLToArray a←ArrayToXMLms 1↓⎕AV
0
      ((2⊃b)≠1↓⎕AV)/1↓⍳⍴⎕AV
4 12 220 236 239

Creating XML using MSXML in Dyalog version 8.2

I have not attempted to create the equivalent function using MSXML in Dyalog 8.2 because the character version is perfectly adequate and version 8.2 is quite tricky. Direct definition would not be applicable, but otherwise the general scheme would be much the same as the 9.0 version. The only thing that might not be immediately obvious is how to call the appendChild method. The following shows the general approach.

      'xDOM'⎕WC'OLEClient' 'MSXML2.DOMDocument.2.6'
      xDOM.loadXML dtd,'<array></array>'
1
      'xRoot'⎕WC xDOM.documentElement
      'xRank'⎕WC xDOM.createElement 'rank'
      3 ⎕NQ'xRoot' 'appendChild' 'xRank'
 #.appendChild  #.newChild
      'xrc'⎕WC xRoot.childNodes
      'c0'xrc.item 0
      xrc.c0.nodeName
rank

Conclusion

XML seems to be quite simple to manipulate with MSXML and Dyalog APL. Version 9.0 is a great improvement over 8.2, but while more tedious, 8.2 is perfectly capable of carrying out the task.