Ext version tested:
  • Ext 3.2 rev 0

Adapter used:
  • ext

Browser versions tested against:
  • Firefox 3.6 (firebug 1.5.3)

Operating System:
  • WinXP Pro

Description:
When readResponse is called in XmlReader, it attempts to call extractData against all elements matching this.meta.record. It appears that the intention is, if no matching elements are found, to instead call extractData using all elements matching this.meta.root. Line 136 of XmlReader.js:
Code:
data: this.extractData(q.select(this.meta.record, doc) || q.select(this.meta.root, doc), false),
The problem is Ext.DomQuery.select returns an empty array if no elements are found, and (at least under Firefox) an empty array evaluates to true (see "test case"). Because of this, q.select(this.meta.root, doc) is never evaluated and it's impossible to retrieve any data if this.meta.record matches no elements in the server response.

Test Case:
My server is pretty custom so there's not much I can do in the way of a test case, but the following code demonstrates the principle at work:
Code:
console.log([] || ["populated array"]); // outputs "[]"
Steps to reproduce the problem:
  • If you want to reproduce the bug, try creating a XmlReader with a record selector that doesn't match anything in a XML webservice/document, but a root selector that does.
The result that was expected:
  • extractData is called on the element that matches root
The result that occurs instead:
  • A "root-empty" or "root-undefined-response" is thrown.
Debugging already done:
  • It's because of [] evaluating to true as an object in the extractData call.
Possible fix:
The following override fixes the problem for me:
Code:
Ext.override(Ext.data.XmlReader, {
    readResponse : function(action, response) {
        var q   = Ext.DomQuery,
        doc     = response.responseXML,
        nodes   = q.select(this.meta.record, doc);
        if(nodes.length === 0) {
            nodes = q.select(this.meta.root, doc);
        }

        // create general Response instance.
        var res = new Ext.data.Response({
            action: action,
            success : this.getSuccess(doc),
            message: this.getMessage(doc),
            data: this.extractData(nodes, false),
            raw: doc
        });

        if (Ext.isEmpty(res.success)) {
            throw new Ext.data.DataReader.Error('successProperty-response', this.meta.successProperty);
        }

        // Create actions from a response having status 200 must return pk
        if (action === Ext.data.Api.actions.create) {
            var def = Ext.isDefined(res.data);
            if (def && Ext.isEmpty(res.data)) {
                throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
            }
            else if (!def) {
                throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
            }
        }
        return res;
    }
});
There may be a more compact way to do it...