XML Archives
XMLTL: XML Transport Layer
An increasing transport need is for object serialization. Since nearly everything on the planet understands XML, this is a logical way to transport serialized data (similar to the mechanics behind JSON). In the end, my hope is to be able to access node data in target platforms as native objects--unaware if they were transported as JSON, XML, or carrier pidgeon.
This is a formal structure and type representation that can represent any data structure as XML. The DTD is located at http://rushmoreradio.net/public/xmltl.dtd
PHP Implementation
XMLTL.php (8587 bytes)
<?php
/****************************************************************************
XMLTL - XML Transport Layer
Version: 0.0.0
2009-02-10
Zac Hester
An increasing transport need is for object serialization. Since nearly
everything on the planet understands XML, this is a logical way to
transport serialized data (similar to the mechanics behind JSON).
In the end, my hope is to be able to access node data in target
platforms as native objects--unaware if they were transported as
JSON, XML, or carrier pidgeon.
//Example encoding (simple):
$msg = new XMLTL(array('k'=>'v','k1'=>'v1','k2'=>'v2'));
echo $msg;
//Example encoding (complex):
class Example { public $k='v';public $k1='v1';public $k2='v2'; }
$msg = new XMLTL(new Example());
echo $msg->export();
//Example decoding:
$tl = new XMLTL('<'.'?xml version="1.0"?'.'><xmltl> ... </xmltl>');
//Provide a native PHP object.
$php_object = $tl->import();
//Provide a PHP associative array.
$php_assoc_array = $tl->import(false, true);
****************************************************************************/
class XMLTL {
public $error = '';
public $pretty = false; //Not functional at this time.
protected $src = false;
protected $xml = false;
private $d = 0;
private $assoc = false;
/**
* __construct
* Allows the user to initialize the object for either encoding or
* decoding.
*
* @param arg An initial array, object, or XMLTL string
*/
public function __construct($arg = false) {
if($arg && (is_array($arg) || is_object($arg))) {
$this->src = $arg;
}
else if($arg && is_string($arg)) {
$this->xml = $arg;
}
else if($arg) {
$this->error = 'Invalid source type specified.';
}
}
/**
* export
* Exports the data representation as an XMLTL string.
*
* @param src Specify an immediate source object for exporting
* @return An XMLTL string representing the provided object
*/
public function export($src = false) {
if($src) {
$this->src = $arg;
}
$this->buildXML();
return($this->xml);
}
/**
* import
* Imports and XMLTL string for translating into a PHP data type.
*
* @param xml Specify an immediate XMLTL string for importing
* @param assoc Set to true to get an associative array instead of
* a PHP object.
* @return A native PHP data representation of the XMLTL string
*/
public function import($xml = false, $assoc = false) {
if($xml) {
$this->xml = $xml;
}
$this->assoc = $assoc;
$this->importXML();
return($this->src);
}
/**
* __toString
* Handles string requests by sending back the XMLTL string.
*
* @return The output of the export method
*/
public function __toString() {
return($this->export());
}
/*---------------------------------------------------------------------*/
/**
* buildXML
* Constructs a complete XMLTL document using known data.
*/
protected function buildXML() {
$stype = $this->getType($this->src);
$this->xml = '<'.'?xml version="1.0"?'
.">\n".'<!DOCTYPE xmltl PUBLIC "-//NRR//XMLTL//EN"'
.' "http://rushmoreradio.net/public/xmltl.dtd">'
."\n".'<xmltl bt="'.$stype.'">';
if($stype == 'a') {
foreach($this->src as $v) {
$this->xml .= $this->getTag($v);
}
}
else if($stype == 'h') {
foreach($this->src as $k => $v) {
$this->xml .= $this->getTag($v, $k);
}
}
else {
$this->xml .= $this->getTagData($this->src);
}
$this->xml .= "\n</xmltl>";
}
/**
* getType
* Determines the XMLTL type for any PHP variable.
*
* @param node Any PHP variable
* @return The XMLTL type string for the variable
*/
private function getType($node) {
if(is_array($node)) {
$ks = array_keys($node);
if(is_string($ks[0])) {
return('h');
}
return('a');
}
else if(is_int($node)) { return('i'); }
else if(is_null($node)) { return('n'); }
else if(is_bool($node)) { return('b'); }
else if(is_float($node)) { return('f'); }
else if(is_string($node)) { return('s'); }
else if(is_object($node)) { return('h'); }
return('');
}
/**
* getTag
* Builds a complete XMLTL tag for any PHP variable.
*
* @param v The PHP variable for which to build a tag.
* @param k The key name for named variables.
* @return An XMLTL string representing the PHP variable.
*/
private function getTag($v, $k = false) {
$t = $this->getType($v);
$n = $k ? ' n="'.htmlentities($k).'"' : '';
$in = str_repeat("\t", $this->d+1);
switch($t) {
case 'n':
return("\n$in".'<s'.$n.' t="n"/>');
case 'b':
return(
"\n$in".'<s'.$n.' t="b">'
.$this->getTagData($t,$v).'</s>'
);
case 'f': case 'i':
return(
"\n$in".'<s'.$n.' t="'.$t.'">'
.$this->getTagData($t,$v).'</s>'
);
case 's':
return(
"\n$in".'<s'.$n.'>'
.$this->getTagData($t,$v).'</s>'
);
case 'a':
$b = "\n$in".'<a'.$n.'>';
++$this->d;
foreach($v as $value) {
$b .= $in.$this->getTag($value);
}
--$this->d;
return($b."\n$in</a>");
case 'h':
$b = "\n$in<h$n>";
++$this->d;
foreach($v as $key => $value) {
$b .= $in.$this->getTag($value, $key);
}
--$this->d;
return($b."\n$in</h>");
}
$this->error = 'Invalid type found in array/object.';
return('<!-- XMLTL Unknown Data -->');
}
/**
* getTagData
* Builds the CDATA value for a scalor tag.
*
* @param type The XMLTL data type.
* @param value A scalor variable value.
* @return A valid XMLTL scalor value string.
*/
private function getTagData($type, $value) {
switch($type) {
case 'n':
return('');
case 'b':
return($value?'true':'false');
case 'f': case 'i': case 's':
return(htmlspecialchars($value));
}
$this->error = 'Internal logic error.';
return('<!-- XMLTL Internal Error -->');
}
/**
* importXML
* Imports the provided XML document into native PHP data types.
*/
private function importXML() {
$sx = new SimpleXMLElement($this->xml);
if($this->assoc) {
$this->src = $this->getArray($sx);
}
else {
$this->src = $this->getObject($sx);
}
}
/**
* getArray
* Builds an appropriate array out of a SimpleXML object.
*
* @param sxe The SimpleXMLElement instance
* @return An array representing the SimpleXMLElement
*/
private function getArray($sxe) {
//Current tag name.
$ctag = $sxe->getName();
//Scalor tags.
if($ctag == 's') { return($this->getScalor($sxe)); }
//Array buffer.
$arr = array();
//Run through each child node.
foreach($sxe as $node) {
//Keyed node.
if(isset($node['n'])) {
//Assign with associative index.
$arr[strval($node['n'])] = $this->getArray($node);
}
//Anonymous node.
else {
//Assign with numeric index.
$arr[] = $this->getArray($node);
}
}
return($arr);
}
/**
* getObject
* Builds an appropriate PHP object out of a SimpleXML object.
*
* @param sxe The SimpleXMLElement instance
* @return An object representing the SimpleXMLElement
*/
private function getObject($sxe) {
//Current tag.
$ctag = $sxe->getName();
//Check for root node.
if($ctag == 'xmltl') {
//Get base type.
$bt = isset($sxe['bt']) ? strval($sxe['bt']) : 'h';
//Pretend to be one of the other tags.
$ctag = ($bt != 'h' && $bt != 'a') ? 's' : $bt;
}
//Scalor.
if($ctag == 's') {
return($this->getScalor($sxe));
}
//Array.
else if($ctag == 'a') {
$arr = array();
foreach($sxe as $node) {
$arr[] = $this->getObject($node);
}
return($arr);
}
//Hash.
else if($ctag == 'h') {
$obj = new stdClass();
$anon = 0;
foreach($sxe as $node) {
//Keyed node is assigned to a named property.
if(isset($node['n'])) {
$obj->{strval($node['n'])} = $this->getObject($node);
}
//Anonymous node is assigned to a list of anonymous nodes.
else {
if($anon == 0) { $obj->ANONYMOUS = array(); }
$obj->ANONYMOUS[$anon] = $this->getObject($node);
++$anon;
}
}
return($obj);
}
//Conforming XML documents won't get here.
$this->error = 'Invalid XML document.';
return(false);
}
/**
* getScalor
* Returns an appropriate scalor value for an end-point SimpleXMLElement
*
* @param sxe The SimpleXMLElement instance
* @return A properly-typed PHP scalor value
*/
private function getScalor($sxe) {
$str = strval($sxe);
if(isset($sxe['t'])) {
switch($sxe['t']) {
case 'n': return(NULL);
case 'b': return($str=='true'?true:false);
case 'i': return(intval($str));
case 'f': return(floatval($str));
}
}
return($str);
}
}
?>Full JavaScript Implementation
XMLTL.js (4762 bytes)
/****************************************************************************
XMLTL: XML Transport Layer
Zac Hester
2009-02-11
DTD: http://rushmoreradio.net/public/xmltl.dtd
This an exhaustive functional class, not intended for implementation.
Use the tiny classes XMLTLImport.js for specific use.
****************************************************************************/
function XMLTL() {
this.src = false;
this.xml = false;
this.error = '';
if(arguments[0]) {
if(typeof(arguments[0]) == 'object') {
this.src = arguments[0];
}
else if(typeof(arguments[0]) == 'string') {
this.xml = arguments[0];
}
else {
this.error = 'Invalid type passed to constructor.';
}
}
}
XMLTL.prototype.export = function() {
if(arguments[0]) {
this.src = arguments[0];
}
this.buildXML();
return(this.xml);
};
XMLTL.prototype.import = function() {
if(arguments[0]) {
this.xml = arguments[0];
}
this.importXML();
return(this.src);
};
XMLTL.prototype.toString = function() {
return(this.export());
};
/*----------------------------------------------------------------------*/
XMLTL.prototype.buildXML = function() {
var stype = this.getType(this.src);
this.xml = '<?xml version="1.0"?'
+">\n"+'<!DOCTYPE xmltl PUBLIC "-//NRR//XMLTL//EN"'
+' "http://rushmoreradio.net/public/xmltl.dtd">'
+"\n"+'<xmltl bt="'+stype+'">';
if(stype == 'a') {
for(var i in this.src) {
this.xml += this.getTag(this.src[i]);
}
}
else if(stype == 'h') {
for(var k in this.src) {
this.xml += this.getTag(this.src[k], k);
}
}
else {
this.xml += this.getTagData(this.src);
}
this.xml += "\n</xmltl>";
};
XMLTL.prototype.getType = function(node) {
if(typeof(node) == 'object') {
if(node == null) { return('n'); }
if(node.length) { return('a'); }
return('h');
}
else if(typeof(node) == 'number') {
if(node.indexOf('.') != -1) { return('f'); }
return('i');
}
else if(typeof(node) == 'null') { return('n'); }
else if(typeof(node) == 'boolean') { return('b'); }
else if(typeof(node) == 'string') { return('s'); }
return('');
};
XMLTL.prototype.getTag = function(v) {
var n = arguments[1] ? ' n="'+this.getHE(arguments[1])+'"' : '';
var t = this.getType(v);
switch(t) {
case 'n':
return('<s'+n+' t="n"/>');
case 'f': case 'i': case 'b':
return('<s'+n+' t="'+t+'">'+this.getTagData(t,v)+'</s>');
case 's':
return('<s'+n+'>'+this.getTagData(t,v)+'</s>');
case 'a':
var a = '<a'+n+'>';
for(var i in v) { a += this.getTag(v[i]); }
return(a+'</a>');
case 'h':
var h = '<h'+n+'>';
for(var k in v) { h += this.getTag(v[k], k); }
return(h+'</h>');
}
this.error = 'Invalid type found in array/object.';
return('<!-- XMLTL Unknown Data -->');
};
XMLTL.prototype.getTagData = function(type, value) {
switch(type) {
case 'n': return('');
case 'b': return(value?'true':'false');
case 'f': case 'i': case 's': return(this.getHE(value));
}
this.error = 'Internal logic error.';
return('<!-- XMLTL Internal Error -->');
};
XMLTL.prototype.importXML = function() {
var dom = null;
if(window.ActiveXObject) {
dom = new ActiveXObject('Microsoft.XMLDOM');
dom.async = 'false';
dom.loadXML(this.xml);
}
else {
var dp = new DOMParser();
dom = dp.parseFromString(this.xml, 'text/xml');
}
this.src = this.getObject(dom.childNodes[1]);
return(this.src);
};
XMLTL.prototype.getObject = function(elem) {
var ctag = elem.tagName;
if(ctag == 'xmltl') {
var bt = elem.getAttribute('bt') ? elem.getAttribute('bt') : 'h';
ctag = (bt != 'h' && bt != 'a') ? 's' : bt;
}
if(ctag == 's') {
return(this.getScalor(elem));
}
else if(ctag == 'a') {
var arr = [];
for(var i = 0; i < elem.childNodes.length; ++i) {
arr.push(this.getObject(elem.childNodes[i]));
}
return(arr);
}
else if(ctag == 'h') {
var obj = {};
var c = null;
for(var i = 0; i < elem.childNodes.length; ++i) {
c = elem.childNodes[i];
if(c.getAttribute('n')) {
obj[c.getAttribute('n')] = this.getObject(c);
}
else {
if(!obj.ANONYMOUS) { obj.ANONYMOUS = []; }
obj.ANONYMOUS.push(this.getObject(c));
}
}
return(obj);
}
this.error = 'Invalid XML document.';
return(null);
};
XMLTL.prototype.getScalor = function(elem) {
var s = elem.textContent;
var t = elem.getAttribute('t');
if(t) {
switch(t) {
case 'n': return(null);
case 'b': return(s=='true'?true:false);
case 'i': return(parseInt(s));
case 'f': return(parseFloat(s));
}
}
return(s);
};
XMLTL.prototype.getHE = function(str) {
var nstr = str.replace(
/&|<|>|"/g,
function(m) {
switch(m) {
case '&': return('&');
case '<': return('<');
case '>': return('>');
case '"': return('"');
}
return(null);
}
);
return(nstr);
};
Import-only JavaScript Implementation
XMLTLImport.js (1348 bytes)
//XMLTLImport, Zac Hester, 2009-02-11
function XMLTLImport(xmltl_dom_root) {
this.dom = xmltl_dom_root;
}
XMLTLImport.prototype.import = function() {
return(this.getObject(arguments[0]?arguments[0]:this.dom));
};
XMLTLImport.prototype.getObject = function(elem) {
var ctag = elem.tagName;
if(ctag == 'xmltl') {
var bt = elem.getAttribute('bt') ? elem.getAttribute('bt') : 'h';
ctag = (bt != 'h' && bt != 'a') ? 's' : bt;
}
if(ctag == 's') {
return(this.getScalor(elem));
}
else if(ctag == 'a') {
var arr = [];
for(var i = 0; i < elem.childNodes.length; ++i) {
arr.push(this.getObject(elem.childNodes[i]));
}
return(arr);
}
else if(ctag == 'h') {
var obj = {};
var c = null;
for(var i = 0; i < elem.childNodes.length; ++i) {
c = elem.childNodes[i];
if(c.getAttribute('n')) {
obj[c.getAttribute('n')] = this.getObject(c);
}
else {
if(!obj.ANONYMOUS) { obj.ANONYMOUS = []; }
obj.ANONYMOUS.push(this.getObject(c));
}
}
return(obj);
}
this.error = 'Invalid XML document.';
return(null);
};
XMLTLImport.prototype.getScalor = function(elem) {
var s = elem.textContent;
var t = elem.getAttribute('t');
if(t) {
switch(t) {
case 'n': return(null);
case 'b': return(s=='true'?true:false);
case 'i': return(parseInt(s));
case 'f': return(parseFloat(s));
}
}
return(s);
};
Import-only ActionScript 3.0 Implementation
XMLTLImport.as (2879 bytes)
/****************************************************************************
XMLTLImport: XML Transport Layer, Client Import Utility
Version 0.0.0 for ActionScript 3.0
Zac Hester
2009-02-11
XMLTL native object importing for Action Script 3.0.
XML object data transport layer for highly structured data types.
DTD: http://rushmoreradio.net/public/xmltl.dtd
Example usage:
var xi:XMLTLImport = new XMLTLImport(<xmltl>
<s n="k0">v0</s>
<h n="k1">
<s n="k1a">v1a</s>
<s n="k1b">v1b</s>
</h>
</xmltl>);
var as3obj:Object = xi.importObject();
trace(as3obj.k0); //outputs "v1"
trace(as3obj.k1.k1b); //outputs "v1b"
****************************************************************************/
package {
public class XMLTLImport {
//Handles the root XML document element.
private var xml;
/**
* XMLTLImport
* Creates a new XMLTLImport object.
*
* @param xml_root The XMLTL XML object to import
*/
public function XMLTLImport(xml_root:XML) {
xml = xml_root;
}
/**
* importObject
* Provides an interface for importing the XMLTL document.
*
* @param xml_root An optional XMLTL XML object to import
* @return A native object representing the XMLTL contents
*/
public function importObject() {
return(getObject(arguments[0]?arguments[0]:xml));
}
/**
* getObject
* Constructs an AS3 object out of an XMLTL node.
*
* @param node The XMLTL node element
* @return An AS3 object representing the node
*/
private function getObject(node:XML) {
var ctag:String = node.localName();
if(ctag == 'xmltl') {
var bt:String = node.@bt ? node.@bt : 'h';
ctag = (bt != 'h' && bt != 'a') ? 's' : bt;
}
if(ctag == 's') {
return(getScalor(node));
}
else if(ctag == 'a') {
var arr:Array = new Array();
for(var i = 0; i < node.children().length(); ++i) {
arr.push(getObject(node.children()[i]));
}
return(arr);
}
else if(ctag == 'h') {
var obj:Object = new Object();
var c:XML = null;
for(var j = 0; j < node.children().length(); ++j) {
c = node.children()[j];
if(c.@n) {
obj[c.@n] = getObject(c);
}
else {
if(!obj.ANONYMOUS) { obj.ANONYMOUS = new Array(); }
obj.ANONYMOUS.push(getObject(c));
}
}
return(obj);
}
return(null);
}
/**
* getScalor
* Converts the contents of a leaf node into a properly-typed
* AS3 scalor value.
*
* @param node The XMLTL leaf node
* @return A typed scalor value representing the node's value
*/
private function getScalor(node:XML) {
var s:String = node.toString();
var t:String = node.@t;
if(t) {
switch(t) {
case 'n': return(null);
case 'b': return(s=='true'?true:false);
case 'i': return(parseInt(s));
case 'f': return(parseFloat(s));
}
}
return(s);
}
}
}