/** * This class parses the XTemplate syntax and calls abstract methods to process the parts. * @private */Ext.define('Ext.util.XTemplateParser', { /* eslint-disable dot-notation */ requires: [ 'Ext.String' ], 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, expectTplNext; 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) { // In the case of a switch statement, we expect a tpl for each case. // However, if we have spaces they will get matched as plaintext, so // we want to skip over them here. s = str.substring(index, begin); if (!(expectTplNext && Ext.String.trim(s) === '')) { me.doText(s); } } expectTplNext = false; 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' }); expectTplNext = true; } 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 // eslint-disable-next-line no-cond-assign 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 // eslint-disable-next-line no-cond-assign 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 /* eslint-disable no-useless-escape */ 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*$/});