    // @tag core
    /**
     * This class parses the XTemplate syntax and calls abstract methods to process the parts.
     * @private
     */
    Ext.define('Ext.XTemplateParser', {
        constructor: function (config) {
            Ext.apply(this, config);
        },

        /**
         * @property {Number} level The 'for' or 'foreach' loop context level. This is adjusted
         * up by one prior to calling {@link #doFor} or {@link #doForEach} and down by one after
         * calling the corresponding {@link #doEnd} that closes the loop. This will be 1 on the
         * first {@link #doFor} or {@link #doForEach} call.
         */

        /**
         * This method is called to process a piece of raw text from the tpl.
         * @param {String} text
         * @method doText
         */
        // doText: function (text)

        /**
         * This method is called to process expressions (like `{[expr]}`).
         * @param {String} expr The body of the expression (inside "{[" and "]}").
         * @method doExpr
         */
        // doExpr: function (expr)

        /**
         * This method is called to process simple tags (like `{tag}`).
         * @method doTag
         */
        // doTag: function (tag)

        /**
         * This method is called to process `<tpl else>`.
         * @method doElse
         */
        // doElse: function ()

        /**
         * This method is called to process `{% text %}`.
         * @param {String} text
         * @method doEval
         */
        // doEval: function (text)

        /**
         * This method is called to process `<tpl if="action">`. If there are other attributes,
         * these are passed in the actions object.
         * @param {String} action
         * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
         * @method doIf
         */
        // doIf: function (action, actions)

        /**
         * This method is called to process `<tpl elseif="action">`. If there are other attributes,
         * these are passed in the actions object.
         * @param {String} action
         * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
         * @method doElseIf
         */
        // doElseIf: function (action, actions)

        /**
         * This method is called to process `<tpl switch="action">`. If there are other attributes,
         * these are passed in the actions object.
         * @param {String} action
         * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
         * @method doSwitch
         */
        // doSwitch: function (action, actions)

        /**
         * This method is called to process `<tpl case="action">`. If there are other attributes,
         * these are passed in the actions object.
         * @param {String} action
         * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
         * @method doCase
         */
        // doCase: function (action, actions)

        /**
         * This method is called to process `<tpl default>`.
         * @method doDefault
         */
        // doDefault: function ()

        /**
         * This method is called to process `</tpl>`. It is given the action type that started
         * the tpl and the set of additional actions.
         * @param {String} type The type of action that is being ended.
         * @param {Object} actions The other actions keyed by the attribute name (such as 'exec').
         * @method doEnd
         */
        // doEnd: function (type, actions)

        /**
         * This method is called to process `<tpl for="action">`. If there are other attributes,
         * these are passed in the actions object.
         * @param {String} action
         * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
         * @method doFor
         */
        // doFor: function (action, actions)

        /**
         * This method is called to process `<tpl foreach="action">`. If there are other
         * attributes, these are passed in the actions object.
         * @param {String} action
         * @param {Object} actions Other actions keyed by the attribute name (such as 'exec').
         * @method doForEach
         */
        // doForEach: function (action, actions)

        /**
         * This method is called to process `<tpl exec="action">`. If there are other attributes,
         * these are passed in the actions object.
         * @param {String} action
         * @param {Object} actions Other actions keyed by the attribute name.
         * @method doExec
         */
        // doExec: function (action, actions)

        /**
         * This method is called to process an empty `<tpl>`. This is unlikely to need to be
         * implemented, so a default (do nothing) version is provided.
         * @method
         */
        doTpl: Ext.emptyFn,

        parse: function (str) {
            var me = this,
                len = str.length,
                aliases = { elseif: 'elif' },
                topRe = me.topRe,
                actionsRe = me.actionsRe,
                index, stack, s, m, t, prev, frame, subMatch, begin, end, actions,
                prop;

            me.level = 0;
            me.stack = stack = [];

            for (index = 0; index < len; index = end) {
                topRe.lastIndex = index;
                m = topRe.exec(str);

                if (!m) {
                    me.doText(str.substring(index, len));
                    break;
                }

                begin = m.index;
                end = topRe.lastIndex;

                if (index < begin) {
                    me.doText(str.substring(index, begin));
                }

                if (m[1]) {
                    end = str.indexOf('%}', begin+2);
                    me.doEval(str.substring(begin+2, end));
                    end += 2;
                } else if (m[2]) {
                    end = str.indexOf(']}', begin+2);
                    me.doExpr(str.substring(begin+2, end));
                    end += 2;
                } else if (m[3]) { // if ('{' token)
                    me.doTag(m[3]);
                } else if (m[4]) { // content of a <tpl xxxxxx xxx> tag
                    actions = null;
                    while ((subMatch = actionsRe.exec(m[4])) !== null) {
                        s = subMatch[2] || subMatch[3];
                        if (s) {
                            s = Ext.String.htmlDecode(s); // decode attr value
                            t = subMatch[1];
                            t = aliases[t] || t;
                            actions = actions || {};
                            prev = actions[t];

                            if (typeof prev == 'string') {
                                actions[t] = [prev, s];
                            } else if (prev) {
                                actions[t].push(s);
                            } else {
                                actions[t] = s;
                            }
                        }
                    }

                    if (!actions) {
                        if (me.elseRe.test(m[4])) {
                            me.doElse();
                        } else if (me.defaultRe.test(m[4])) {
                            me.doDefault();
                        } else {
                            me.doTpl();
                            stack.push({ type: 'tpl' });
                        }
                    }
                    else if (actions['if']) {
                        me.doIf(actions['if'], actions);
                        stack.push({ type: 'if' });
                    }
                    else if (actions['switch']) {
                        me.doSwitch(actions['switch'], actions);
                        stack.push({ type: 'switch' });
                    }
                    else if (actions['case']) {
                        me.doCase(actions['case'], actions);
                    }
                    else if (actions['elif']) {
                        me.doElseIf(actions['elif'], actions);
                    }
                    else if (actions['for']) {
                        ++me.level;

                        // Extract property name to use from indexed item
                        if (prop = me.propRe.exec(m[4])) {
                            actions.propName = prop[1] || prop[2];
                        }
                        me.doFor(actions['for'], actions);
                        stack.push({ type: 'for', actions: actions });
                    }
                    else if (actions['foreach']) {
                        ++me.level;

                        // Extract property name to use from indexed item
                        if (prop = me.propRe.exec(m[4])) {
                            actions.propName = prop[1] || prop[2];
                        }
                        me.doForEach(actions['foreach'], actions);
                        stack.push({ type: 'foreach', actions: actions });
                    }
                    else if (actions.exec) {
                        me.doExec(actions.exec, actions);
                        stack.push({ type: 'exec', actions: actions });
                    }
                    /*
                    else {
                        // todo - error
                    }
                    */
                } else if (m[0].length === 5) {
                    // if the length of m[0] is 5, assume that we're dealing with an opening tpl tag with no attributes (e.g. <tpl>...</tpl>)
                    // in this case no action is needed other than pushing it on to the stack
                    stack.push({ type: 'tpl' });
                } else {
                    frame = stack.pop();
                    me.doEnd(frame.type, frame.actions);
                    if (frame.type == 'for' || frame.type == 'foreach') {
                        --me.level;
                    }
                }
            }
        },

        // Internal regexes

        topRe:     /(?:(\{\%)|(\{\[)|\{([^{}]+)\})|(?:<tpl([^>]*)\>)|(?:<\/tpl>)/g,
        actionsRe: /\s*(elif|elseif|if|for|foreach|exec|switch|case|eval|between)\s*\=\s*(?:(?:"([^"]*)")|(?:'([^']*)'))\s*/g,
        propRe:    /prop=(?:(?:"([^"]*)")|(?:'([^']*)'))/,
        defaultRe: /^\s*default\s*$/,
        elseRe:    /^\s*else\s*$/
    });