/**
 * @author Ed Spencer
 * @class Ext.data.reader.Xml
 * @extends Ext.data.reader.Reader
 *
 * XMLリーダーは、XMLフォーマットで返されたサーバーレスポンスをプロキシが読み込む際に使われます。これは通常、ストアをロードした結果として発生します。たとえば、このように生成します。
 *
 *     Ext.define('User', {
 *         extend: 'Ext.data.Model',
 *         config: {
 *             fields: ['id', 'name', 'email']
 *         }
 *     });
 *
 *     var store = Ext.create('Ext.data.Store', {
 *         model: 'User',
 *         proxy: {
 *             type: 'ajax',
 *             url : 'users.xml',
 *             reader: {
 *                 type: 'xml',
 *                 record: 'user'
 *             }
 *         }
 *     });
 *
 * 上の例では、'User'モデルを生成しています。モデルに馴染みのない方のためのモデルの説明は、{@link Ext.data.Model モデル}ドキュメントに記載されています。
 *
 * {@link Ext.data.Store ストア}の{@link Ext.data.proxy.Proxy プロキシ}にXMLリーダーが必要だと伝えることにより、できるだけ単純な種類のXMLリーダーを生成します。ストアは、設定後のモデルを自動的にストアに渡すので、代わりに次を渡したのと同じことになります。
 *
 *     reader: {
 *         type : 'xml',
 *         model: 'User',
 *         record: 'user'
 *     }
 *
 * ここで設定したリーダーは、サーバーからデータを読み込める状態になっています。この時点では、次のようなレスポンスを受け取ります。
 *
 *     <?xml version="1.0" encoding="UTF-8"?>
 *     <users>
 *          <user>
 *              <id>1</id>
 *              <name>Ed Spencer</name>
 *              <email>[email protected]</email>
 *          </user>
 *          <user>
 *              <id>2</id>
 *              <name>Abe Elias</name>
 *              <email>[email protected]</email>
 *          </user>
 *      </users>
 *
 * XMLリーダーは、設定済みの{@link #record}オプションを使って各レコードのデータを読み込みます。この場合、レコードを'user'に設定するため、上記の各`<user>`がUserモデルに変換されます。
 *
 * ##他のXMLフォーマットを読み込む
 *
 * 定義済みのXMLフォーマットがあり、それが上記のものと大きく異なる場合、通常XmlReaderに設定オプションをいくつか渡すことにより、そのフォーマットをパースすることができます。たとえば、次のようなデータが返ってきた場合、{@link #rootProperty}設定を使えばパースできます。
 *
 *     <?xml version="1.0" encoding="UTF-8"?>
 *     <users>
 *         <user>
 *             <id>1</id>
 *             <name>Ed Spencer</name>
 *             <email>[email protected]</email>
 *         </user>
 *         <user>
 *             <id>2</id>
 *             <name>Abe Elias</name>
 *             <email>[email protected]</email>
 *         </user>
 *     </users>
 *
 * これをパースするには、上記の'users'と一致する{@link #rootProperty}設定で渡します。
 *
 *     reader: {
 *         type: 'xml',
 *         record: 'user',
 *         rootProperty: 'users'
 *     }
 *
 * XMLリーダーは、{@link #rootProperty}要素や{@link #record}要素が、より大きな構造の中で深くネストされているかどうかは問題にしません。したがって、次のようなレスポンスに対しても動作します。
 *
 *     <?xml version="1.0" encoding="UTF-8"?>
 *     <deeply>
 *         <nested>
 *             <xml>
 *                 <users>
 *                     <user>
 *                         <id>1</id>
 *                         <name>Ed Spencer</name>
 *                         <email>[email protected]</email>
 *                     </user>
 *                     <user>
 *                         <id>2</id>
 *                         <name>Abe Elias</name>
 *                         <email>[email protected]</email>
 *                     </user>
 *                 </users>
 *             </xml>
 *         </nested>
 *     </deeply>
 *
 * ##レスポンスメタデータ
 *
 * サーバーは、レスポンスとして、{@link #totalProperty レコードの総数}や{@link #successProperty レスポンスの成功ステータス}のような追加データを返すことができます。このデータは通常、次のようなXMLレスポンスに含まれています。
 *
 *     <?xml version="1.0" encoding="UTF-8"?>
 *     <users>
 *         <total>100</total>
 *         <success>true</success>
 *         <user>
 *             <id>1</id>
 *             <name>Ed Spencer</name>
 *             <email>[email protected]</email>
 *         </user>
 *         <user>
 *             <id>2</id>
 *             <name>Abe Elias</name>
 *             <email>[email protected]</email>
 *         </user>
 *     </users>
 *
 * XMLレスポンスの中にこのようなプロパティが存在する場合、XMLリーダーを使ってパースして、ロード元のストアで利用することができます。設定オプションの最後の組を指定すれば、これらのプロパティに名前を設定することができます。
 *
 *     reader: {
 *         type: 'xml',
 *         rootProperty: 'users',
 *         totalProperty  : 'total',
 *         successProperty: 'success'
 *     }
 *
 * この最後のオプションは、リーダーの動作には必要ありませんが、サーバーがエラーを出力するときや、利用できるデータがたくさんあり、現在返されたのはその一部だけであることを示さなければならないときには、役立つことがあります。
 *
 * ##レスポンスのフォーマット
 *
 * __注意:__返されたXMLドキュメントをブラウザがパースするには、HTTPレスポンスのContent-Typeヘッダーが「text/xml」または「application/xml」に設定されている必要があります。これは極めて重要で、さもないと、XMLリーダーは正しく動作しません。
 */
Ext.define('Ext.data.reader.Xml', {
    extend: 'Ext.data.reader.Reader',
    alternateClassName: 'Ext.data.XmlReader',
    alias: 'reader.xml',

    config: {
        /**
         * @cfg {String} record レコード情報を含む繰り返し要素へのDomQueryパスです。
         */
        record: null
    },

    /**
     * @private
     * レスポンスから特定のデータのキーを返す関数を作成します。{@link #totalProperty}および{@link #successProperty}は、特殊な型キャストとして扱われます。それ以外はすべて単純なセレクタです。
     * @param {String} expr
     * @return {Function}

     */
    createAccessor: function(expr) {
        var me = this;

        if (Ext.isEmpty(expr)) {
            return Ext.emptyFn;
        }

        if (Ext.isFunction(expr)) {
            return expr;
        }

        return function(root) {
            return me.getNodeValue(Ext.DomQuery.selectNode(expr, root));
        };
    },

    getNodeValue: function(node) {
        if (node && node.firstChild) {
            return node.firstChild.nodeValue;
        }
        return undefined;
    },

    //inherit docs
    getResponseData: function(response) {
        // Check to see if the response is already an xml node.
        if (response.nodeType === 1 || response.nodeType === 9) {
            return response;
        }

        var xml = response.responseXML;

        //<debug>
        if (!xml) {
            /**
             * @event exceptionリーダーがレスポンスをパースできないときは常に発火します。
             * @param {Ext.data.reader.Xml} reader このリーダへの参照
             * @param {XMLHttpRequest} response XMLHttpRequestレスポンスオブジェクト。
             * @param {String} error エラーメッセージ。
             */
            this.fireEvent('exception', this, response, 'XML data not found in the response');

            Ext.Logger.warn('XML data not found in the response');
        }
        //</debug>

        return xml;
    },

    /**
     * データオブジェクトを正規化します。
     * @param {Object} data 生のデータオブジェクト。
     * @return {Object} 存在する場合はデータオブジェクトの`documentElement`プロパティを返し、存在しない場合は同じオブジェクトを返します。
     */
    getData: function(data) {
        return data.documentElement || data;
    },

    /**
     * @private
     * 与えられたXMLオブジェクト。 リーダーのメタデータによって設定されるような、ルートを表示するエレメントを返します。
     * @param {Object} data XMLデータオブジェクト。
     * @return {XMLElement} ルートノード要素。
     */
    getRoot: function(data) {
        var nodeName = data.nodeName,
            root = this.getRootProperty();

        if (!root || (nodeName && nodeName == root)) {
            return data;
        } else if (Ext.DomQuery.isXml(data)) {
            // This fix ensures we have XML data
            // Related to TreeStore calling getRoot with the root node, which isn't XML
            // Probably should be resolved in TreeStore at some point
            return Ext.DomQuery.selectNode(root, data);
        }
    },

    /**
     * @private
     * 必要なレコードノードを抜き出すことにより、スーパークラスのためのデータを取得します。
     * @param {XMLElement} root XMLルートノード。
     * @return {Ext.data.Model[]} レコード。
     */
    extractData: function(root) {
        var recordName = this.getRecord();

        //<debug>
        if (!recordName) {
            Ext.Logger.error('Record is a required parameter');
        }
        //</debug>

        if (recordName != root.nodeName && recordName !== root.localName) {
            root = Ext.DomQuery.select(recordName, root);
        } else {
            root = [root];
        }
        return this.callParent([root]);
    },

    /**
     * @private
     * {@link Ext.data.reader.Reader#getAssociatedDataRoot}のドキュメントを参照してください。
     * @param {Object} data 生のデータオブジェクト。
     * @param {String} associationName データを取得するためのアソシエーションの名前です(存在する場合、{@link Ext.data.association.Association#associationKey}を使用)。
     * @return {XMLElement} ルート。
     */
    getAssociatedDataRoot: function(data, associationName) {
        return Ext.DomQuery.select(associationName, data)[0];
    },

    /**
     * XMLドキュメントをパースしてモデルインスタンスを格納したResultSetを返します。
     * @param {Object} doc パースされたXMLドキュメント。
     * @return {Ext.data.ResultSet} パースされた結果セット。
     */
    readRecords: function(doc) {
        //it's possible that we get passed an array here by associations. Make sure we strip that out (see Ext.data.reader.Reader#readAssociated)
        if (Ext.isArray(doc)) {
            doc = doc[0];
        }
        return this.callParent([doc]);
    },

    /**
     * @private
     * Fieldのマッピング、または、フィールドのコレクション内における順番をインデックスとして利用して、XML要素から渡されたFieldのアクセッサ式を返します。
     *
     * これは、生のデータをモデルインスタンスに変換するエクストラクタの関数で最適化を行う`buildExtractors`によって使用されます。
     */
    createFieldAccessExpression: function(field, fieldVarName, dataName) {
        var selector = field.getMapping() || field.getName(),
            result;

        if (typeof selector === 'function') {
            result = fieldVarName + '.getMapping()(' + dataName + ', this)';
        } else {
            selector = selector.split('@');

            if (selector.length === 2 && selector[0]) {
                result = 'me.getNodeValue(Ext.DomQuery.selectNode("@' + selector[1] + '", Ext.DomQuery.selectNode("' + selector[0] + '", ' + dataName + ')))';
            } else if (selector.length === 2) {
                result = 'me.getNodeValue(Ext.DomQuery.selectNode("@' + selector[1] + '", ' + dataName + '))';
            } else if (selector.length === 1) {
                result = 'me.getNodeValue(Ext.DomQuery.selectNode("' + selector[0] + '", ' + dataName + '))';
            } else {
                throw "Unsupported query - too many queries for attributes in " + selector.join('@');
            }
        }
        return result;
    }
});