Tree Arrays: putting XML in nested arrays
Trevor DeVore
lists at mangomultimedia.com
Wed Dec 31 09:46:52 EST 2008
On Dec 31, 2008, at 8:02 AM, David Bovill wrote:
> *Aim*
> I am trying to define a generic data structure for storing tree
> structures
> such as XML documents in the new nested arrays. I'd ike to use this
> structure to store XML documents, and to store the tree data
> structures I
> use for tree widgets. I'd like it to be simpler to use, understand and
> debug, than the XML external.
>
> NB - I remember a reference to some scripts that took XML and
> created the
> new nested arrays. Anyone remember where it is - couldn't find it?
Hi David,
I included some md array <-> xml routines in an article I wrote for
revUp. I'm including the latest version of these handlers at the ned
of the email. I updated them a few weeks ago.
The two primary functions are ConvertXMLToArray and ConvertXMLToArray.
The default array structure stores node values as the value of the
node key in the array:
[root]
[@attributes]
[attr1]
[node]
[node] = value
If you have nodes where you need to store both attributes of a node as
well as it's value then you can pass a flag to ConvertXMLToArray and
values will be stored in a @value key:
[root]
[@attributes]
[attr1]
[node]
[node]
[@attribtues]
[attr1]
[@value] = value
Regards,
--
Trevor DeVore
Blue Mango Learning Systems
ScreenSteps: http://www.screensteps.com
Developer Resources: http://revolution.bluemangolearning.com
--
-- Converts an XML tree into a Revolution multi-dimensional array.
-- A nodes attributes will be stored as an array of it's "@attributes"
key.
-- Node names will retain the sequence information (i.e. node[1],
node[2], etc.).
-- This information is necessary to determine order that keys should
be processed in. Example:
-- set the itemDelimiter to "["
-- put the keys of theArray into theKeys
-- sort theKeys numeric by the last item of each
--
-- pUseValueKey: The default value is false. In this case you get an
array that has an @attributes
-- key for nodes that have attributes and either a) no value or b)
only child nodes. Otherwise it contains the node contents.
-- Set to true if you want to store a nodes value in the '@value' key.
This will allow a key to have
-- both attributes (in @attributes key) and a value (in @value key).
--
function ConvertXMLToArray pXML, pStoreEncodedAs, pUseValueKey
local theArray,theResult,theRootNode,theTreeID
local theXMLEncoding
## Create an XML tree from XML text
put revCreateXMLTree(pXML, true, true, false) into theTreeID
if theTreeID is an integer then
## Determine the encoding of the XML, default to UTF-8
put matchtext(pXML, "<\?xml (.*)encoding=" & quote & "(.*)" &
quote & "\?>", versionMatch, theXMLEncoding) into theResult
if theXMLEncoding is empty then put "utf-8" into theXMLEncoding
## Now convert to array.
## The 1st dimension has one key which is the name of the
root node.
put revXMLRootNode(theTreeID) into theRootNode
if theRootNode is not empty and not(theRootNode begins with
"xmlerr,") then
put ConvertXMLNodeToArray(theTreeID, theRootNode,
theXMLEncoding, pStoreEncodedAs, pUseValueKey) into
theArray[theRootNode]
end if
revDeleteXMLTree theTreeID
end if
return theArray
end ConvertXMLToArray
function ConvertXMLTreeToArray pXMLTree, pStoreEncodedAs, pUseValueKey
return ConvertXMLToArray(revXMLText(pXMLTree), pStoreEncodedAs,
pUseValueKey)
end ConvertXMLTreeToArray
--
-- Converts a multi-dimensional array to an XML tree.
-- The array should contain one key in the 1st dimension which
-- will become the root node. Attributes of a node should be stored
-- as an array in the @attributes key. Sequence information for multiple
-- nodes with the same name should be included in the node name using
-- brackets (i.e. node[1], node[2], node[3]).
-- Returns an xml tree id (integer) or an error message.
--
function ConvertArrayToXML pArray, pArrayEncoding, pStoreEncodedAs
local theError,theRootNode,theXML,theXMLTree
## if pArrayEncoding is empty then current platform encoding is
assumed
if pStoreEncodedAs is empty then put "UTF-8" into pStoreEncodedAs
## Create XML for root node. Note that we take extra steps in
order to support
## converting an array that only represents part of a tree rather
than the entire tree.
## In this case there may be multiple nodes at the root level.
put line 1 of the keys of pArray into theRootNode
set the itemdelimiter to "["
put "<" & item 1 of theRootNode & "/>" into theXML
## Create XML needed to create tree
put format("<?xml version=\"1.0\" encoding=\"%s\"?>%s", \
pStoreEncodedAs, theXML) into theXML
put revCreateXMLTree(theXML, true, true, false) into theXMLTree
if theXMLTree is an integer then
## Loop over all nodes at root level
put false into stripMetaKeys
put SortArrayKeysWithXMLOrdering(pArray, stripMetaKeys) into
theNodes
## Create tree using helper function
repeat for each line theNode in theNodes
ConvertArrayDimensionToXML pArray[theNode], theXMLTree,
slash & theNode, \
pArrayEncoding, pStoreEncodedAs
put the result into theError
if theError is not empty then exit repeat
end repeat
if theError is not empty then
## something went wrong, clean bad tree
revDeleteXMLTree theXMLTree
end if
else
put theXMLTree into theError
end if
if theError is not empty then
return theError
else
return theXMLTree
end if
end ConvertArrayToXML
--
-- Helper function for ConvertArrayToXML
-- Converts the multi-dimensional array pArray to nodes in pTreeID.
-- Calls itself recursively.
-- Returns error message.
--
private command ConvertArrayDimensionToXML pArray, pTreeID, pNode,
pArrayEncoding, pStoreEncodedAs
local theError,theKey,theKeys,theNode
## A workaround for fact that Revolution does not return
## keys in the order we created them
put false into stripMetaKeys
put SortArrayKeysWithXMLOrdering(pArray, stripMetaKeys) into
theNodes
## Arrays might have sequencing info in name
## (i.e. step[1], step[2], ... )
set the itemdelimiter to "["
repeat for each line theFullNode in theNodes
put item 1 of theFullNode into theNode
## Look for attributes. These will be added as attributes to
pNode.
if theNode is "@attributes" or theNode is "@attr" then
repeat for each line theKey in the keys of
pArray[theFullNode]
revSetXMLAttribute pTreeID, pNode, theKey, \
EncodeString(pArray[theFullNode][theKey], \
pArrayEncoding, pStoreEncodedAs)
if the result begins with "xmlerr," then
put the result && "(setting attribute" && theKey
&& "for node" && pNode & ")" into theError
end if
if theError is not empty then exit repeat
end repeat
else if theNode is "@value" then
## This XML tree is using complex structure. Node is the
value of the parent node
revPutIntoXMLNode pTreeID, pNode,
EncodeString(pArray[theFullNode], pArrayEncoding, pStoreEncodedAs)
if the result begins with "xmlerr," then
put the result && "(adding child node" && theNode &&
"to node" && pNode & ")" into theError
end if
else
if the keys of pArray[theFullNode] is not empty then
## Node has children. Add node to XML tree then call
self recursivly to create children nodes.
revAddXMLNode pTreeID, pNode, theNode, empty
if the result begins with "xmlerr," then
put the result && "(adding node" && theNode & ")"
into theError
end if
if theError is empty then
ConvertArrayDimensionToXML pArray[theFullNode],
pTreeID, pNode & slash & theFullNode, \
pArrayEncoding, pStoreEncodedAs
put the result into theError
end if
else
## Node has no children but possibly a value. Create
node and add value (which may be empty).
revAddXMLNode pTreeID, pNode, theNode, \
EncodeString(pArray[theFullNode],
pArrayEncoding, pStoreEncodedAs)
if the result begins with "xmlerr," then
put the result && "(adding child node" && theNode
&& "to node" && pNode & ")" into theError
end if
end if
end if
if theError is not empty then exit repeat
end repeat
return theError
end ConvertArrayDimensionToXML
--
-- Revolution array keys are never guaranteed to be in order you
created
-- them in so we must come up with some other way of maintaining
-- proper sequence. For arrays representing XML, the XML syntax is
-- used (i.e. node[1], node[2], etc.). This handler will sort keys
that use
-- this syntax for representing sequence.
--
function SortArrayKeysWithXMLOrdering pArray, pStripMetaKeys
put pStripMetaKeys is not false into pStripMetaKeys
put the keys of pArray into theKeys
set the itemdelimiter to "["
sort theKeys numeric by the last item of each -- 1], 2], 3], etc.
if pStripMetaKeys then
filter theKeys without "@*"
end if
return theKeys
end SortArrayKeysWithXMLOrdering
--
-- Helper function for ConvertXMLToArray.
-- Converts an XML node to a multi-dimensional array.
-- Calls itself recursively.
--
private function ConvertXMLNodeToArray pTreeID, pNode,
pXMLTreeEncoding, pStoreEncodedAs, pUseValueKey
local theArrayA,theAttributes,theChildNode,theKey
## Look for attributes of the node. Store as array in
"@attributes" key
put revXMLAttributes(pTreeID, pNode, tab, cr) into theAttributes
if theAttributes is not empty then
put EncodeString(theAttributes, pXMLTreeEncoding,
pStoreEncodedAs) into theAttributes
split theAttributes by cr and tab -- create array
put theAttributes into theArrayA["@attributes"]
end if
## Look for children nodes.
set the itemdelimiter to slash
put revXMLFirstChild(pTreeID, pNode) into theChildNode
if theChildNode is empty or theChildNode begins with "xmlerr," then
put EncodeString(revXMLNodeContents(pTreeID, pNode),
pXMLTreeEncoding, pStoreEncodedAs) into theValue
if word 1 to -1 of theValue is empty and the keys of
theArrayA is not empty then
## Empty node that has attributes
return theArrayA
else if pUseValueKey then
## Force value into @value
put theValue into theArrayA["@value"]
return theArrayA
else
## Single Node with value: Return value. Attributes are
ignored.
return theValue
end if
else
## Child nodes were found. Recursively call self and store
result in array.
repeat while theChildNode is not empty and not (theChildNode
begins with "xmlerr,")
put the last item of theChildNode into theKey
put ConvertXMLNodeToArray(pTreeID, theChildNode,
pXMLTreeEncoding, pStoreEncodedAs, pUseValueKey) into theArrayA[theKey]
put revXMLNextSibling(pTreeID, theChildNode) into
theChildNode
end repeat
return theArrayA
end if
end ConvertXMLNodeToArray
--
-- Helper function for converting the encoding of strings when
converting to and from XML.
--
private function EncodeString pString, pInEncoding, pOutEncoding
## convert utf-8 to utf8 for uniencode/decode
replace "-" with empty in pInEncoding
replace "-" with empty in pOutEncoding
if pInEncoding is not empty then
-- if pOutEncoding is empty then pString will be converted to
the current platform encoding
return unidecode(uniencode(pString, pInEncoding), pOutEncoding)
else
if pOutEncoding is not empty then
-- if pInEncoding is empty then pString is assumed to be
in the current platform encoding
return unidecode(uniencode(pString, pInEncoding),
pOutEncoding)
else
return pString
end if
end if
end EncodeString
More information about the use-livecode
mailing list