--- title: JXON slug: Archive/JXON translation_of: Archive/JXON ---

JXON (无损 JavaScript XML对象注释)  是一个通用名称,通过它定义使用 XML的JavaScript对象的表示。 这种转换没有真正的标准,但一些公约开始出现在网络上。在某些情况下,必须从JavaScript解释器中读取XML文档的全部内容(例如用于Web应用程序语言或设置XML文档)。在这些情况下,JXON可能是最实用的方法。

在本文中,我们将演示如何将解析的XML Document(即Document的一个实例)转换为JavaScript Object树(即Object的嵌套实例树),反之亦然,用一些不同的算法。首先阅读XML介绍文章会比较有帮助。

如果您想要一个完整的双向JXON库(在JSON全局对象上建模),请跳至专用段落(但请阅读关于const语句兼容性的注释)。

注意:如果您只想解决XML文档的某些部分(而不是以JavaScript / JSON为模板目的开始),则使用XPath而不是将整个文档转换为JSON。

Conversion snippets

现在想象你有这个示例XML文档:

example.xml
<?xml version="1.0"?>
<!DOCTYPE catalog SYSTEM "catalog.dtd">
<catalog>
  <product description="Cardigan Sweater">
   <catalog_item gender="Men's">
     <item_number>QWZ5671</item_number>
     <price>39.95</price>
     <size description="Medium">
       <color_swatch image="red_cardigan.jpg">Red</color_swatch>
       <color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch>
     </size>
     <size description="Large">
       <color_swatch image="red_cardigan.jpg">Red</color_swatch>
       <color_swatch image="burgundy_cardigan.jpg">Burgundy</color_swatch>
     </size>
   </catalog_item>
   <catalog_item gender="Women's">
     <item_number>RRX9856</item_number>
     <discount_until>Dec 25, 1995</discount_until>
     <price>42.50</price>
     <size description="Medium">
       <color_swatch image="black_cardigan.jpg">Black</color_swatch>
     </size>
   </catalog_item>
  </product>
  <script type="text/javascript"><![CDATA[function matchwo(a,b) {
    if (a < b && a < 0) { return 1; }
    else { return 0; }
}]]></script>
</catalog>

首先,按照“如何创建DOM树”文章中的描述,创建一个类似前面示例的DOM树。如果您已经有使用XMLHttpRequest的DOM树,请跳到下一段。

注意:如果您正在使用XMLHttpRequest实例来检索XML文件,请使用yourRequest.responseXML属性来获取已解析的XML文档。不要使用yourRequest.responseText

这里提出的算法(参见:#1#2#3#4)将只考虑以下类型的节点及其属性:

  1. Document (只作为函数参数),
  2. DocumentFragment (只作为函数参数),
  3. Element,
  4. Text (从不作为函数参数),
  5. CDATASection (从不作为函数参数),
  6. Attr (从不作为函数参数)。

对于JavaScript的使用来说,这是一个很好的标准化的妥协,因为XML文档的所有信息都包含在这些节点类型中。所有其他信息(如处理指令,模式,注释等)都将丢失。这种算法仍然被认为是无损的,因为丢失的是元信息而不是信息

为了避免冲突,节点和属性名称的表示不区分大小写(总是以小写形式呈现),所以使用JavaScript设置的对象的本地属性名称必须总是具有某种大小写(即至少有一个大写字母在他们的名字),如你可以看到下面。

下面的算法有些基于Parker公约(版本0.4),它规定了标签名称对象属性名称的转换以及每个标签(纯文本解析)的所有收集 text content typeof 的识别。但有一些分歧(所以,可以说我们遵循我们的惯例)。而且,对于设想的节点,所有算法都是同样无损的

我们认为第三种算法最具代表性和实用性的JXON解析算法

现在让我们将doc(DOM树)序列化为一个JavaScript对象树(您可以阅读关于使用对象以及Javascript如何面向对象的更多信息)。我们可以使用几种算法将其内容转换为Javascript对象树。

算法 #1: 一个冗长的方式

这个简单的递归构造函数将一个XML DOM树转换成一个JavaScript对象树。每个元素的文本内容都存储在keyValue属性中,而nodeAttributes(如果存在)列在子对象keyAttributes下。构造函数的参数可以是整个XML DocumentDocumentFragment或简单的Element节点。

/*\
|*|
|*|  JXON Snippet #1 - Mozilla Developer Network
|*|
|*|  https://developer.mozilla.org/en-US/docs/JXON
|*|  https://developer.mozilla.org/User:fusionchess
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/

function parseText (sValue) {
  if (/^\s*$/.test(sValue)) { return null; }
  if (/^(?:true|false)$/i.test(sValue)) { return sValue.toLowerCase() === "true"; }
  if (isFinite(sValue)) { return parseFloat(sValue); }
  if (isFinite(Date.parse(sValue))) { return new Date(sValue); }
  return sValue;
}

function JXONTree (oXMLParent) {
  var nAttrLen = 0, nLength = 0, sCollectedTxt = "";
  if (oXMLParent.hasChildNodes()) {
    for (var oNode, sProp, vContent, nItem = 0; nItem < oXMLParent.childNodes.length; nItem++) {
      oNode = oXMLParent.childNodes.item(nItem);
      if ((oNode.nodeType - 1 | 1) === 3) { sCollectedTxt += oNode.nodeType === 3 ? oNode.nodeValue.trim() : oNode.nodeValue; } // nodeType is "Text" (3) or "CDATASection" (4)
      else if (oNode.nodeType === 1 && !oNode.prefix) { // nodeType is "Element" (1)
        sProp = oNode.nodeName.toLowerCase();
        vContent = new JXONTree(oNode);
        if (this.hasOwnProperty(sProp)) {
          if (this[sProp].constructor !== Array) { this[sProp] = [this[sProp]]; }
          this[sProp].push(vContent);
        } else { this[sProp] = vContent; nLength++; }
      }
    }
    this.keyValue = parseText(sCollectedTxt);
  } else { this.keyValue = null; }
  if (oParentNode.hasAttributes && oXMLParent.hasAttributes()) {
    var oAttrib;
    this.keyAttributes = {};
    for (nAttrLen; nAttrLen < oXMLParent.attributes.length; nAttrLen++) {
      oAttrib = oXMLParent.attributes.item(nAttrLen);
      this.keyAttributes[oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
    }
  }
  /*
  * Optional properties...

  this.keyLength = nLength;
  this.attributesLength = nAttrLen;
  // this.DOMNode = oXMLParent;

  */

  /* Object.freeze(this); */
}

/*
* Optional methods... Uncomment the optional properties first!

JXONTree.prototype.valueOf = function () { return this.keyValue; };
JXONTree.prototype.toString = function () { return String(this.keyValue); };
JXONTree.prototype.getItem = function (nItem) {
  if (nLength === 0) { return null; }
  var nCount = 0;
  for (var sKey in this) { if (nCount === nItem) { return this[sKey]; } nCount++; }
  return null;
};
JXONTree.prototype.getAttribute = function (nAttrId) {
  if (nAttrLen === 0 || nAttrId + 1 > nAttrLen) { return null; }
  var nAttr = 0;
  for (var sAttrName in this.keyAttributes) { if (nAttr === nAttrId) { return this.keyAttributes[sAttrName]; } nAttr++; }
  return null;
};
JXONTree.prototype.hasChildren = function () { return this.keyLength > 0; };

*/

var myObject = new JXONTree(doc);
// we got our javascript object! try: alert(JSON.stringify(myObject));
注意:如果你想冻结整个对象树(因为XML文档的“静态”性质),取消注释字符串:/* Object.freeze(this); */Object.freeze()方法防止将新属性添加到该属性中,防止现有属性被删除,并防止现有属性或其可枚举性,可配置性或可写性发生更改。本质上,对象树是有效的不可变的。

 

用这个算法我们的例子变成:

{
 "catalog": {
   "product": {
     "catalog_item": [{
       "item_number": {
         "keyValue": "QWZ5671"
       },
       "price": {
         "keyValue": 39.95
       },
       "size": [{
         "color_swatch": [{
           "keyValue": "Red",
           "keyAttributes": {
             "image": "red_cardigan.jpg"
           }
         }, {
           "keyValue": "Burgundy",
           "keyAttributes": {
             "image": "burgundy_cardigan.jpg"
           }
         }],
         "keyValue": null,
         "keyAttributes": {
           "description": "Medium"
         }
       }, {
         "color_swatch": [{
           "keyValue": "Red",
           "keyAttributes": {
             "image": "red_cardigan.jpg"
           }
         }, {
           "keyValue": "Burgundy",
           "keyAttributes": {
             "image": "burgundy_cardigan.jpg"
           }
         }],
         "purchased": {
           "keyValue": null
         },
         "keyValue": null,
         "keyAttributes": {
           "description": "Large"
         }
       }],
       "keyValue": null,
       "keyAttributes": {
         "gender": "Men's"
       }
     }, {
       "item_number": {
         "keyValue": "RRX9856"
       },
       "discount_until": {
         "keyValue": new Date(1995, 11, 25)
       },
       "price": {
         "keyValue": 42.5
       },
       "size": {
         "color_swatch": {
           "keyValue": "Black",
           "keyAttributes": {
             "image": "black_cardigan.jpg"
           }
         },
         "keyValue": null,
         "keyAttributes": {
           "description": "Medium"
         }
       },
       "keyValue": null,
       "keyAttributes": {
         "gender": "Women's"
       }
     }],
     "keyValue": null,
     "keyAttributes": {
       "description": "Cardigan Sweater"
     }
   },
   "script": {
     "keyValue": "function matchwo(a,b) {\n if (a < b && a < 0) { return 1; }\n else { return 0; }\n}",
     "keyAttributes": {
       "type": "text/javascript"
     }
   },
   "keyValue": null
 },
 "keyValue": null
}

如果您部分了解XML文档的结构,则可以使用这种技术。

算法 #2: 一个不太冗长的方式

这里是另一个更简单的转换方法,其中nodeAttributes列在子节点的同一对象下,但带有“@”前缀(由BadgerFish Convention提出)。如上所述,文本内容存储在keyValue属性中。构造函数的参数可以是整个XML Document,一个DocumentFragment或简单的Element节点。

/*\
|*|
|*|  JXON Snippet #2 - Mozilla Developer Network
|*|
|*|  https://developer.mozilla.org/en-US/docs/JXON
|*|  https://developer.mozilla.org/User:fusionchess
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/

function parseText (sValue) {
  if (/^\s*$/.test(sValue)) { return null; }
  if (/^(?:true|false)$/i.test(sValue)) { return sValue.toLowerCase() === "true"; }
  if (isFinite(sValue)) { return parseFloat(sValue); }
  if (isFinite(Date.parse(sValue))) { return new Date(sValue); }
  return sValue;
}

function JXONTree (oXMLParent) {
  if (oXMLParent.hasChildNodes()) {
    var sCollectedTxt = "";
    for (var oNode, sProp, vContent, nItem = 0; nItem < oXMLParent.childNodes.length; nItem++) {
      oNode = oXMLParent.childNodes.item(nItem);
      if ((oNode.nodeType - 1 | 1) === 3) { sCollectedTxt += oNode.nodeType === 3 ? oNode.nodeValue.trim() : oNode.nodeValue; }
      else if (oNode.nodeType === 1 && !oNode.prefix) {
        sProp = oNode.nodeName.toLowerCase();
        vContent = new JXONTree(oNode);
        if (this.hasOwnProperty(sProp)) {
          if (this[sProp].constructor !== Array) { this[sProp] = [this[sProp]]; }
          this[sProp].push(vContent);
        } else { this[sProp] = vContent; }
      }
    }
    if (sCollectedTxt) { this.keyValue = parseText(sCollectedTxt); }
  }
  if (oParentNode.hasAttributes && oXMLParent.hasAttributes()) {
    var oAttrib;
    for (var nAttrib = 0; nAttrib < oXMLParent.attributes.length; nAttrib++) {
      oAttrib = oXMLParent.attributes.item(nAttrib);
      this["@" + oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
    }
  }
  /* Object.freeze(this); */
}

var myObject = new JXONTree(doc);
// we got our javascript object! try: alert(JSON.stringify(myObject));
注意:如果你想冻结整个对象树(因为XML文档的“静态”性质),取消注释字符串:/* Object.freeze(this); */Object.freeze()方法防止将新属性添加到该属性中,防止现有属性被删除,并防止现有属性或其可枚举性,可配置性或可写性发生更改。本质上,对象树是有效的不可变的。

用这个算法我们的例子变成:

{
  "catalog": {
    "product": {
      "catalog_item": [{
        "item_number": {
          "keyValue": "QWZ5671"
        },
        "price": {
          "keyValue": 39.95
        },
        "size": [{
          "color_swatch": [{
            "keyValue": "Red",
            "@image": "red_cardigan.jpg"
          }, {
            "keyValue": "Burgundy",
            "@image": "burgundy_cardigan.jpg"
          }],
          "@description": "Medium"
        }, {
          "color_swatch": [{
            "keyValue": "Red",
            "@image": "red_cardigan.jpg"
          }, {
            "keyValue": "Burgundy",
            "@image": "burgundy_cardigan.jpg"
          }],
          "@description": "Large"
        }],
        "@gender": "Men's"
      }, {
        "item_number": {
          "keyValue": "RRX9856"
        },
        "discount_until": {
          "keyValue": new Date(1995, 11, 25)
        },
        "price": {
          "keyValue": 42.5
        },
        "size": {
          "color_swatch": {
            "keyValue": "Black",
            "@image": "black_cardigan.jpg"
          },
          "@description": "Medium"
        },
        "@gender": "Women's"
      }],
      "@description": "Cardigan Sweater"
    },
    "script": {
      "keyValue": "function matchwo(a,b) {\n  if (a < b && a < 0) { return 1; }\n  else { return 0; }\n}",
      "@type": "text/javascript"
    }
  }
}

如果您部分了解XML文档的结构,则可以使用这种技术。

Algorithm #3: 一种组合的技巧

这是另一种转换方法。这个算法是最接近Parker约定的。除了不包含除TextCDATASection以外的其他可识别节点的节点不被视为对象,而是直接作为布尔值,字符串,数字或Date对象(请参阅Parker约定)。空节点(即不包含其他Element节点,Text节点,CDATASection节点或Attr节点)的默认值为true(请参阅代码注意事项)。另外,这次我们使用一个函数来代替构造函数。函数的参数可以是整个XML  Document,一个DocumentFragment,或者只是一个 Element 节点。根据BadgerFish公约的建议,nodeAttributes具有“@”前缀。在很多情况下,这是最实用的转换方法。

/*\
|*|
|*|  JXON Snippet #3 - Mozilla Developer Network
|*|
|*|  https://developer.mozilla.org/en-US/docs/JXON
|*|  https://developer.mozilla.org/User:fusionchess
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/

function parseText (sValue) {
  if (/^\s*$/.test(sValue)) { return null; }
  if (/^(?:true|false)$/i.test(sValue)) { return sValue.toLowerCase() === "true"; }
  if (isFinite(sValue)) { return parseFloat(sValue); }
  if (isFinite(Date.parse(sValue))) { return new Date(sValue); }
  return sValue;
}

function getJXONTree (oXMLParent) {
  var vResult = /* put here the default value for empty nodes! */ true, nLength = 0, sCollectedTxt = "";
  if (oXMLParent.hasAttributes && oXMLParent.hasAttributes()) {
    vResult = {};
    for (nLength; nLength < oXMLParent.attributes.length; nLength++) {
      oAttrib = oXMLParent.attributes.item(nLength);
      vResult["@" + oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
    }
  }
  if (oXMLParent.hasChildNodes()) {
    for (var oNode, sProp, vContent, nItem = 0; nItem < oXMLParent.childNodes.length; nItem++) {
      oNode = oXMLParent.childNodes.item(nItem);
      if (oNode.nodeType === 4) { sCollectedTxt += oNode.nodeValue; } /* nodeType is "CDATASection" (4) */
      else if (oNode.nodeType === 3) { sCollectedTxt += oNode.nodeValue.trim(); } /* nodeType is "Text" (3) */
      else if (oNode.nodeType === 1 && !oNode.prefix) { /* nodeType is "Element" (1) */
        if (nLength === 0) { vResult = {}; }
        sProp = oNode.nodeName.toLowerCase();
        vContent = getJXONTree(oNode);
        if (vResult.hasOwnProperty(sProp)) {
          if (vResult[sProp].constructor !== Array) { vResult[sProp] = [vResult[sProp]]; }
          vResult[sProp].push(vContent);
        } else { vResult[sProp] = vContent; nLength++; }
      }
    }
  }
  if (sCollectedTxt) { nLength > 0 ? vResult.keyValue = parseText(sCollectedTxt) : vResult = parseText(sCollectedTxt); }
  /* if (nLength > 0) { Object.freeze(vResult); } */
  return vResult;
}

var myObject = getJXONTree(doc);
// we got our javascript object! try: alert(JSON.stringify(myObject));
注意:如果你想冻结整个对象树(因为XML文档的“静态”性质),取消注释字符串:/* Object.freeze(this); */Object.freeze()方法防止将新属性添加到该属性中,防止现有属性被删除,并防止现有属性或其可枚举性,可配置性或可写性发生更改。本质上,对象树是有效的不可变的。

用这个算法我们的例子变成:

{
  "catalog": {
    "product": {
      "@description": "Cardigan Sweater",
      "catalog_item": [{
        "@gender": "Men's",
        "item_number": "QWZ5671",
        "price": 39.95,
        "size": [{
          "@description": "Medium",
          "color_swatch": [{
            "@image": "red_cardigan.jpg",
            "keyValue": "Red"
          }, {
            "@image": "burgundy_cardigan.jpg",
            "keyValue": "Burgundy"
          }]
        }, {
          "@description": "Large",
          "color_swatch": [{
            "@image": "red_cardigan.jpg",
            "keyValue": "Red"
          }, {
            "@image": "burgundy_cardigan.jpg",
            "keyValue": "Burgundy"
          }]
        }]
      }, {
        "@gender": "Women's",
        "item_number": "RRX9856",
        "discount_until": new Date(1995, 11, 25),
        "price": 42.5,
        "size": {
          "@description": "Medium",
          "color_swatch": {
            "@image": "black_cardigan.jpg",
            "keyValue": "Black"
          }
        }
      }]
    },
    "script": {
      "@type": "text/javascript",
      "keyValue": "function matchwo(a,b) {\n  if (a < b && a < 0) { return 1; }\n  else { return 0; }\n}"
    }
  }
}

如果您知道XML文档的结构,这是推荐的技术。

算法 #4: 一个非常简约的方式

以下是另一种可以实现的转换方法。它也非常接近Parker约定。使用此算法,包含同一级别中的其他子元素,文本或CDATASection节点的所有元素节点都将被视为Boolean ,Number, String,或Date构造函数的实例。因此,任何子元素节点(如果存在)将嵌套在这些类型的对象中。

For example:

<employee type="usher">John Smith</employee>
<manager>Lisa Carlucci</manager>

becomes

var myObject = {
  "employee": new String("John Smith"),
  "manager": "Lisa Carlucci"
};

myObject.employee["@type"] = "usher";

// test

alert(myObject.manager); // "Lisa Carlucci"
alert(myObject.employee["@type"]); // "usher"
alert(myObject.employee); // "John Smith"
注意:这个算法代表了转换的特殊情况。生成的JavaScript对象树不可字符串化(请参阅Code considerations)。内部JavaScript访问非常实用,但如果要通过JSON字符串传输树,请不要使用它!

对于第三种算法,不包含除Text或CDATASection之外的其他可识别节点的节点不被视为对象,而是直接作为Boolean ,Number(原始值), String,或Date对象;而空节点(即,不包含其他Element节点,Text节点,CDATASection节点或Attr节点)具有默认值true。至于第三个算法,它不是使用构造函数,而是一个函数。该函数的参数可以是整个XML文档,一个DocumentFragment或简单的Element节点。根据BadgerFish公约的建议,nodeAttributes具有“@”前缀。

/*\
|*|
|*|  JXON Snippet #4 - Mozilla Developer Network
|*|
|*|  https://developer.mozilla.org/en-US/docs/JXON
|*|  https://developer.mozilla.org/User:fusionchess
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/

function parseText (sValue) {
  if (/^\s*$/.test(sValue)) { return null; }
  if (/^(?:true|false)$/i.test(sValue)) { return sValue.toLowerCase() === "true"; }
  if (isFinite(sValue)) { return parseFloat(sValue); }
  if (isFinite(Date.parse(sValue))) { return new Date(sValue); }
  return sValue;
}

function objectify (vValue) {
  if (vValue === null) {
    return new (function() {
      this.toString = function() { return "null"; }
      this.valueOf = function() { return null; }
    })();
  }
  return vValue instanceof Object ? vValue : new vValue.constructor(vValue);
}

var aTmpEls = []; // loaded element nodes cache

function getJXONTree (oXMLParent) {
  var  sProp, vContent, vResult, nLength = 0, nLevelStart = aTmpEls.length,
      nChildren = oXMLParent.hasChildNodes() ? oXMLParent.childNodes.length : 0, sCollectedTxt = "";

  for (var oNode, nItem = 0; nItem < nChildren; nItem++) {
    oNode = oXMLParent.childNodes.item(nItem);
    if (oNode.nodeType === 4) { sCollectedTxt += oNode.nodeValue; } /* nodeType is "CDATASection" (4) */
    else if (oNode.nodeType === 3) { sCollectedTxt += oNode.nodeValue.trim(); } /* nodeType is "Text" (3) */
    else if (oNode.nodeType === 1 && !oNode.prefix) { aTmpEls.push(oNode); } /* nodeType is "Element" (1) */
  }

  var nLevelEnd = aTmpEls.length, vBuiltVal = parseText(sCollectedTxt);

  if (oParentNode.hasAttributes && oXMLParent.hasAttributes()) {
    vResult = objectify(vBuiltVal);
    for (nLength; nLength < oXMLParent.attributes.length; nLength++) {
      oAttrib = oXMLParent.attributes.item(nLength);
      vResult["@" + oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
    }
  } else if (nLevelEnd > nLevelStart) { vResult = objectify(vBuiltVal); }

  for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) {
    sProp = aTmpEls[nElId].nodeName.toLowerCase();
    vContent = getJXONTree(aTmpEls[nElId]);
    if (vResult.hasOwnProperty(sProp)) {
    if (vResult[sProp].constructor !== Array) { vResult[sProp] = [vResult[sProp]]; }
      vResult[sProp].push(vContent);
    } else { vResult[sProp] = vContent; nLength++; }
  }

  aTmpEls.length = nLevelStart;

  if (nLength === 0) { vResult = sCollectedTxt ? vBuiltVal : /* put here the default value for empty nodes: */ true; }
  /* else { Object.freeze(vResult); } */

  return vResult;
}

var myObject = getJXONTree(doc);
alert(myObject.catalog.product.catalog_item[1].size.color_swatch["@image"]); // "black_cardigan.jpg"
alert(myObject.catalog.product.catalog_item[1].size.color_swatch); // "Black" !

 

注意:如果你想冻结整个对象树(因为XML文档的“静态”性质),取消注释字符串:/* Object.freeze(this); */Object.freeze()方法防止将新属性添加到该属性中,防止现有属性被删除,并防止现有属性或其可枚举性,可配置性或可写性发生更改。本质上,对象树是有效的不可变的。

如果您知道XML文档的结构,这是一种可能的技术。

 

反向算法

为了从JavaScript对象树开始构建一个新的XML文档,可以将这里提出的算法颠倒过来。为了简单,我们将在这里提出一个例子,在一个单一的方法,代表了所有我们的算法的反演。

/*\
|*|
|*|  JXON Snippet #5 - Mozilla Developer Network
|*|
|*|  https://developer.mozilla.org/en-US/docs/JXON
|*|  https://developer.mozilla.org/User:fusionchess
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/

function createXML (oObjTree) {
  function loadObjTree (oParentEl, oParentObj) {
    var vValue, oChild;
    if (oParentObj.constructor === String || oParentObj.constructor === Number || oParentObj.constructor === Boolean) {
      oParentEl.appendChild(oNewDoc.createTextNode(oParentObj.toString())); /* verbosity level is 0 or 1 */
      if (oParentObj === oParentObj.valueOf()) { return; }
    } else if (oParentObj.constructor === Date) {
      oParentEl.appendChild(oNewDoc.createTextNode(oParentObj.toGMTString()));
    }
    for (var sName in oParentObj) {
      if (isFinite(sName)) { continue; } /* verbosity level is 0 */
      vValue = oParentObj[sName];
      if (sName === "keyValue") {
        if (vValue !== null && vValue !== true) { oParentEl.appendChild(oNewDoc.createTextNode(vValue.constructor === Date ? vValue.toGMTString() : String(vValue))); }
      } else if (sName === "keyAttributes") { /* verbosity level is 3 */
        for (var sAttrib in vValue) { oParentEl.setAttribute(sAttrib, vValue[sAttrib]); }
      } else if (sName.charAt(0) === "@") {
        oParentEl.setAttribute(sName.slice(1), vValue);
      } else if (vValue.constructor === Array) {
        for (var nItem = 0; nItem < vValue.length; nItem++) {
          oChild = oNewDoc.createElement(sName);
          loadObjTree(oChild, vValue[nItem]);
          oParentEl.appendChild(oChild);
        }
      } else {
        oChild = oNewDoc.createElement(sName);
        if (vValue instanceof Object) {
          loadObjTree(oChild, vValue);
        } else if (vValue !== null && vValue !== true) {
          oChild.appendChild(oNewDoc.createTextNode(vValue.toString()));
        }
        oParentEl.appendChild(oChild);
      }
    }
  }
  const oNewDoc = document.implementation.createDocument("", "", null);
  loadObjTree(oNewDoc, oObjTree);
  return oNewDoc;
}

var newDoc = createXML(myObject);
// we got our Document instance! try: alert((new XMLSerializer()).serializeToString(newDoc));
注意:通过这个代码,Date实例(如果存在的话)通过toGMTString() 方法转换为 Strings 。没有什么禁止使用任何其他转换方法。此外,具有true值的树的所有属性都将被转换为没有文本节点的空元素(请参阅 Code considerations)。

如果要自动创建XML文档,这是一个很好的解决方案。但是,如果要重新构建以前转换为JSON的XML文档,这是一个不错的选择。虽然双向转换是非常忠实的(除了CDATASection节点,它们将被转换成文本节点),但这个过程是不必要的成本。事实上,如果您的目标是编辑XML文档,强烈建议您使用它而不是创建一个新的。

The Parker Convention

上面列出的用于将XML文档转换为JSON(通常称为“JXON算法”)的功能或多或少地基于Parker公约(尤其是关于将标签名称转换为对象属性名称,识别所有收集到的每个标签的文本内容以及单独的Text和/或CDATASection节点吸收为原始值)。It is called “Parker Convention” in opposition to “BadgerFish Convention”, after the comic Parker & Badger by Cuadrado. See also: BadgerFish Convention.

以下是来自 xml2json-xslt项目网站的“TransformingRules”页面的Parker Convention文章(版本0.4)的转录。

本公约是为了规范从XSLTJSON 的转换而编写的,所以它的一部分对于JavaScript来说是徒劳的。

注意:2013年10月29日,万维网联盟(World Wide Web Consortium)在一篇关于将HTML5微数据转换为JSON的官方算法的备注中转载。但是,HTML微数据不是HTML:微数据是HTML的格式化子集。

Translation JSON

  1. The root element will be absorbed, for there is only one:

    <root>test</root>

    becomes

    "test"
    
  2. Element names become object properties:

    <root><name>Xml</name><encoding>ASCII</encoding></root>

    becomes

    {
      "name": "Xml",
      "encoding": "ASCII"
    }
    
  3. Numbers are recognized (integers and decimals):

    <root><age>12</age><height>1.73</height></root>

    becomes

    {
      "age": 12,
      "height": 1.73
    }
    
  4. Booleans are recognized case insensitive:

    <root><checked>True</checked><answer>FALSE</answer></root>

    becomes

    {
      "checked": true,
      "answer": false
    }
    
  5. Strings are escaped:

    <root>Quote: &quot; New-line:
    </root>
    

    becomes

    "Quote: \" New-line:\n"
  6. Empty elements will become null:

    <root><nil/><empty></empty></root>

    becomes

    {
      "nil": null,
      "empty": null
    }
    
  7. If all sibling elements have the same name, they become an array

    <root><item>1</item><item>2</item><item>three</item></root>
    

    becomes

    [1, 2, "three"]
    
  8. Mixed mode text-nodes, comments and attributes get absorbed:

    <root version="1.0">testing<!--comment--><element test="true">1</element></root>
    

    becomes

    { "element": true }
    
  9. Namespaces get absorbed, and prefixes will just be part of the property name:

    <root xmlns:ding="http://zanstra.com/ding"><ding:dong>binnen</ding:dong></root>
    

    becomes

    { "ding:dong" : "binnen" }
    
注意:我们的算法符合第2,3,4和7点。第三和第四个算法也符合第6点(但是true而不是null - 请参阅Code considerations)。第5点由JavaScript方法JSON.stringify()自动管理。关于第9点,我们选择忽略所有有前缀的节点;你可以通过从我们的算法中删除字符串&& !oNode.prefix来包含它们(参见 Code considerations))。

额外的JavaScript转换

This is the same as the JSON translation, but with these extras:

  1. Property names are only escaped when necessary

    <root><while>true</while><wend>false</wend><only-if/></root>
    

    becomes

    {
      "while": true,
      wend: false,
      "only-if": null
    }
    
  2. Within a string, closing elements "</" are escaped as "<\/"

    <root><![CDATA[<script>alert("YES");</script>]]></root>

    becomes

    { script: "<script>alert(\"YES\")<\/script>" }
    
  3. Dates are created as new Date objects

    <root>2006-12-25</root>

    becomes

    new Date(2006, 12 - 1, 25)
    
  4. Attributes and comments are shown as comments (for testing purposes):

    <!--testing--><root><test version="1.0">123</test></root>
    

    becomes

    /* testing */ { test /* @version = "1.0" */ : 123}
    
  5. A bit of indentation is done, to keep things legible

注意:我们的算法符合第3点(但没有减少月份)。点1和2自动由JavaScript方法 JSON.stringify()进行管理。

In summary

我们以第三种算法作为最具代表性的JXON解析算法。单个结构化XML元素可能有八种不同的配置:

  1. an empty element,
  2. an element with pure text content,
  3. an empty element with attributes,
  4. an element with text content and attributes,
  5. an element containing elements with different names,
  6. an element containing elements with identical names,
  7. an element containing elements and contiguous text,
  8. an element containing elements and non contiguous text.

The following table shows the corresponding conversion patterns between XML and JSON according to the third algorithm.

Case XML JSON Javascript access
1 <animal /> "animal": true myObject.animal
2 <animal>Deka</animal> "animal": "Deka" myObject.animal
3 <animal name="Deka" /> "animal": {"@name": "Deka"} myObject.animal["@name"]
4 <animal name="Deka">is my cat</animal> "animal": { "@name": "Deka", "keyValue": "is my cat" } myObject.animal["@name"], myObject.animal.keyValue
5 <animal> <dog>Charlie</dog> <cat>Deka</cat> </animal> "animal": { "dog": "Charlie", "cat": "Deka" } myObject.animal.dog, myObject.animal.cat
6 <animal> <dog>Charlie</dog> <dog>Mad Max</dog> </animal> "animal": { "dog": ["Charlie", "Mad Max"] } myObject.animal.dog[0], myObject.animal.dog[1]
7 <animal> in my house <dog>Charlie</dog> </animal> "animal": { "keyValue": "in my house", "dog": "Charlie" } myObject.animal.keyValue, myObject.animal.dog
8 <animal> in my ho <dog>Charlie</dog> use </animal> "animal": { "keyValue": "in my house", "dog": "Charlie" } myObject.animal.keyValue, myObject.animal.dog

Code considerations

In these examples we chose to use a property named keyValue for the text content. The lack of standards for XML to JSON conversion leads developers to choose a variety of property names for the text content of XML Element nodes that also contain other child nodes. Sometimes a property called $ is used. Other times a property called #text is used (however, a name like this isn't a good choice, since the text content of a node can be parsed into a non-string value by our algorithms during the conversion). In the algorithms proposed here, you can easily change this name, depending on your needs.

The choice of using a true value instead of a null value to represent empty nodes is due to the fact that when in an XML document there is an empty node the reason is often to express a Boolean, as in this case:

<car>
  <type>Ferrari</type>
  <bought />
</car>

If the value were null it would be more cumbersome to launch a code like this:

if (myObject.car.bought) {
  // do something
}
Note: According to our third algorithm and our fourth algorithm, just CDATASection nodes which contain nothing but white spaces (precisely: /^\s+$/) will be parsed as null.

The fourth algorithm represents a special case of conversion. As you can see, the generated JavaScript Object tree is not stringifyable. It is very practical for internal JavaScript access, but don't use it if you want to transfer the tree via JSON string (as for Worker messages, for example).

We chose to ignore nodes which have a prefix (for example: <ding:dong>binnen</ding:dong>), due to their special case (they are often used in order to represents an XML Schema, which is meta-information concerning how to organize the information of the document, reserved for the XML parser). You can include them removing the string && !oNode.prefix from our algorithms (by doing so the whole tag will become the property name: { "ding:dong": "binnen" }).

An important consideration is that, when using the third or the fourth algorithm, an XML Document can be used to create any type of JavaScript object. For example, If you want to create an object like the following:

{
  "myboolean": true,
  "myarray": ["Cinema", "Hot dogs", false],
  "myobject": {
    "nickname": "Jack",
    "registration_date": new Date(1995, 11, 25),
    "privileged_user": true
  },
  "mynumber": 99,
  "mytext": "Hello World!"
}

you must just create an XML document with the following structure:

<myboolean>true</myboolean>
<myarray>Cinema</myarray>
<myarray>Hot dogs</myarray>
<myarray>false</myarray>
<myobject>
  <nickname>Jack</nickname>
  <registration_date>Dec 25, 1995</registration_date>
  <privileged_user />
</myobject>
<mynumber>99</mynumber>
<mytext>Hello World!</mytext>

This example also shows how the ideal JXON document is an XML document designed specifically to be converted in JSON format, though our algorithms work fine with any kind of XML document.

Note: Despite the term JXON suggesting "lossless" conversions, these techniques are not actually lossless if one needs to preserve ordering of elements, as is common with many XML dialects (including of course XHTML). The ECMAScript standard (JavaScript) indicates that object iteration order is implementation dependent.

Appendix: a complete, bidirectional, JXON library

Now we can create a more complete, bidirectional, JXON library based on all our algorithms (see: #1, #2, #3, #4, reverse). Its usage is modeled on the JSON native object. Before implementing it in a working environment, please read the note about the const statement compatibility. The following code is also available on GitHub.

"use strict";

/*\
|*|
|*|  JXON framework - Copyleft 2011 by Mozilla Developer Network
|*|
|*|  Revision #3 - October 31th, 2016
|*|
|*|  https://developer.mozilla.org/en-US/docs/JXON
|*|  https://developer.mozilla.org/User:fusionchess
|*|  https://github.com/madmurphy/jxon.js
|*|
|*|  This framework is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/

const JXON = new (function () {

  function parseText (sValue) {
    if (rIsNull.test(sValue)) { return null; }
    if (rIsBool.test(sValue)) { return sValue.toLowerCase() === "true"; }
    if (isFinite(sValue)) { return parseFloat(sValue); }
    if (isFinite(Date.parse(sValue))) { return new Date(sValue); }
    return sValue;
  }

  function EmptyTree () {}

  EmptyTree.prototype.toString = function () { return "null"; };

  EmptyTree.prototype.valueOf = function () { return null; };

  function objectify (vVal) {
    return vVal === null ? new EmptyTree() : vVal instanceof Object ? vVal : new vVal.constructor(vVal);
  }

  function createObjTree (oParentNode, nVerb, bFreeze, bNesteAttr) {

    const
      nLevelStart = aCache.length, bChildren = oParentNode.hasChildNodes(),
      bAttributes = oParentNode.hasAttributes && oParentNode.hasAttributes(), bHighVerb = Boolean(nVerb & 2);

    var
      sProp, vContent, nLength = 0, sCollectedTxt = "",
      vResult = bHighVerb ? {} : /* put here the default value for empty nodes: */ true;

    if (bChildren) {
      for (var oNode, nItem = 0; nItem < oParentNode.childNodes.length; nItem++) {
        oNode = oParentNode.childNodes.item(nItem);
        if (oNode.nodeType === 4) { sCollectedTxt += oNode.nodeValue; } /* nodeType is "CDATASection" (4) */
        else if (oNode.nodeType === 3) { sCollectedTxt += oNode.nodeValue.trim(); } /* nodeType is "Text" (3) */
        else if (oNode.nodeType === 1 && !oNode.prefix) { aCache.push(oNode); } /* nodeType is "Element" (1) */
      }
    }

    const nLevelEnd = aCache.length, vBuiltVal = parseText(sCollectedTxt);

    if (!bHighVerb && (bChildren || bAttributes)) { vResult = nVerb === 0 ? objectify(vBuiltVal) : {}; }

    for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) {
      sProp = aCache[nElId].nodeName.toLowerCase();
      vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr);
      if (vResult.hasOwnProperty(sProp)) {
        if (vResult[sProp].constructor !== Array) { vResult[sProp] = [vResult[sProp]]; }
        vResult[sProp].push(vContent);
      } else {
        vResult[sProp] = vContent;
        nLength++;
      }
    }

    if (bAttributes) {

      const
        nAttrLen = oParentNode.attributes.length,
        sAPrefix = bNesteAttr ? "" : sAttrsPref, oAttrParent = bNesteAttr ? {} : vResult;

      for (var oAttrib, nAttrib = 0; nAttrib < nAttrLen; nLength++, nAttrib++) {
        oAttrib = oParentNode.attributes.item(nAttrib);
        oAttrParent[sAPrefix + oAttrib.name.toLowerCase()] = parseText(oAttrib.value.trim());
      }

      if (bNesteAttr) {
        if (bFreeze) { Object.freeze(oAttrParent); }
        vResult[sAttrProp] = oAttrParent;
        nLength -= nAttrLen - 1;
      }

    }

    if (nVerb === 3 || (nVerb === 2 || nVerb === 1 && nLength > 0) && sCollectedTxt) {
      vResult[sValProp] = vBuiltVal;
    } else if (!bHighVerb && nLength === 0 && sCollectedTxt) {
      vResult = vBuiltVal;
    }

    if (bFreeze && (bHighVerb || nLength > 0)) { Object.freeze(vResult); }

    aCache.length = nLevelStart;

    return vResult;

  }

  function loadObjTree (oXMLDoc, oParentEl, oParentObj) {

    var vValue, oChild;

    if (oParentObj.constructor === String || oParentObj.constructor === Number || oParentObj.constructor === Boolean) {
      oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toString())); /* verbosity level is 0 or 1 */
      if (oParentObj === oParentObj.valueOf()) { return; }
    } else if (oParentObj.constructor === Date) {
      oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toGMTString()));
    }

    for (var sName in oParentObj) {
      vValue = oParentObj[sName];
      if (isFinite(sName) || vValue instanceof Function) { continue; } /* verbosity level is 0 */
      if (sName === sValProp) {
        if (vValue !== null && vValue !== true) { oParentEl.appendChild(oXMLDoc.createTextNode(vValue.constructor === Date ? vValue.toGMTString() : String(vValue))); }
      } else if (sName === sAttrProp) { /* verbosity level is 3 */
        for (var sAttrib in vValue) { oParentEl.setAttribute(sAttrib, vValue[sAttrib]); }
      } else if (sName.charAt(0) === sAttrsPref) {
        oParentEl.setAttribute(sName.slice(1), vValue);
      } else if (vValue.constructor === Array) {
        for (var nItem = 0; nItem < vValue.length; nItem++) {
          oChild = oXMLDoc.createElement(sName);
          loadObjTree(oXMLDoc, oChild, vValue[nItem]);
          oParentEl.appendChild(oChild);
        }
      } else {
        oChild = oXMLDoc.createElement(sName);
        if (vValue instanceof Object) {
          loadObjTree(oXMLDoc, oChild, vValue);
        } else if (vValue !== null && vValue !== true) {
          oChild.appendChild(oXMLDoc.createTextNode(vValue.toString()));
        }
        oParentEl.appendChild(oChild);
      }
    }

  }

  /* Uncomment the following code if you want to enable the .appendJXON() method for *all* the "element" objects! */

  /*

  Element.prototype.appendJXON = function (oObjTree) {
    loadObjTree(document, this, oObjTree);
    return this;
  };

  */

  this.build = function (oXMLParent, nVerbosity /* optional */, bFreeze /* optional */, bNesteAttributes /* optional */) {
    const nVerbMask = arguments.length > 1 && typeof nVerbosity === "number" ? nVerbosity & 3 : /* put here the default verbosity level: */ 1;
    return createObjTree(oXMLParent, nVerbMask, bFreeze || false, arguments.length > 3 ? bNesteAttributes : nVerbMask === 3);
  };

  this.unbuild = function (oObjTree, sNamespaceURI /* optional */, sQualifiedName /* optional */, oDocumentType /* optional */) {
    const oNewDoc = document.implementation.createDocument(sNamespaceURI || null, sQualifiedName || "", oDocumentType || null);
    loadObjTree(oNewDoc, oNewDoc, oObjTree);
    return oNewDoc;
  };

  const
    sValProp = "keyValue", sAttrProp = "keyAttributes", sAttrsPref = "@", /* you can customize these values */
    aCache = [], rIsNull = /^\s*$/, rIsBool = /^(?:true|false)$/i;

})();
Note: The current implementation of const (constant statement) is not part of ECMAScript 5. It is supported in Firefox & Chrome (V8) and partially supported in Opera 9+ and Safari. It is not supported in Internet Explorer 6-9, or in the preview of Internet Explorer 10. const is going to be defined by ECMAScript 6, but with different semantics. Similar to variables declared with the let statement, constants declared with const will be block-scoped. We used it only for didactic purpose. If you want a full browser compatibility of this library, please replace all the const statements with the var statements.

Usage

The obtained non-native JXON global object will have two methods:

Method Description
JXON.build(document[, verbosity[, freeze[, nesteAttributes]]]) Returns a JavaScript Object based on the given XML Document.
JXON.unbuild(objTree[, namespaceURI[, qualifiedNameStr[, documentType]]]) Returns an XML Document based on the given JavaScript Object.

These methods are inverses of each other. So, you can work with the JXON object by inserting the previous code at the beginning of your scripts. If you are not interested in a bidirectional conversion, don't use it, use only one of our algotithm instead.

Sample usage:

var myObject = JXON.build(doc);
// we got our javascript object! try: alert(JSON.stringify(myObject));

var newDoc = JXON.unbuild(myObject);
// we got our Document instance! try: alert((new XMLSerializer()).serializeToString(newDoc));

…the same thing using AJAX:

function reqListener () {

    var myObject = JXON.build(this.responseXML);
    // we got our javascript object!
    alert(JSON.stringify(myObject));

    var newDoc = JXON.unbuild(myObject);
    // we got our Document instance!
    alert((new XMLSerializer()).serializeToString(newDoc));

};

var oReq = new XMLHttpRequest();
oReq.onload = reqListener;
oReq.open("get", "example.xml", true);
oReq.send();

JXON.build syntax

JXON.build(document[, verbosity[, freeze[, nesteAttributes]]])

JXON.build description

Returns a JavaScript Object based on the given XML Document.

JXON.build parameters

document
The XML document to be converted into JSON format.
verbosity Optional
The verbosity level of conversion (optional), from 0 to 3. It is almost equivalent to our algorithms from #4 to #1 (default value is 1, which is equivalent to the algorithm #3).
freeze Optional
A boolean (optional) expressing whether the created object must be freezed or not (default value is false).
nesteAttributes Optional
A boolean (optional) expressing whether the the nodeAttributes must be nested into a child-object named keyAttributes or not (default value is false for verbosity levels from 0 to 2; true for verbosity level 3).

JXON.unbuild syntax

JXON.unbuild(objTree[, namespaceURI[, qualifiedNameStr[, documentType]]])

JXON.unbuild description

Returns an XML Document based on the given JavaScript Object.

JXON.unbuild parameters

objTree
The JavaScript Object from which you want to create your XML Document.
namespaceURI Optional
Is a DOMString containing the namespace URI of the document to be created, or null if the document doesn't belong to one.
qualifiedNameStr Optional
Is a DOMString containing the qualified name, that is an optional prefix and colon plus the local root element name, of the document to be created.
documentType Optional
Is the DocumentType of the document to be created. It defaults to null.

Extend the native Element.prototype object

If you want to enable the .appendJXON() method for all the native element objects, you can uncomment the following code from the JXON library:

  /* Uncomment the following code if you want to enable the .appendJXON() method for *all* the "element" objects! */

  /*

  Element.prototype.appendJXON = function (oObjTree) {
    loadObjTree(document, this, oObjTree);
    return this;
  };

  */

Example

Imagine you want to populate the following HTMLElement through JSON:

<div id="form_container"></div>

Then, the following code:

document.getElementById("form_container").appendJXON({
  "form": {
    "script": {
      "@type": "text/javascript",
      "keyValue": "\n  function numbersOnly (oToCheckField, oKeyEvent) {\n  return oKeyEvent.charCode === 0 || /\\d/.test(String.fromCharCode(oKeyEvent.charCode));\n  }\n"
    },
    "input": [{
      "@type": "hidden",
      "@name": "instId",
      "@value": 1234
    }, {
      "@type": "hidden",
      "@name": "currency",
      "@value": "GBP"
    }, {
      "@type": "hidden",
      "@name": "amount",
      "@value": 0
    }, {
      "@type": "hidden",
      "@name": "name",
      "@value": "CAPTURED"
    }],
    "table": {
      "tr": [{
        "th": {
          "@style": "text-align: right;",
          "keyValue": "Product:"
        },
        "td": {
          "span": [{
            "input": {
              "@type": "radio",
              "@name": "nome",
              "@id": "rel_tshirt",
              "@value": "tshirt"
            },
            "label": {
              "@for": "rel_tshirt",
              "keyValue": "T-Shirt"
            },
            "@class": "product"
          }, {
            "input": {
              "@type": "radio",
              "@name": "nome",
              "@id": "rel_trousers",
              "@value": "trousers"
            },
            "label": {
              "@for": "rel_trousers",
              "keyValue": "Trousers"
            },
            "@class": "product"
          }, {
            "input": {
              "@type": "radio",
              "@name": "nome",
              "@id": "rel_pullover",
              "@value": "pullover"
            },
            "label": {
              "@for": "rel_pullover",
              "keyValue": "Pullover"
            },
            "@class": "product"
          }]
        }
      }, {
        "th": {
          "@style": "text-align: right;",
          "keyValue": "Quantity:"
        },
        "td": {
          "input": {
            "@type": "text",
            "@name": "myInput",
            "@onkeypress": "return numbersOnly(this, event);",
            "@onpaste": "return false;"
          }
        }
      }]
    },
    "p": {
      "input": {
        "@type": "submit",
        "@value": "Purchase!"
      }
    },
    "@action": "https://secure-test.worldpay.com/wcc/purchase",
    "@name": "BuyForm",
    "@method": "POST"
  }
});

will populate the previous element in the following way:

<div id="form_container">
  <form action="https://secure-test.worldpay.com/wcc/purchase" name="BuyForm" method="POST">
    <script type="text/javascript">
      function numbersOnly(oToCheckField, oKeyEvent) {
        return oKeyEvent.charCode === 0 || /\d/.test(String.fromCharCode(oKeyEvent.charCode));
      }
    </script>
    <input type="hidden" name="instId" value="1234" />
    <input type="hidden" name="currency" value="GBP" />
    <input type="hidden" name="amount" value="0" />
    <input type="hidden" name="name" value="CAPTURED" />
    <table>
      <tr>
        <th style="text-align: right;">Product:</th>
        <td><span class="product"><input type="radio" name="nome" id="rel_tshirt" value="tshirt"/><label for="rel_tshirt">T-Shirt</label></span><span class="product"><input type="radio" name="nome" id="rel_trousers" value="trousers"/><label for="rel_trousers">Trousers</label></span><span class="product"><input type="radio" name="nome" id="rel_pullover" value="pullover"/><label for="rel_pullover">Pullover</label></span>
        </td>
      </tr>
      <tr>
        <th style="text-align: right;">Quantity:</th>
        <td>
          <input type="text" name="myInput" onkeypress="return numbersOnly(this, event);" onpaste="return false;" />
        </td>
      </tr>
    </table>
    <p>
      <input type="submit" value="Purchase!" />
    </p>
  </form>
</div>

Other examples

Example #1: How to use JXON to create an HTML document instead of an XML document:

/* The structure of my document */
var oMyHTMLStruct = {
  "html": {
    "head": {
      "meta": {
        "@http-equiv": "Content-Type",
        "@content": "text/html; charset=UTF-8"
      },
      "title": "My HTML Document",
      "script": {
        "@type": "text/javascript",
        "keyValue": "alert(\"Welcome!\");"
      },
      "style": "p:first-letter {\n  font: italic bold 30px Georgia, serif;\n}"
    },
    "body": {
      "h1": "My HTML Document",
      "p": "Hello world!!"
    }
  }
};

/* Create the document */
var oMyHTMLDoc = JXON.unbuild(oMyHTMLStruct, "http://www.w3.org/1999/xhtml");

…And here is the output of alert((new XMLSerializer()).serializeToString(oMyHTMLDoc)):

<html>

<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <title>My HTML Document</title>
  <script type="text/javascript">
    alert("Welcome!");
  </script>
  <style>
    p:first-letter {
      font: italic bold 30px Georgia, serif;
    }
  </style>
</head>

<body>
  <h1>My HTML Document</h1>
  <p>Hello world!!</p>
</body>

</html>
Note: As we already said in the note within Code considerations, despite the bidirectional conversion between XML and JSON is lossless regarding the whole content and the structure of an XML document, it is not lossless regarding the ordering of elements, which for some XML dialects (like XHTML) is part of the information. For instance, a bidirectional conversion of the following HTML paragraph:
<p>She <strong>loves</strong> you. And definitely <strong>hates</strong> me.</p>
would determine a result like the following:
<p><strong>loves</strong><strong>hates</strong>Sheyou. And definitelyme.</p>
As you can see in this special case, the whole information is preserved, the ordering of the elements is not.
It turns out then that for some XML dialects JXON can be not the best choise, while it can be a really powerful tool in dealing with standard XML. One conversion method which is lossless for element order, as it relies on arrays (but, with a less human-readable, JavaScript-friendly syntax), is JsonML.

About this library

The JXON.build() method summarizes all our four ways of conversion (see: #1, #2, #3, #4). The result is therefore the same of our four algorithms, depending on the level of verbosity utilised. As above, optional properties and methods (commented in the example) of the first algorithm (verbosity level: 3) are not included.

The JXON.unbuild() method utilises our reverse algorithm.

Therefore, all code considerations remain the same.

Resources

See also