Titanium は、JavaScript でコードがかけると言っても色々と制約があり、eval() を使うようなライブラリは使用できません。

2013/02/28追記

dom2json に渡すオブジェクトを dom.documentElement に変更しました。dom を直接渡すと、Android でエラーが発生することがあります。

2011/01/18追記

Titanium.include(“TiDOMParser.js”) だと実機のほうでエラーが発生するという報告を頂いたので、TiDomParser に変更しました。合わせて、プログラム中の TiDOMParser を TiDomParser に変更しました。ご報告ありがとうございました。

2010/09/15追記

eval() が使えないと書きましたが、使えることが判明しました。。。KitchenSink のサンプルにもありました。。 僕の環境で動かないと思っていたのは、eval(“1+1”); と試していたからで、eval() の中はコードになるので正確にはセミコロンが必要で、eval(“1+1;”); としないとダメでした。。訂正です。。

XML を JSON にするライブラリに、XML to JSON というライブラリがありますが、内部で eval を使っているため動きません。(中身を書き換えれば動きそうですね。。)

そこで、DOM から JSON に変換する XML.ObjTree というライブラリを元に、Titanium 上で XML を JSON に変換するライブラリを作ってみました。

ライセンスとか気にしたつもりですが、ObjTree のライセンスに引っかかるとかあればご連絡ください。すぐに対応します。

Titanium で XML を JSON に変換する

Titanium 上で XML を JSON に変換するには次のような手順を踏みます。

  1. XML 文字列を Titanium.XML.parseString() を使って DOM に変換する
  2. DOM を本ライブラリを使ってパースし、JSON に変換する

もともと、XML.ObjTree というライブラリを使う予定でしたが、Titanium.XML.parseString() で得られるオブジェクトは純粋な DOM ではなく、TiDOMDocument というオブジェクトのため ObjTree をそのままでは使えませんでした。

そこで、一部修正して、内部で TiDOMDocument を処理できるようにしてみました。使い方は次の通り。

Titanium.include("TiDomParser.js");
var parser = new com.hamasyou.XML.TiDomParser();
var dom = Titanium.XML.parseString(xml);
var json = parser.dom2Json(dom.documentElement);

バグやおかしな点があれば、コメントください。

Download

ソースコード全文

TiDomParser.js

/**
 * @author hamasyou
 * Copyright (C) 2010 hamasyou, INC. All Rights Reserved.
 *
 * @License:
 * This program is free software; you can redistribute it
 * and/or modify it under the Artistic license.
 * Or whatever license I choose, which I will do instead of
 * keeping this documentation like it is.
 *
 * @See http://www.kawa.net/works/js/xml/objtree.html - ObjTree.js
 */
var com = {};
if (typeof(com.hamasyou) == "undefined") com.hamasyou = {};
if (typeof(com.hamasyou.XML) == "undefined") com.hamasyou.XML = {};
(function() {
  var Node = {
    ELEMENT_NODE: 1,
    ATTRIBUTE_NODE: 2,
    TEXT_NODE: 3,
    CDATA_SECTION_NODE: 4,
    ENTITY_REFERENCE_NODE: 5,
    ENTITY_NODE: 6,
    PROCESSING_INSTRUCTION_NODE: 7,
    COMMENT_NODE: 8,
    DOCUMENT_NODE: 9,
    DOCUMENT_TYPE_NODE: 10,
    DOCUMENT_FRAGMENT_NODE: 11,
    NOTATION_NODE: 12
  };

  var ns = com.hamasyou.XML;
  ns.TiDomParser = function() {
    this.initialize.apply(this, arguments);
  };
  ns.TiDomParser.prototype = {
    initialize: function() {
    },

    dom2Json: function(root) {
      this.__force_array = {};
      if (this.force_array) {
        for (var i = 0; i < this.force_array.length; i++) {
          this.__force_array[this.force_array[i]] = 1;
        }
      }

      var json = this._parseElement(root); // parse root node
      if (this.__force_array[root.nodeName]) {
        json = [json];
      }
      if (root.nodeType != Node.DOCUMENT_FRAGMENT_NODE) {
        var tmp = {};
        tmp[root.nodeName] = json; // root nodeName
        json = tmp;
      }
      return json;
    },

    _parseElement: function(elem) {
      if (elem.nodeType == Node.COMMENT_NODE) {
        return;
      }

      if (elem.nodeType == Node.TEXT_NODE || elem.nodeType == Node.CDATA_SECTION_NODE) {
        var bool = elem.text.match(/[^\x00-\x20\u00A0]/);
        if (bool == null) return; // ignore white spaces
        return elem.text;
      }

      var retval;
      var cnt = {};
      //  parse attributes
      if (elem.attributes && elem.attributes.length) {
        retval = {};
        for (var i = 0, len = elem.attributes.length; i < len; i++) {
          var key = elem.attributes.item(i).nodeName;
          if (typeof(key) != "string") continue;
          var val = elem.attributes.item(i).nodeValue;
          if (!val) continue;
          if (typeof(cnt[key]) == "undefined") cnt[key] = 0;
          cnt[key]++;
          this._addNode(retval, key, cnt[key], val);
        }
      }

      //  parse child nodes (recursive)
      if (elem.childNodes && elem.childNodes.length) {
        var textonly = true;
        if (retval) textonly = false; // some attributes exists
        for (var i = 0, len = elem.childNodes.length; i < len && textonly; i++) {
          var ntype = elem.childNodes.item(i).nodeType;
          if (ntype == Node.TEXT_NODE || ntype == Node.CDATA_SECTION_NODE) continue;
          textonly = false;
        }
        if (textonly) {
          if (!retval) retval = "";
          for (var i = 0, len = elem.childNodes.length; i < len; i++) {
            retval += elem.childNodes.item(i).text;
          }
        } else {
          if (!retval) retval = {};
          for (var i = 0, len = elem.childNodes.length; i < len; i++) {
            var key = elem.childNodes.item(i).nodeName;
            if (typeof(key) != "string") continue;
            var val = this._parseElement(elem.childNodes.item(i));
            if (!val) continue;
            if (typeof(cnt[key]) == "undefined") cnt[key] = 0;
            cnt[key]++;
            this._addNode(retval, key, cnt[key], val);
          }
        }
      }
      return retval;
    },

    _addNode: function(hash, key, cnts, val) {
      if (this.__force_array[key]) {
        if (cnts == 1) hash[key] = [];
        hash[key][hash[key].length] = val; // push
      } else if (cnts == 1) { // 1st sibling
        hash[key] = val;
      } else if (cnts == 2) { // 2nd sibling
        hash[key] = [hash[key], val];
      } else { // 3rd sibling and more
        hash[key][hash[key].length] = val;
      }
    }
  };

  ns.TiDomParser.VERSION = 0.1;
})();