var Cache = require('../cache') var config = require('../config') var dirParser = require('./directive') var regexEscapeRE = /[-.*+?^${}()|[\]\/\\]/g var cache, tagRE, htmlRE, firstChar, lastChar /** * Escape a string so it can be used in a RegExp * constructor. * * @param {String} str */ function escapeRegex (str) { return str.replace(regexEscapeRE, '\\$&') } /** * Compile the interpolation tag regex. * * @return {RegExp} */ function compileRegex () { config._delimitersChanged = false var open = config.delimiters[0] var close = config.delimiters[1] firstChar = open.charAt(0) lastChar = close.charAt(close.length - 1) var firstCharRE = escapeRegex(firstChar) var lastCharRE = escapeRegex(lastChar) var openRE = escapeRegex(open) var closeRE = escapeRegex(close) tagRE = new RegExp( firstCharRE + '?' + openRE + '(.+?)' + closeRE + lastCharRE + '?', 'g' ) htmlRE = new RegExp( '^' + firstCharRE + openRE + '.*' + closeRE + lastCharRE + '$' ) // reset cache cache = new Cache(1000) } /** * Parse a template text string into an array of tokens. * * @param {String} text * @return {Array | null} * - {String} type * - {String} value * - {Boolean} [html] * - {Boolean} [oneTime] */ exports.parse = function (text) { if (config._delimitersChanged) { compileRegex() } var hit = cache.get(text) if (hit) { return hit } if (!tagRE.test(text)) { return null } var tokens = [] var lastIndex = tagRE.lastIndex = 0 var match, index, value, first, oneTime, partial /* jshint boss:true */ while (match = tagRE.exec(text)) { index = match.index // push text token if (index > lastIndex) { tokens.push({ value: text.slice(lastIndex, index) }) } // tag token first = match[1].charCodeAt(0) oneTime = first === 0x2A // * partial = first === 0x3E // > value = (oneTime || partial) ? match[1].slice(1) : match[1] tokens.push({ tag: true, value: value.trim(), html: htmlRE.test(match[0]), oneTime: oneTime, partial: partial }) lastIndex = index + match[0].length } if (lastIndex < text.length) { tokens.push({ value: text.slice(lastIndex) }) } cache.put(text, tokens) return tokens } /** * Format a list of tokens into an expression. * e.g. tokens parsed from 'a {{b}} c' can be serialized * into one single expression as '"a " + b + " c"'. * * @param {Array} tokens * @param {Vue} [vm] * @return {String} */ exports.tokensToExp = function (tokens, vm) { return tokens.length > 1 ? tokens.map(function (token) { return formatToken(token, vm) }).join('+') : formatToken(tokens[0], vm, true) } /** * Format a single token. * * @param {Object} token * @param {Vue} [vm] * @param {Boolean} single * @return {String} */ function formatToken (token, vm, single) { return token.tag ? vm && token.oneTime ? '"' + vm.$eval(token.value) + '"' : single ? token.value : inlineFilters(token.value) : '"' + token.value + '"' } /** * For an attribute with multiple interpolation tags, * e.g. attr="some-{{thing | filter}}", in order to combine * the whole thing into a single watchable expression, we * have to inline those filters. This function does exactly * that. This is a bit hacky but it avoids heavy changes * to directive parser and watcher mechanism. * * @param {String} exp * @return {String} */ var filterRE = /[^|]\|[^|]/ function inlineFilters (exp) { if (!filterRE.test(exp)) { return '(' + exp + ')' } else { var dir = dirParser.parse(exp)[0] if (!dir.filters) { return '(' + exp + ')' } else { exp = dir.expression for (var i = 0, l = dir.filters.length; i < l; i++) { var filter = dir.filters[i] var args = filter.args ? ',"' + filter.args.join('","') + '"' : '' exp = 'this.$options.filters["' + filter.name + '"]' + '.apply(this,[' + exp + args + '])' } return exp } } }