diff options
Diffstat (limited to 'blockly/demos/plane/soy/soyutils.js')
-rw-r--r-- | blockly/demos/plane/soy/soyutils.js | 2767 |
1 files changed, 2767 insertions, 0 deletions
diff --git a/blockly/demos/plane/soy/soyutils.js b/blockly/demos/plane/soy/soyutils.js new file mode 100644 index 0000000..bde8e41 --- /dev/null +++ b/blockly/demos/plane/soy/soyutils.js @@ -0,0 +1,2767 @@ +/* + * Copyright 2008 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileoverview + * Utility functions and classes for Soy. + * + * <p> + * The top portion of this file contains utilities for Soy users:<ul> + * <li> soy.StringBuilder: Compatible with the 'stringbuilder' code style. + * <li> soy.renderElement: Render template and set as innerHTML of an element. + * <li> soy.renderAsFragment: Render template and return as HTML fragment. + * </ul> + * + * <p> + * The bottom portion of this file contains utilities that should only be called + * by Soy-generated JS code. Please do not use these functions directly from + * your hand-writen code. Their names all start with '$$'. + * + * @author Garrett Boyer + * @author Mike Samuel + * @author Kai Huang + * @author Aharon Lanin + */ + + +// COPIED FROM nogoog_shim.js + +// Create closure namespaces. +var goog = goog || {}; + + +goog.DEBUG = false; + + +goog.inherits = function(childCtor, parentCtor) { + /** @constructor */ + function tempCtor() {} + tempCtor.prototype = parentCtor.prototype; + childCtor.superClass_ = parentCtor.prototype; + childCtor.prototype = new tempCtor(); + childCtor.prototype.constructor = childCtor; +}; + + +// Just enough browser detection for this file. +if (!goog.userAgent) { + goog.userAgent = (function() { + var userAgent = ""; + if ("undefined" !== typeof navigator && navigator + && "string" == typeof navigator.userAgent) { + userAgent = navigator.userAgent; + } + var isOpera = userAgent.indexOf('Opera') == 0; + return { + jscript: { + /** + * @type {boolean} + */ + HAS_JSCRIPT: 'ScriptEngine' in this + }, + /** + * @type {boolean} + */ + OPERA: isOpera, + /** + * @type {boolean} + */ + IE: !isOpera && userAgent.indexOf('MSIE') != -1, + /** + * @type {boolean} + */ + WEBKIT: !isOpera && userAgent.indexOf('WebKit') != -1 + }; + })(); +} + +if (!goog.asserts) { + goog.asserts = { + /** + * @param {*} condition Condition to check. + */ + assert: function (condition) { + if (!condition) { + throw Error('Assertion error'); + } + }, + /** + * @param {...*} var_args + */ + fail: function (var_args) {} + }; +} + + +// Stub out the document wrapper used by renderAs*. +if (!goog.dom) { + goog.dom = {}; + /** + * @param {Document=} d + * @constructor + */ + goog.dom.DomHelper = function(d) { + this.document_ = d || document; + }; + /** + * @return {!Document} + */ + goog.dom.DomHelper.prototype.getDocument = function() { + return this.document_; + }; + /** + * Creates a new element. + * @param {string} name Tag name. + * @return {!Element} + */ + goog.dom.DomHelper.prototype.createElement = function(name) { + return this.document_.createElement(name); + }; + /** + * Creates a new document fragment. + * @return {!DocumentFragment} + */ + goog.dom.DomHelper.prototype.createDocumentFragment = function() { + return this.document_.createDocumentFragment(); + }; +} + + +if (!goog.format) { + goog.format = { + insertWordBreaks: function(str, maxCharsBetweenWordBreaks) { + str = String(str); + + var resultArr = []; + var resultArrLen = 0; + + // These variables keep track of important state inside str. + var isInTag = false; // whether we're inside an HTML tag + var isMaybeInEntity = false; // whether we might be inside an HTML entity + var numCharsWithoutBreak = 0; // number of chars since last word break + var flushIndex = 0; // index of first char not yet flushed to resultArr + + for (var i = 0, n = str.length; i < n; ++i) { + var charCode = str.charCodeAt(i); + + // If hit maxCharsBetweenWordBreaks, and not space next, then add <wbr>. + if (numCharsWithoutBreak >= maxCharsBetweenWordBreaks && + // space + charCode != 32) { + resultArr[resultArrLen++] = str.substring(flushIndex, i); + flushIndex = i; + resultArr[resultArrLen++] = goog.format.WORD_BREAK; + numCharsWithoutBreak = 0; + } + + if (isInTag) { + // If inside an HTML tag and we see '>', it's the end of the tag. + if (charCode == 62) { + isInTag = false; + } + + } else if (isMaybeInEntity) { + switch (charCode) { + // Inside an entity, a ';' is the end of the entity. + // The entity that just ended counts as one char, so increment + // numCharsWithoutBreak. + case 59: // ';' + isMaybeInEntity = false; + ++numCharsWithoutBreak; + break; + // If maybe inside an entity and we see '<', we weren't actually in + // an entity. But now we're inside and HTML tag. + case 60: // '<' + isMaybeInEntity = false; + isInTag = true; + break; + // If maybe inside an entity and we see ' ', we weren't actually in + // an entity. Just correct the state and reset the + // numCharsWithoutBreak since we just saw a space. + case 32: // ' ' + isMaybeInEntity = false; + numCharsWithoutBreak = 0; + break; + } + + } else { // !isInTag && !isInEntity + switch (charCode) { + // When not within a tag or an entity and we see '<', we're now + // inside an HTML tag. + case 60: // '<' + isInTag = true; + break; + // When not within a tag or an entity and we see '&', we might be + // inside an entity. + case 38: // '&' + isMaybeInEntity = true; + break; + // When we see a space, reset the numCharsWithoutBreak count. + case 32: // ' ' + numCharsWithoutBreak = 0; + break; + // When we see a non-space, increment the numCharsWithoutBreak. + default: + ++numCharsWithoutBreak; + break; + } + } + } + + // Flush the remaining chars at the end of the string. + resultArr[resultArrLen++] = str.substring(flushIndex); + + return resultArr.join(''); + }, + /** + * String inserted as a word break by insertWordBreaks(). Safari requires + * <wbr></wbr>, Opera needs the 'shy' entity, though this will give a + * visible hyphen at breaks. Other browsers just use <wbr>. + * @type {string} + * @private + */ + WORD_BREAK: goog.userAgent.WEBKIT + ? '<wbr></wbr>' : goog.userAgent.OPERA ? '­' : '<wbr>' + }; +} + + +if (!goog.i18n) { + goog.i18n = { + bidi: { + /** + * Check the directionality of a piece of text, return true if the piece + * of text should be laid out in RTL direction. + * @param {string} text The piece of text that need to be detected. + * @param {boolean=} opt_isHtml Whether {@code text} is HTML/HTML-escaped. + * Default: false. + * @return {boolean} + * @private + */ + detectRtlDirectionality: function(text, opt_isHtml) { + text = soyshim.$$bidiStripHtmlIfNecessary_(text, opt_isHtml); + return soyshim.$$bidiRtlWordRatio_(text) + > soyshim.$$bidiRtlDetectionThreshold_; + } + } + }; +} + +/** + * Directionality enum. + * @enum {number} + */ +goog.i18n.bidi.Dir = { + RTL: -1, + UNKNOWN: 0, + LTR: 1 +}; + + +/** + * Convert a directionality given in various formats to a goog.i18n.bidi.Dir + * constant. Useful for interaction with different standards of directionality + * representation. + * + * @param {goog.i18n.bidi.Dir|number|boolean} givenDir Directionality given in + * one of the following formats: + * 1. A goog.i18n.bidi.Dir constant. + * 2. A number (positive = LRT, negative = RTL, 0 = unknown). + * 3. A boolean (true = RTL, false = LTR). + * @return {goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the given + * directionality. + */ +goog.i18n.bidi.toDir = function(givenDir) { + if (typeof givenDir == 'number') { + return givenDir > 0 ? goog.i18n.bidi.Dir.LTR : + givenDir < 0 ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.UNKNOWN; + } else { + return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR; + } +}; + + +/** + * Utility class for formatting text for display in a potentially + * opposite-directionality context without garbling. Provides the following + * functionality: + * + * @param {goog.i18n.bidi.Dir|number|boolean} dir The context + * directionality as a number + * (positive = LRT, negative = RTL, 0 = unknown). + * @constructor + */ +goog.i18n.BidiFormatter = function(dir) { + this.dir_ = goog.i18n.bidi.toDir(dir); +}; + + +/** + * Returns 'dir="ltr"' or 'dir="rtl"', depending on {@code text}'s estimated + * directionality, if it is not the same as the context directionality. + * Otherwise, returns the empty string. + * + * @param {string} text Text whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether {@code text} is HTML / HTML-escaped. + * Default: false. + * @return {string} 'dir="rtl"' for RTL text in non-RTL context; 'dir="ltr"' for + * LTR text in non-LTR context; else, the empty string. + */ +goog.i18n.BidiFormatter.prototype.dirAttr = function (text, opt_isHtml) { + var dir = soy.$$bidiTextDir(text, opt_isHtml); + return dir && dir != this.dir_ ? dir < 0 ? 'dir="rtl"' : 'dir="ltr"' : ''; +}; + +/** + * Returns the trailing horizontal edge, i.e. "right" or "left", depending on + * the global bidi directionality. + * @return {string} "left" for RTL context and "right" otherwise. + */ +goog.i18n.BidiFormatter.prototype.endEdge = function () { + return this.dir_ < 0 ? 'left' : 'right'; +}; + +/** + * Returns the Unicode BiDi mark matching the context directionality (LRM for + * LTR context directionality, RLM for RTL context directionality), or the + * empty string for neutral / unknown context directionality. + * + * @return {string} LRM for LTR context directionality and RLM for RTL context + * directionality. + */ +goog.i18n.BidiFormatter.prototype.mark = function () { + return ( + (this.dir_ > 0) ? '\u200E' /*LRM*/ : + (this.dir_ < 0) ? '\u200F' /*RLM*/ : + ''); +}; + +/** + * Returns a Unicode BiDi mark matching the context directionality (LRM or RLM) + * if the directionality or the exit directionality of {@code text} are opposite + * to the context directionality. Otherwise returns the empty string. + * + * @param {string} text The input text. + * @param {boolean=} opt_isHtml Whether {@code text} is HTML / HTML-escaped. + * Default: false. + * @return {string} A Unicode bidi mark matching the global directionality or + * the empty string. + */ +goog.i18n.BidiFormatter.prototype.markAfter = function (text, opt_isHtml) { + var dir = soy.$$bidiTextDir(text, opt_isHtml); + return soyshim.$$bidiMarkAfterKnownDir_(this.dir_, dir, text, opt_isHtml); +}; + +/** + * Formats a string of unknown directionality for use in HTML output of the + * context directionality, so an opposite-directionality string is neither + * garbled nor garbles what follows it. + * + * @param {string} str The input text. + * @param {boolean=} placeholder This argument exists for consistency with the + * Closure Library. Specifying it has no effect. + * @return {string} Input text after applying the above processing. + */ +goog.i18n.BidiFormatter.prototype.spanWrap = function(str, placeholder) { + str = String(str); + var textDir = soy.$$bidiTextDir(str, true); + var reset = soyshim.$$bidiMarkAfterKnownDir_(this.dir_, textDir, str, true); + if (textDir > 0 && this.dir_ <= 0) { + str = '<span dir="ltr">' + str + '</span>'; + } else if (textDir < 0 && this.dir_ >= 0) { + str = '<span dir="rtl">' + str + '</span>'; + } + return str + reset; +}; + +/** + * Returns the leading horizontal edge, i.e. "left" or "right", depending on + * the global bidi directionality. + * @return {string} "right" for RTL context and "left" otherwise. + */ +goog.i18n.BidiFormatter.prototype.startEdge = function () { + return this.dir_ < 0 ? 'right' : 'left'; +}; + +/** + * Formats a string of unknown directionality for use in plain-text output of + * the context directionality, so an opposite-directionality string is neither + * garbled nor garbles what follows it. + * As opposed to {@link #spanWrap}, this makes use of unicode BiDi formatting + * characters. In HTML, its *only* valid use is inside of elements that do not + * allow mark-up, e.g. an 'option' tag. + * + * @param {string} str The input text. + * @param {boolean=} placeholder This argument exists for consistency with the + * Closure Library. Specifying it has no effect. + * @return {string} Input text after applying the above processing. + */ +goog.i18n.BidiFormatter.prototype.unicodeWrap = function(str, placeholder) { + str = String(str); + var textDir = soy.$$bidiTextDir(str, true); + var reset = soyshim.$$bidiMarkAfterKnownDir_(this.dir_, textDir, str, true); + if (textDir > 0 && this.dir_ <= 0) { + str = '\u202A' + str + '\u202C'; + } else if (textDir < 0 && this.dir_ >= 0) { + str = '\u202B' + str + '\u202C'; + } + return str + reset; +}; + + +if (!goog.string) { + goog.string = { + /** + * Converts \r\n, \r, and \n to <br>s + * @param {*} str The string in which to convert newlines. + * @param {boolean=} opt_xml Whether to use XML compatible tags. + * @return {string} A copy of {@code str} with converted newlines. + */ + newLineToBr: function(str, opt_xml) { + + str = String(str); + + // This quick test helps in the case when there are no chars to replace, + // in the worst case this makes barely a difference to the time taken. + if (!goog.string.NEWLINE_TO_BR_RE_.test(str)) { + return str; + } + + return str.replace(/(\r\n|\r|\n)/g, opt_xml ? '<br />' : '<br>'); + }, + urlEncode: encodeURIComponent, + /** + * Regular expression used within newlineToBr(). + * @type {RegExp} + * @private + */ + NEWLINE_TO_BR_RE_: /[\r\n]/ + }; +} + +/** + * Utility class to facilitate much faster string concatenation in IE, + * using Array.join() rather than the '+' operator. For other browsers + * we simply use the '+' operator. + * + * @param {Object|number|string|boolean=} opt_a1 Optional first initial item + * to append. + * @param {...Object|number|string|boolean} var_args Other initial items to + * append, e.g., new goog.string.StringBuffer('foo', 'bar'). + * @constructor + */ +goog.string.StringBuffer = function(opt_a1, var_args) { + /** + * Internal buffer for the string to be concatenated. + * @type {string|Array} + * @private + */ + this.buffer_ = goog.userAgent.jscript.HAS_JSCRIPT ? [] : ''; + + if (opt_a1 != null) { + this.append.apply(this, arguments); + } +}; + + +/** + * Length of internal buffer (faster than calling buffer_.length). + * Only used for IE. + * @type {number} + * @private + */ +goog.string.StringBuffer.prototype.bufferLength_ = 0; + +/** + * Appends one or more items to the string. + * + * Calling this with null, undefined, or empty arguments is an error. + * + * @param {Object|number|string|boolean} a1 Required first string. + * @param {Object|number|string|boolean=} opt_a2 Optional second string. + * @param {...Object|number|string|boolean} var_args Other items to append, + * e.g., sb.append('foo', 'bar', 'baz'). + * @return {goog.string.StringBuffer} This same StringBuilder object. + */ +goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) { + + if (goog.userAgent.jscript.HAS_JSCRIPT) { + if (opt_a2 == null) { // no second argument (note: undefined == null) + // Array assignment is 2x faster than Array push. Also, use a1 + // directly to avoid arguments instantiation, another 2x improvement. + this.buffer_[this.bufferLength_++] = a1; + } else { + var arr = /**@type {Array.<number|string|boolean>}*/(this.buffer_); + arr.push.apply(arr, arguments); + this.bufferLength_ = this.buffer_.length; + } + + } else { + + // Use a1 directly to avoid arguments instantiation for single-arg case. + this.buffer_ += a1; + if (opt_a2 != null) { // no second argument (note: undefined == null) + for (var i = 1; i < arguments.length; i++) { + this.buffer_ += arguments[i]; + } + } + } + + return this; +}; + + +/** + * Clears the string. + */ +goog.string.StringBuffer.prototype.clear = function() { + + if (goog.userAgent.jscript.HAS_JSCRIPT) { + this.buffer_.length = 0; // reuse array to avoid creating new object + this.bufferLength_ = 0; + + } else { + this.buffer_ = ''; + } +}; + + +/** + * Returns the concatenated string. + * + * @return {string} The concatenated string. + */ +goog.string.StringBuffer.prototype.toString = function() { + + if (goog.userAgent.jscript.HAS_JSCRIPT) { + var str = this.buffer_.join(''); + // Given a string with the entire contents, simplify the StringBuilder by + // setting its contents to only be this string, rather than many fragments. + this.clear(); + if (str) { + this.append(str); + } + return str; + + } else { + return /** @type {string} */ (this.buffer_); + } +}; + + +if (!goog.soy) goog.soy = { + /** + * Helper function to render a Soy template and then set the + * output string as the innerHTML of an element. It is recommended + * to use this helper function instead of directly setting + * innerHTML in your hand-written code, so that it will be easier + * to audit the code for cross-site scripting vulnerabilities. + * + * @param {Function} template The Soy template defining element's content. + * @param {Object=} opt_templateData The data for the template. + * @param {Object=} opt_injectedData The injected data for the template. + * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM + * nodes will be created. + */ + renderAsElement: function( + template, opt_templateData, opt_injectedData, opt_dom) { + return /** @type {!Element} */ (soyshim.$$renderWithWrapper_( + template, opt_templateData, opt_dom, true /* asElement */, + opt_injectedData)); + }, + /** + * Helper function to render a Soy template into a single node or + * a document fragment. If the rendered HTML string represents a + * single node, then that node is returned (note that this is + * *not* a fragment, despite them name of the method). Otherwise a + * document fragment is returned containing the rendered nodes. + * + * @param {Function} template The Soy template defining element's content. + * @param {Object=} opt_templateData The data for the template. + * @param {Object=} opt_injectedData The injected data for the template. + * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM + * nodes will be created. + * @return {!Node} The resulting node or document fragment. + */ + renderAsFragment: function( + template, opt_templateData, opt_injectedData, opt_dom) { + return soyshim.$$renderWithWrapper_( + template, opt_templateData, opt_dom, false /* asElement */, + opt_injectedData); + }, + /** + * Helper function to render a Soy template and then set the output string as + * the innerHTML of an element. It is recommended to use this helper function + * instead of directly setting innerHTML in your hand-written code, so that it + * will be easier to audit the code for cross-site scripting vulnerabilities. + * + * NOTE: New code should consider using goog.soy.renderElement instead. + * + * @param {Element} element The element whose content we are rendering. + * @param {Function} template The Soy template defining the element's content. + * @param {Object=} opt_templateData The data for the template. + * @param {Object=} opt_injectedData The injected data for the template. + */ + renderElement: function( + element, template, opt_templateData, opt_injectedData) { + element.innerHTML = template(opt_templateData, null, opt_injectedData); + }, + data: {} +}; + + +/** + * A type of textual content. + * + * This is an enum of type Object so that these values are unforgeable. + * + * @enum {!Object} + */ +goog.soy.data.SanitizedContentKind = { + + /** + * A snippet of HTML that does not start or end inside a tag, comment, entity, + * or DOCTYPE; and that does not contain any executable code + * (JS, {@code <object>}s, etc.) from a different trust domain. + */ + HTML: {}, + + /** + * Executable Javascript code or expression, safe for insertion in a + * script-tag or event handler context, known to be free of any + * attacker-controlled scripts. This can either be side-effect-free + * Javascript (such as JSON) or Javascript that entirely under Google's + * control. + */ + JS: goog.DEBUG ? {sanitizedContentJsStrChars: true} : {}, + + /** + * A sequence of code units that can appear between quotes (either kind) in a + * JS program without causing a parse error, and without causing any side + * effects. + * <p> + * The content should not contain unescaped quotes, newlines, or anything else + * that would cause parsing to fail or to cause a JS parser to finish the + * string its parsing inside the content. + * <p> + * The content must also not end inside an escape sequence ; no partial octal + * escape sequences or odd number of '{@code \}'s at the end. + */ + JS_STR_CHARS: {}, + + /** A properly encoded portion of a URI. */ + URI: {}, + + /** + * Repeated attribute names and values. For example, + * {@code dir="ltr" foo="bar" onclick="trustedFunction()" checked}. + */ + ATTRIBUTES: goog.DEBUG ? {sanitizedContentHtmlAttribute: true} : {}, + + // TODO: Consider separating rules, declarations, and values into + // separate types, but for simplicity, we'll treat explicitly blessed + // SanitizedContent as allowed in all of these contexts. + /** + * A CSS3 declaration, property, value or group of semicolon separated + * declarations. + */ + CSS: {}, + + /** + * Unsanitized plain-text content. + * + * This is effectively the "null" entry of this enum, and is sometimes used + * to explicitly mark content that should never be used unescaped. Since any + * string is safe to use as text, being of ContentKind.TEXT makes no + * guarantees about its safety in any other context such as HTML. + */ + TEXT: {} +}; + + + +/** + * A string-like object that carries a content-type. + * + * IMPORTANT! Do not create these directly, nor instantiate the subclasses. + * Instead, use a trusted, centrally reviewed library as endorsed by your team + * to generate these objects. Otherwise, you risk accidentally creating + * SanitizedContent that is attacker-controlled and gets evaluated unescaped in + * templates. + * + * @constructor + */ +goog.soy.data.SanitizedContent = function() { + throw Error('Do not instantiate directly'); +}; + + +/** + * The context in which this content is safe from XSS attacks. + * @type {goog.soy.data.SanitizedContentKind} + */ +goog.soy.data.SanitizedContent.prototype.contentKind; + + +/** + * The already-safe content. + * @type {string} + */ +goog.soy.data.SanitizedContent.prototype.content; + + +/** @override */ +goog.soy.data.SanitizedContent.prototype.toString = function() { + return this.content; +}; + + +var soy = { esc: {} }; +var soydata = {}; +soydata.VERY_UNSAFE = {}; +var soyshim = { $$DEFAULT_TEMPLATE_DATA_: {} }; +/** + * Helper function to render a Soy template into a single node or a document + * fragment. If the rendered HTML string represents a single node, then that + * node is returned. Otherwise a document fragment is created and returned + * (wrapped in a DIV element if #opt_singleNode is true). + * + * @param {Function} template The Soy template defining the element's content. + * @param {Object=} opt_templateData The data for the template. + * @param {(goog.dom.DomHelper|Document)=} opt_dom The context in which DOM + * nodes will be created. + * @param {boolean=} opt_asElement Whether to wrap the fragment in an + * element if the template does not render a single element. If true, + * result is always an Element. + * @param {Object=} opt_injectedData The injected data for the template. + * @return {!Node} The resulting node or document fragment. + * @private + */ +soyshim.$$renderWithWrapper_ = function( + template, opt_templateData, opt_dom, opt_asElement, opt_injectedData) { + + var dom = opt_dom || document; + var wrapper = dom.createElement('div'); + wrapper.innerHTML = template( + opt_templateData || soyshim.$$DEFAULT_TEMPLATE_DATA_, undefined, + opt_injectedData); + + // If the template renders as a single element, return it. + if (wrapper.childNodes.length == 1) { + var firstChild = wrapper.firstChild; + if (!opt_asElement || firstChild.nodeType == 1 /* Element */) { + return /** @type {!Node} */ (firstChild); + } + } + + // If we're forcing it to be a single element, return the wrapper DIV. + if (opt_asElement) { + return wrapper; + } + + // Otherwise, create and return a fragment. + var fragment = dom.createDocumentFragment(); + while (wrapper.firstChild) { + fragment.appendChild(wrapper.firstChild); + } + return fragment; +}; + + +/** + * Returns a Unicode BiDi mark matching bidiGlobalDir (LRM or RLM) if the + * directionality or the exit directionality of text are opposite to + * bidiGlobalDir. Otherwise returns the empty string. + * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes + * in text, making the logic suitable for HTML and HTML-escaped text. + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {number} dir text's directionality: 1 if ltr, -1 if rtl, 0 if unknown. + * @param {string} text The text whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped. + * Default: false. + * @return {string} A Unicode bidi mark matching bidiGlobalDir, or + * the empty string when text's overall and exit directionalities both match + * bidiGlobalDir, or bidiGlobalDir is 0 (unknown). + * @private + */ +soyshim.$$bidiMarkAfterKnownDir_ = function( + bidiGlobalDir, dir, text, opt_isHtml) { + return ( + bidiGlobalDir > 0 && (dir < 0 || + soyshim.$$bidiIsRtlExitText_(text, opt_isHtml)) ? '\u200E' : // LRM + bidiGlobalDir < 0 && (dir > 0 || + soyshim.$$bidiIsLtrExitText_(text, opt_isHtml)) ? '\u200F' : // RLM + ''); +}; + + +/** + * Strips str of any HTML mark-up and escapes. Imprecise in several ways, but + * precision is not very important, since the result is only meant to be used + * for directionality detection. + * @param {string} str The string to be stripped. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {string} The stripped string. + * @private + */ +soyshim.$$bidiStripHtmlIfNecessary_ = function(str, opt_isHtml) { + return opt_isHtml ? str.replace(soyshim.$$BIDI_HTML_SKIP_RE_, ' ') : str; +}; + + +/** + * Simplified regular expression for am HTML tag (opening or closing) or an HTML + * escape - the things we want to skip over in order to ignore their ltr + * characters. + * @type {RegExp} + * @private + */ +soyshim.$$BIDI_HTML_SKIP_RE_ = /<[^>]*>|&[^;]+;/g; + + +/** + * A practical pattern to identify strong LTR character. This pattern is not + * theoretically correct according to unicode standard. It is simplified for + * performance and small code size. + * @type {string} + * @private + */ +soyshim.$$bidiLtrChars_ = + 'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' + + '\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'; + + +/** + * A practical pattern to identify strong neutral and weak character. This + * pattern is not theoretically correct according to unicode standard. It is + * simplified for performance and small code size. + * @type {string} + * @private + */ +soyshim.$$bidiNeutralChars_ = + '\u0000-\u0020!-@[-`{-\u00BF\u00D7\u00F7\u02B9-\u02FF\u2000-\u2BFF'; + + +/** + * A practical pattern to identify strong RTL character. This pattern is not + * theoretically correct according to unicode standard. It is simplified for + * performance and small code size. + * @type {string} + * @private + */ +soyshim.$$bidiRtlChars_ = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; + + +/** + * Regular expressions to check if a piece of text is of RTL directionality + * on first character with strong directionality. + * @type {RegExp} + * @private + */ +soyshim.$$bidiRtlDirCheckRe_ = new RegExp( + '^[^' + soyshim.$$bidiLtrChars_ + ']*[' + soyshim.$$bidiRtlChars_ + ']'); + + +/** + * Regular expressions to check if a piece of text is of neutral directionality. + * Url are considered as neutral. + * @type {RegExp} + * @private + */ +soyshim.$$bidiNeutralDirCheckRe_ = new RegExp( + '^[' + soyshim.$$bidiNeutralChars_ + ']*$|^http://'); + + +/** + * Check the directionality of the a piece of text based on the first character + * with strong directionality. + * @param {string} str string being checked. + * @return {boolean} return true if rtl directionality is being detected. + * @private + */ +soyshim.$$bidiIsRtlText_ = function(str) { + return soyshim.$$bidiRtlDirCheckRe_.test(str); +}; + + +/** + * Check the directionality of the a piece of text based on the first character + * with strong directionality. + * @param {string} str string being checked. + * @return {boolean} true if all characters have neutral directionality. + * @private + */ +soyshim.$$bidiIsNeutralText_ = function(str) { + return soyshim.$$bidiNeutralDirCheckRe_.test(str); +}; + + +/** + * This constant controls threshold of rtl directionality. + * @type {number} + * @private + */ +soyshim.$$bidiRtlDetectionThreshold_ = 0.40; + + +/** + * Returns the RTL ratio based on word count. + * @param {string} str the string that need to be checked. + * @return {number} the ratio of RTL words among all words with directionality. + * @private + */ +soyshim.$$bidiRtlWordRatio_ = function(str) { + var rtlCount = 0; + var totalCount = 0; + var tokens = str.split(' '); + for (var i = 0; i < tokens.length; i++) { + if (soyshim.$$bidiIsRtlText_(tokens[i])) { + rtlCount++; + totalCount++; + } else if (!soyshim.$$bidiIsNeutralText_(tokens[i])) { + totalCount++; + } + } + + return totalCount == 0 ? 0 : rtlCount / totalCount; +}; + + +/** + * Regular expressions to check if the last strongly-directional character in a + * piece of text is LTR. + * @type {RegExp} + * @private + */ +soyshim.$$bidiLtrExitDirCheckRe_ = new RegExp( + '[' + soyshim.$$bidiLtrChars_ + '][^' + soyshim.$$bidiRtlChars_ + ']*$'); + + +/** + * Regular expressions to check if the last strongly-directional character in a + * piece of text is RTL. + * @type {RegExp} + * @private + */ +soyshim.$$bidiRtlExitDirCheckRe_ = new RegExp( + '[' + soyshim.$$bidiRtlChars_ + '][^' + soyshim.$$bidiLtrChars_ + ']*$'); + + +/** + * Check if the exit directionality a piece of text is LTR, i.e. if the last + * strongly-directional character in the string is LTR. + * @param {string} str string being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether LTR exit directionality was detected. + * @private + */ +soyshim.$$bidiIsLtrExitText_ = function(str, opt_isHtml) { + str = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml); + return soyshim.$$bidiLtrExitDirCheckRe_.test(str); +}; + + +/** + * Check if the exit directionality a piece of text is RTL, i.e. if the last + * strongly-directional character in the string is RTL. + * @param {string} str string being checked. + * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped. + * Default: false. + * @return {boolean} Whether RTL exit directionality was detected. + * @private + */ +soyshim.$$bidiIsRtlExitText_ = function(str, opt_isHtml) { + str = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml); + return soyshim.$$bidiRtlExitDirCheckRe_.test(str); +}; + + +// ============================================================================= +// COPIED FROM soyutils_usegoog.js + + +// ----------------------------------------------------------------------------- +// StringBuilder (compatible with the 'stringbuilder' code style). + + +/** + * Utility class to facilitate much faster string concatenation in IE, + * using Array.join() rather than the '+' operator. For other browsers + * we simply use the '+' operator. + * + * @param {Object} var_args Initial items to append, + * e.g., new soy.StringBuilder('foo', 'bar'). + * @constructor + */ +soy.StringBuilder = goog.string.StringBuffer; + + +// ----------------------------------------------------------------------------- +// soydata: Defines typed strings, e.g. an HTML string {@code "a<b>c"} is +// semantically distinct from the plain text string {@code "a<b>c"} and smart +// templates can take that distinction into account. + +/** + * A type of textual content. + * + * This is an enum of type Object so that these values are unforgeable. + * + * @enum {!Object} + */ +soydata.SanitizedContentKind = goog.soy.data.SanitizedContentKind; + + +/** + * Content of type {@link soydata.SanitizedContentKind.HTML}. + * + * The content is a string of HTML that can safely be embedded in a PCDATA + * context in your app. If you would be surprised to find that an HTML + * sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs) and + * you wouldn't write a template that produces {@code s} on security or privacy + * grounds, then don't pass {@code s} here. + * + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ +soydata.SanitizedHtml = function() { + goog.soy.data.SanitizedContent.call(this); // Throws an exception. +}; +goog.inherits(soydata.SanitizedHtml, goog.soy.data.SanitizedContent); + +/** @override */ +soydata.SanitizedHtml.prototype.contentKind = soydata.SanitizedContentKind.HTML; + + +/** + * Content of type {@link soydata.SanitizedContentKind.JS}. + * + * The content is Javascript source that when evaluated does not execute any + * attacker-controlled scripts. + * + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ +soydata.SanitizedJs = function() { + goog.soy.data.SanitizedContent.call(this); // Throws an exception. +}; +goog.inherits(soydata.SanitizedJs, goog.soy.data.SanitizedContent); + +/** @override */ +soydata.SanitizedJs.prototype.contentKind = + soydata.SanitizedContentKind.JS; + + +/** + * Content of type {@link soydata.SanitizedContentKind.JS_STR_CHARS}. + * + * The content can be safely inserted as part of a single- or double-quoted + * string without terminating the string. + * + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ +soydata.SanitizedJsStrChars = function() { + goog.soy.data.SanitizedContent.call(this); // Throws an exception. +}; +goog.inherits(soydata.SanitizedJsStrChars, goog.soy.data.SanitizedContent); + +/** @override */ +soydata.SanitizedJsStrChars.prototype.contentKind = + soydata.SanitizedContentKind.JS_STR_CHARS; + + +/** + * Content of type {@link soydata.SanitizedContentKind.URI}. + * + * The content is a URI chunk that the caller knows is safe to emit in a + * template. + * + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ +soydata.SanitizedUri = function() { + goog.soy.data.SanitizedContent.call(this); // Throws an exception. +}; +goog.inherits(soydata.SanitizedUri, goog.soy.data.SanitizedContent); + +/** @override */ +soydata.SanitizedUri.prototype.contentKind = soydata.SanitizedContentKind.URI; + + +/** + * Content of type {@link soydata.SanitizedContentKind.ATTRIBUTES}. + * + * The content should be safely embeddable within an open tag, such as a + * key="value" pair. + * + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ +soydata.SanitizedHtmlAttribute = function() { + goog.soy.data.SanitizedContent.call(this); // Throws an exception. +}; +goog.inherits(soydata.SanitizedHtmlAttribute, goog.soy.data.SanitizedContent); + +/** @override */ +soydata.SanitizedHtmlAttribute.prototype.contentKind = + soydata.SanitizedContentKind.ATTRIBUTES; + + +/** + * Content of type {@link soydata.SanitizedContentKind.CSS}. + * + * The content is non-attacker-exploitable CSS, such as {@code color:#c3d9ff}. + * + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ +soydata.SanitizedCss = function() { + goog.soy.data.SanitizedContent.call(this); // Throws an exception. +}; +goog.inherits(soydata.SanitizedCss, goog.soy.data.SanitizedContent); + +/** @override */ +soydata.SanitizedCss.prototype.contentKind = + soydata.SanitizedContentKind.CSS; + + +/** + * Unsanitized plain text string. + * + * While all strings are effectively safe to use as a plain text, there are no + * guarantees about safety in any other context such as HTML. This is + * sometimes used to mark that should never be used unescaped. + * + * @param {*} content Plain text with no guarantees. + * @constructor + * @extends {goog.soy.data.SanitizedContent} + */ +soydata.UnsanitizedText = function(content) { + /** @override */ + this.content = String(content); +}; +goog.inherits(soydata.UnsanitizedText, goog.soy.data.SanitizedContent); + +/** @override */ +soydata.UnsanitizedText.prototype.contentKind = + soydata.SanitizedContentKind.TEXT; + + +/** + * Creates a factory for SanitizedContent types. + * + * This is a hack so that the soydata.VERY_UNSAFE.ordainSanitized* can + * instantiate Sanitized* classes, without making the Sanitized* constructors + * publicly usable. Requiring all construction to use the VERY_UNSAFE names + * helps callers and their reviewers easily tell that creating SanitizedContent + * is not always safe and calls for careful review. + * + * @param {function(new: T, string)} ctor A constructor. + * @return {!function(*): T} A factory that takes content and returns a + * new instance. + * @template T + * @private + */ +soydata.$$makeSanitizedContentFactory_ = function(ctor) { + /** @constructor */ + function InstantiableCtor() {} + InstantiableCtor.prototype = ctor.prototype; + return function(content) { + var result = new InstantiableCtor(); + result.content = String(content); + return result; + }; +}; + + +// ----------------------------------------------------------------------------- +// Sanitized content ordainers. Please use these with extreme caution (with the +// exception of markUnsanitizedText). A good recommendation is to limit usage +// of these to just a handful of files in your source tree where usages can be +// carefully audited. + + +/** + * Protects a string from being used in an noAutoescaped context. + * + * This is useful for content where there is significant risk of accidental + * unescaped usage in a Soy template. A great case is for user-controlled + * data that has historically been a source of vulernabilities. + * + * @param {*} content Text to protect. + * @return {!soydata.UnsanitizedText} A wrapper that is rejected by the + * Soy noAutoescape print directive. + */ +soydata.markUnsanitizedText = function(content) { + return new soydata.UnsanitizedText(content); +}; + + +/** + * Takes a leap of faith that the provided content is "safe" HTML. + * + * @param {*} content A string of HTML that can safely be embedded in + * a PCDATA context in your app. If you would be surprised to find that an + * HTML sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs) + * and you wouldn't write a template that produces {@code s} on security or + * privacy grounds, then don't pass {@code s} here. + * @return {!soydata.SanitizedHtml} Sanitized content wrapper that + * indicates to Soy not to escape when printed as HTML. + */ +soydata.VERY_UNSAFE.ordainSanitizedHtml = + soydata.$$makeSanitizedContentFactory_(soydata.SanitizedHtml); + + +/** + * Takes a leap of faith that the provided content is "safe" (non-attacker- + * controlled, XSS-free) Javascript. + * + * @param {*} content Javascript source that when evaluated does not + * execute any attacker-controlled scripts. + * @return {!soydata.SanitizedJs} Sanitized content wrapper that indicates to + * Soy not to escape when printed as Javascript source. + */ +soydata.VERY_UNSAFE.ordainSanitizedJs = + soydata.$$makeSanitizedContentFactory_(soydata.SanitizedJs); + + +// TODO: This function is probably necessary, either externally or internally +// as an implementation detail. Generally, plain text will always work here, +// as there's no harm to unescaping the string and then re-escaping when +// finally printed. +/** + * Takes a leap of faith that the provided content can be safely embedded in + * a Javascript string without re-esacping. + * + * @param {*} content Content that can be safely inserted as part of a + * single- or double-quoted string without terminating the string. + * @return {!soydata.SanitizedJsStrChars} Sanitized content wrapper that + * indicates to Soy not to escape when printed in a JS string. + */ +soydata.VERY_UNSAFE.ordainSanitizedJsStrChars = + soydata.$$makeSanitizedContentFactory_(soydata.SanitizedJsStrChars); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as a URI + * in a Soy template. + * + * This creates a Soy SanitizedContent object which indicates to Soy there is + * no need to escape it when printed as a URI (e.g. in an href or src + * attribute), such as if it's already been encoded or if it's a Javascript: + * URI. + * + * @param {*} content A chunk of URI that the caller knows is safe to + * emit in a template. + * @return {!soydata.SanitizedUri} Sanitized content wrapper that indicates to + * Soy not to escape or filter when printed in URI context. + */ +soydata.VERY_UNSAFE.ordainSanitizedUri = + soydata.$$makeSanitizedContentFactory_(soydata.SanitizedUri); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as an + * HTML attribute. + * + * @param {*} content An attribute name and value, such as + * {@code dir="ltr"}. + * @return {!soydata.SanitizedHtmlAttribute} Sanitized content wrapper that + * indicates to Soy not to escape when printed as an HTML attribute. + */ +soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute = + soydata.$$makeSanitizedContentFactory_(soydata.SanitizedHtmlAttribute); + + +/** + * Takes a leap of faith that the provided content is "safe" to use as CSS + * in a style attribute or block. + * + * @param {*} content CSS, such as {@code color:#c3d9ff}. + * @return {!soydata.SanitizedCss} Sanitized CSS wrapper that indicates to + * Soy there is no need to escape or filter when printed in CSS context. + */ +soydata.VERY_UNSAFE.ordainSanitizedCss = + soydata.$$makeSanitizedContentFactory_(soydata.SanitizedCss); + + +// ----------------------------------------------------------------------------- +// Public utilities. + + +/** + * Helper function to render a Soy template and then set the output string as + * the innerHTML of an element. It is recommended to use this helper function + * instead of directly setting innerHTML in your hand-written code, so that it + * will be easier to audit the code for cross-site scripting vulnerabilities. + * + * NOTE: New code should consider using goog.soy.renderElement instead. + * + * @param {Element} element The element whose content we are rendering. + * @param {Function} template The Soy template defining the element's content. + * @param {Object=} opt_templateData The data for the template. + * @param {Object=} opt_injectedData The injected data for the template. + */ +soy.renderElement = goog.soy.renderElement; + + +/** + * Helper function to render a Soy template into a single node or a document + * fragment. If the rendered HTML string represents a single node, then that + * node is returned (note that this is *not* a fragment, despite them name of + * the method). Otherwise a document fragment is returned containing the + * rendered nodes. + * + * NOTE: New code should consider using goog.soy.renderAsFragment + * instead (note that the arguments are different). + * + * @param {Function} template The Soy template defining the element's content. + * @param {Object=} opt_templateData The data for the template. + * @param {Document=} opt_document The document used to create DOM nodes. If not + * specified, global document object is used. + * @param {Object=} opt_injectedData The injected data for the template. + * @return {!Node} The resulting node or document fragment. + */ +soy.renderAsFragment = function( + template, opt_templateData, opt_document, opt_injectedData) { + return goog.soy.renderAsFragment( + template, opt_templateData, opt_injectedData, + new goog.dom.DomHelper(opt_document)); +}; + + +/** + * Helper function to render a Soy template into a single node. If the rendered + * HTML string represents a single node, then that node is returned. Otherwise, + * a DIV element is returned containing the rendered nodes. + * + * NOTE: New code should consider using goog.soy.renderAsElement + * instead (note that the arguments are different). + * + * @param {Function} template The Soy template defining the element's content. + * @param {Object=} opt_templateData The data for the template. + * @param {Document=} opt_document The document used to create DOM nodes. If not + * specified, global document object is used. + * @param {Object=} opt_injectedData The injected data for the template. + * @return {!Element} Rendered template contents, wrapped in a parent DIV + * element if necessary. + */ +soy.renderAsElement = function( + template, opt_templateData, opt_document, opt_injectedData) { + return goog.soy.renderAsElement( + template, opt_templateData, opt_injectedData, + new goog.dom.DomHelper(opt_document)); +}; + + +// ----------------------------------------------------------------------------- +// Below are private utilities to be used by Soy-generated code only. + + +/** + * Builds an augmented map. The returned map will contain mappings from both + * the base map and the additional map. If the same key appears in both, then + * the value from the additional map will be visible, while the value from the + * base map will be hidden. The base map will be used, but not modified. + * + * @param {!Object} baseMap The original map to augment. + * @param {!Object} additionalMap A map containing the additional mappings. + * @return {!Object} An augmented map containing both the original and + * additional mappings. + */ +soy.$$augmentMap = function(baseMap, additionalMap) { + + // Create a new map whose '__proto__' field is set to baseMap. + /** @constructor */ + function TempCtor() {} + TempCtor.prototype = baseMap; + var augmentedMap = new TempCtor(); + + // Add the additional mappings to the new map. + for (var key in additionalMap) { + augmentedMap[key] = additionalMap[key]; + } + + return augmentedMap; +}; + + +/** + * Checks that the given map key is a string. + * @param {*} key Key to check. + * @return {string} The given key. + */ +soy.$$checkMapKey = function(key) { + if ((typeof key) != 'string') { + throw Error( + 'Map literal\'s key expression must evaluate to string' + + ' (encountered type "' + (typeof key) + '").'); + } + return key; +}; + + +/** + * Gets the keys in a map as an array. There are no guarantees on the order. + * @param {Object} map The map to get the keys of. + * @return {Array.<string>} The array of keys in the given map. + */ +soy.$$getMapKeys = function(map) { + var mapKeys = []; + for (var key in map) { + mapKeys.push(key); + } + return mapKeys; +}; + + +/** + * Gets a consistent unique id for the given delegate template name. Two calls + * to this function will return the same id if and only if the input names are + * the same. + * + * <p> Important: This function must always be called with a string constant. + * + * <p> If Closure Compiler is not being used, then this is just this identity + * function. If Closure Compiler is being used, then each call to this function + * will be replaced with a short string constant, which will be consistent per + * input name. + * + * @param {string} delTemplateName The delegate template name for which to get a + * consistent unique id. + * @return {string} A unique id that is consistent per input name. + * + * @consistentIdGenerator + */ +soy.$$getDelTemplateId = function(delTemplateName) { + return delTemplateName; +}; + + +/** + * Map from registered delegate template key to the priority of the + * implementation. + * @type {Object} + * @private + */ +soy.$$DELEGATE_REGISTRY_PRIORITIES_ = {}; + +/** + * Map from registered delegate template key to the implementation function. + * @type {Object} + * @private + */ +soy.$$DELEGATE_REGISTRY_FUNCTIONS_ = {}; + + +/** + * Registers a delegate implementation. If the same delegate template key (id + * and variant) has been registered previously, then priority values are + * compared and only the higher priority implementation is stored (if + * priorities are equal, an error is thrown). + * + * @param {string} delTemplateId The delegate template id. + * @param {string} delTemplateVariant The delegate template variant (can be + * empty string). + * @param {number} delPriority The implementation's priority value. + * @param {Function} delFn The implementation function. + */ +soy.$$registerDelegateFn = function( + delTemplateId, delTemplateVariant, delPriority, delFn) { + + var mapKey = 'key_' + delTemplateId + ':' + delTemplateVariant; + var currPriority = soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey]; + if (currPriority === undefined || delPriority > currPriority) { + // Registering new or higher-priority function: replace registry entry. + soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey] = delPriority; + soy.$$DELEGATE_REGISTRY_FUNCTIONS_[mapKey] = delFn; + } else if (delPriority == currPriority) { + // Registering same-priority function: error. + throw Error( + 'Encountered two active delegates with the same priority ("' + + delTemplateId + ':' + delTemplateVariant + '").'); + } else { + // Registering lower-priority function: do nothing. + } +}; + + +/** + * Retrieves the (highest-priority) implementation that has been registered for + * a given delegate template key (id and variant). If no implementation has + * been registered for the key, then the fallback is the same id with empty + * variant. If the fallback is also not registered, and allowsEmptyDefault is + * true, then returns an implementation that is equivalent to an empty template + * (i.e. rendered output would be empty string). + * + * @param {string} delTemplateId The delegate template id. + * @param {string} delTemplateVariant The delegate template variant (can be + * empty string). + * @param {boolean} allowsEmptyDefault Whether to default to the empty template + * function if there's no active implementation. + * @return {Function} The retrieved implementation function. + */ +soy.$$getDelegateFn = function( + delTemplateId, delTemplateVariant, allowsEmptyDefault) { + + var delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_[ + 'key_' + delTemplateId + ':' + delTemplateVariant]; + if (! delFn && delTemplateVariant != '') { + // Fallback to empty variant. + delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId + ':']; + } + + if (delFn) { + return delFn; + } else if (allowsEmptyDefault) { + return soy.$$EMPTY_TEMPLATE_FN_; + } else { + throw Error( + 'Found no active impl for delegate call to "' + delTemplateId + ':' + + delTemplateVariant + '" (and not allowemptydefault="true").'); + } +}; + + +/** + * Private helper soy.$$getDelegateFn(). This is the empty template function + * that is returned whenever there's no delegate implementation found. + * + * @param {Object.<string, *>=} opt_data + * @param {soy.StringBuilder=} opt_sb + * @param {Object.<string, *>=} opt_ijData + * @return {string} + * @private + */ +soy.$$EMPTY_TEMPLATE_FN_ = function(opt_data, opt_sb, opt_ijData) { + return ''; +}; + + +// ----------------------------------------------------------------------------- +// Escape/filter/normalize. + + +/** + * Escapes HTML special characters in a string. Escapes double quote '"' in + * addition to '&', '<', and '>' so that a string can be included in an HTML + * tag attribute value within double quotes. + * Will emit known safe HTML as-is. + * + * @param {*} value The string-like value to be escaped. May not be a string, + * but the value will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeHtml = function(value) { + // TODO: Perhaps we should just ignore the contentKind property and instead + // look only at the constructor. + if (value && value.contentKind && + value.contentKind === goog.soy.data.SanitizedContentKind.HTML) { + goog.asserts.assert( + value.constructor === soydata.SanitizedHtml); + return value.content; + } + return soy.esc.$$escapeHtmlHelper(value); +}; + + +/** + * Strips unsafe tags to convert a string of untrusted HTML into HTML that + * is safe to embed. + * + * @param {*} value The string-like value to be escaped. May not be a string, + * but the value will be coerced to a string. + * @return {string} A sanitized and normalized version of value. + */ +soy.$$cleanHtml = function(value) { + if (value && value.contentKind && + value.contentKind === goog.soy.data.SanitizedContentKind.HTML) { + goog.asserts.assert( + value.constructor === soydata.SanitizedHtml); + return value.content; + } + return soy.$$stripHtmlTags(value, soy.esc.$$SAFE_TAG_WHITELIST_); +}; + + +/** + * Escapes HTML special characters in a string so that it can be embedded in + * RCDATA. + * <p> + * Escapes HTML special characters so that the value will not prematurely end + * the body of a tag like {@code <textarea>} or {@code <title>}. RCDATA tags + * cannot contain other HTML entities, so it is not strictly necessary to escape + * HTML special characters except when part of that text looks like an HTML + * entity or like a close tag : {@code </textarea>}. + * <p> + * Will normalize known safe HTML to make sure that sanitized HTML (which could + * contain an innocuous {@code </textarea>} don't prematurely end an RCDATA + * element. + * + * @param {*} value The string-like value to be escaped. May not be a string, + * but the value will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeHtmlRcdata = function(value) { + if (value && value.contentKind && + value.contentKind === goog.soy.data.SanitizedContentKind.HTML) { + goog.asserts.assert( + value.constructor === soydata.SanitizedHtml); + return soy.esc.$$normalizeHtmlHelper(value.content); + } + return soy.esc.$$escapeHtmlHelper(value); +}; + + +/** + * Matches any/only HTML5 void elements' start tags. + * See http://www.w3.org/TR/html-markup/syntax.html#syntax-elements + * @type {RegExp} + * @private + */ +soy.$$HTML5_VOID_ELEMENTS_ = new RegExp( + '^<(?:area|base|br|col|command|embed|hr|img|input' + + '|keygen|link|meta|param|source|track|wbr)\\b'); + + +/** + * Removes HTML tags from a string of known safe HTML. + * If opt_tagWhitelist is not specified or is empty, then + * the result can be used as an attribute value. + * + * @param {*} value The HTML to be escaped. May not be a string, but the + * value will be coerced to a string. + * @param {Object.<string, number>=} opt_tagWhitelist Has an own property whose + * name is a lower-case tag name and whose value is {@code 1} for + * each element that is allowed in the output. + * @return {string} A representation of value without disallowed tags, + * HTML comments, or other non-text content. + */ +soy.$$stripHtmlTags = function(value, opt_tagWhitelist) { + if (!opt_tagWhitelist) { + // If we have no white-list, then use a fast track which elides all tags. + return String(value).replace(soy.esc.$$HTML_TAG_REGEX_, '') + // This is just paranoia since callers should normalize the result + // anyway, but if they didn't, it would be necessary to ensure that + // after the first replace non-tag uses of < do not recombine into + // tags as in "<<foo>script>alert(1337)</<foo>script>". + .replace(soy.esc.$$LT_REGEX_, '<'); + } + + // Escapes '[' so that we can use [123] below to mark places where tags + // have been removed. + var html = String(value).replace(/\[/g, '['); + + // Consider all uses of '<' and replace whitelisted tags with markers like + // [1] which are indices into a list of approved tag names. + // Replace all other uses of < and > with entities. + var tags = []; + html = html.replace( + soy.esc.$$HTML_TAG_REGEX_, + function(tok, tagName) { + if (tagName) { + tagName = tagName.toLowerCase(); + if (opt_tagWhitelist.hasOwnProperty(tagName) && + opt_tagWhitelist[tagName]) { + var start = tok.charAt(1) === '/' ? '</' : '<'; + var index = tags.length; + tags[index] = start + tagName + '>'; + return '[' + index + ']'; + } + } + return ''; + }); + + // Escape HTML special characters. Now there are no '<' in html that could + // start a tag. + html = soy.esc.$$normalizeHtmlHelper(html); + + var finalCloseTags = soy.$$balanceTags_(tags); + + // Now html contains no tags or less-than characters that could become + // part of a tag via a replacement operation and tags only contains + // approved tags. + // Reinsert the white-listed tags. + html = html.replace( + /\[(\d+)\]/g, function(_, index) { return tags[index]; }); + + // Close any still open tags. + // This prevents unclosed formatting elements like <ol> and <table> from + // breaking the layout of containing HTML. + return html + finalCloseTags; +}; + + +/** + * Throw out any close tags that don't correspond to start tags. + * If {@code <table>} is used for formatting, embedded HTML shouldn't be able + * to use a mismatched {@code </table>} to break page layout. + * + * @param {Array.<string>} tags an array of tags that will be modified in place + * include tags, the empty string, or concatenations of empty tags. + * @return {string} zero or more closed tags that close all elements that are + * opened in tags but not closed. + * @private + */ +soy.$$balanceTags_ = function(tags) { + var open = []; + for (var i = 0, n = tags.length; i < n; ++i) { + var tag = tags[i]; + if (tag.charAt(1) === '/') { + var openTagIndex = open.length - 1; + // NOTE: This is essentially lastIndexOf, but it's not supported in IE. + while (openTagIndex >= 0 && open[openTagIndex] != tag) { + openTagIndex--; + } + if (openTagIndex < 0) { + tags[i] = ''; // Drop close tag. + } else { + tags[i] = open.slice(openTagIndex).reverse().join(''); + open.length = openTagIndex; + } + } else if (!soy.$$HTML5_VOID_ELEMENTS_.test(tag)) { + open.push('</' + tag.substring(1)); + } + } + return open.reverse().join(''); +}; + + +/** + * Escapes HTML special characters in an HTML attribute value. + * + * @param {*} value The HTML to be escaped. May not be a string, but the + * value will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeHtmlAttribute = function(value) { + if (value && value.contentKind) { + // NOTE: We don't accept ATTRIBUTES here because ATTRIBUTES is + // actually not the attribute value context, but instead k/v pairs. + if (value.contentKind === goog.soy.data.SanitizedContentKind.HTML) { + // NOTE: After removing tags, we also escape quotes ("normalize") so that + // the HTML can be embedded in attribute context. + goog.asserts.assert( + value.constructor === soydata.SanitizedHtml); + return soy.esc.$$normalizeHtmlHelper(soy.$$stripHtmlTags(value.content)); + } + } + return soy.esc.$$escapeHtmlHelper(value); +}; + + +/** + * Escapes HTML special characters in a string including space and other + * characters that can end an unquoted HTML attribute value. + * + * @param {*} value The HTML to be escaped. May not be a string, but the + * value will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeHtmlAttributeNospace = function(value) { + if (value && value.contentKind) { + if (value.contentKind === goog.soy.data.SanitizedContentKind.HTML) { + goog.asserts.assert(value.constructor === + soydata.SanitizedHtml); + return soy.esc.$$normalizeHtmlNospaceHelper( + soy.$$stripHtmlTags(value.content)); + } + } + return soy.esc.$$escapeHtmlNospaceHelper(value); +}; + + +/** + * Filters out strings that cannot be a substring of a valid HTML attribute. + * + * Note the input is expected to be key=value pairs. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} A valid HTML attribute name part or name/value pair. + * {@code "zSoyz"} if the input is invalid. + */ +soy.$$filterHtmlAttributes = function(value) { + // NOTE: Explicitly no support for SanitizedContentKind.HTML, since that is + // meaningless in this context, which is generally *between* html attributes. + if (value && + value.contentKind === goog.soy.data.SanitizedContentKind.ATTRIBUTES) { + goog.asserts.assert(value.constructor === + soydata.SanitizedHtmlAttribute); + // Add a space at the end to ensure this won't get merged into following + // attributes, unless the interpretation is unambiguous (ending with quotes + // or a space). + return value.content.replace(/([^"'\s])$/, '$1 '); + } + // TODO: Dynamically inserting attributes that aren't marked as trusted is + // probably unnecessary. Any filtering done here will either be inadequate + // for security or not flexible enough. Having clients use kind="attributes" + // in parameters seems like a wiser idea. + return soy.esc.$$filterHtmlAttributesHelper(value); +}; + + +/** + * Filters out strings that cannot be a substring of a valid HTML element name. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} A valid HTML element name part. + * {@code "zSoyz"} if the input is invalid. + */ +soy.$$filterHtmlElementName = function(value) { + // NOTE: We don't accept any SanitizedContent here. HTML indicates valid + // PCDATA, not tag names. A sloppy developer shouldn't be able to cause an + // exploit: + // ... {let userInput}script src=http://evil.com/evil.js{/let} ... + // ... {param tagName kind="html"}{$userInput}{/param} ... + // ... <{$tagName}>Hello World</{$tagName}> + return soy.esc.$$filterHtmlElementNameHelper(value); +}; + + +/** + * Escapes characters in the value to make it valid content for a JS string + * literal. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + * @deprecated + */ +soy.$$escapeJs = function(value) { + return soy.$$escapeJsString(value); +}; + + +/** + * Escapes characters in the value to make it valid content for a JS string + * literal. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeJsString = function(value) { + if (value && + value.contentKind === goog.soy.data.SanitizedContentKind.JS_STR_CHARS) { + // TODO: It might still be worthwhile to normalize it to remove + // unescaped quotes, null, etc: replace(/(?:^|[^\])['"]/g, '\\$ + goog.asserts.assert(value.constructor === + soydata.SanitizedJsStrChars); + return value.content; + } + return soy.esc.$$escapeJsStringHelper(value); +}; + + +/** + * Encodes a value as a JavaScript literal. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} A JavaScript code representation of the input. + */ +soy.$$escapeJsValue = function(value) { + // We surround values with spaces so that they can't be interpolated into + // identifiers by accident. + // We could use parentheses but those might be interpreted as a function call. + if (value == null) { // Intentionally matches undefined. + // Java returns null from maps where there is no corresponding key while + // JS returns undefined. + // We always output null for compatibility with Java which does not have a + // distinct undefined value. + return ' null '; + } + if (value.contentKind == goog.soy.data.SanitizedContentKind.JS) { + goog.asserts.assert(value.constructor === + soydata.SanitizedJs); + return value.content; + } + switch (typeof value) { + case 'boolean': case 'number': + return ' ' + value + ' '; + default: + return "'" + soy.esc.$$escapeJsStringHelper(String(value)) + "'"; + } +}; + + +/** + * Escapes characters in the string to make it valid content for a JS regular + * expression literal. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeJsRegex = function(value) { + return soy.esc.$$escapeJsRegexHelper(value); +}; + + +/** + * Matches all URI mark characters that conflict with HTML attribute delimiters + * or that cannot appear in a CSS uri. + * From <a href="http://www.w3.org/TR/CSS2/grammar.html">G.2: CSS grammar</a> + * <pre> + * url ([!#$%&*-~]|{nonascii}|{escape})* + * </pre> + * + * @type {RegExp} + * @private + */ +soy.$$problematicUriMarks_ = /['()]/g; + +/** + * @param {string} ch A single character in {@link soy.$$problematicUriMarks_}. + * @return {string} + * @private + */ +soy.$$pctEncode_ = function(ch) { + return '%' + ch.charCodeAt(0).toString(16); +}; + +/** + * Escapes a string so that it can be safely included in a URI. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeUri = function(value) { + if (value && value.contentKind === goog.soy.data.SanitizedContentKind.URI) { + goog.asserts.assert(value.constructor === + soydata.SanitizedUri); + return soy.$$normalizeUri(value); + } + // Apostophes and parentheses are not matched by encodeURIComponent. + // They are technically special in URIs, but only appear in the obsolete mark + // production in Appendix D.2 of RFC 3986, so can be encoded without changing + // semantics. + var encoded = soy.esc.$$escapeUriHelper(value); + soy.$$problematicUriMarks_.lastIndex = 0; + if (soy.$$problematicUriMarks_.test(encoded)) { + return encoded.replace(soy.$$problematicUriMarks_, soy.$$pctEncode_); + } + return encoded; +}; + + +/** + * Removes rough edges from a URI by escaping any raw HTML/JS string delimiters. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$normalizeUri = function(value) { + return soy.esc.$$normalizeUriHelper(value); +}; + + +/** + * Vets a URI's protocol and removes rough edges from a URI by escaping + * any raw HTML/JS string delimiters. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$filterNormalizeUri = function(value) { + if (value && value.contentKind == goog.soy.data.SanitizedContentKind.URI) { + goog.asserts.assert(value.constructor === + soydata.SanitizedUri); + return soy.$$normalizeUri(value); + } + return soy.esc.$$filterNormalizeUriHelper(value); +}; + + +/** + * Escapes a string so it can safely be included inside a quoted CSS string. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} An escaped version of value. + */ +soy.$$escapeCssString = function(value) { + return soy.esc.$$escapeCssStringHelper(value); +}; + + +/** + * Encodes a value as a CSS identifier part, keyword, or quantity. + * + * @param {*} value The value to escape. May not be a string, but the value + * will be coerced to a string. + * @return {string} A safe CSS identifier part, keyword, or quanitity. + */ +soy.$$filterCssValue = function(value) { + if (value && value.contentKind === goog.soy.data.SanitizedContentKind.CSS) { + goog.asserts.assert(value.constructor === + soydata.SanitizedCss); + return value.content; + } + // Uses == to intentionally match null and undefined for Java compatibility. + if (value == null) { + return ''; + } + return soy.esc.$$filterCssValueHelper(value); +}; + + +/** + * Sanity-checks noAutoescape input for explicitly tainted content. + * + * SanitizedContentKind.TEXT is used to explicitly mark input that was never + * meant to be used unescaped. + * + * @param {*} value The value to filter. + * @return {string} The value, that we dearly hope will not cause an attack. + */ +soy.$$filterNoAutoescape = function(value) { + if (value && value.contentKind === goog.soy.data.SanitizedContentKind.TEXT) { + // Fail in development mode. + goog.asserts.fail( + 'Tainted SanitizedContentKind.TEXT for |noAutoescape: `%s`', + [value.content]); + // Return innocuous data in production. + return 'zSoyz'; + } + return String(value); +}; + + +// ----------------------------------------------------------------------------- +// Basic directives/functions. + + +/** + * Converts \r\n, \r, and \n to <br>s + * @param {*} str The string in which to convert newlines. + * @return {string} A copy of {@code str} with converted newlines. + */ +soy.$$changeNewlineToBr = function(str) { + return goog.string.newLineToBr(String(str), false); +}; + + +/** + * Inserts word breaks ('wbr' tags) into a HTML string at a given interval. The + * counter is reset if a space is encountered. Word breaks aren't inserted into + * HTML tags or entities. Entites count towards the character count; HTML tags + * do not. + * + * @param {*} str The HTML string to insert word breaks into. Can be other + * types, but the value will be coerced to a string. + * @param {number} maxCharsBetweenWordBreaks Maximum number of non-space + * characters to allow before adding a word break. + * @return {string} The string including word breaks. + */ +soy.$$insertWordBreaks = function(str, maxCharsBetweenWordBreaks) { + return goog.format.insertWordBreaks(String(str), maxCharsBetweenWordBreaks); +}; + + +/** + * Truncates a string to a given max length (if it's currently longer), + * optionally adding ellipsis at the end. + * + * @param {*} str The string to truncate. Can be other types, but the value will + * be coerced to a string. + * @param {number} maxLen The maximum length of the string after truncation + * (including ellipsis, if applicable). + * @param {boolean} doAddEllipsis Whether to add ellipsis if the string needs + * truncation. + * @return {string} The string after truncation. + */ +soy.$$truncate = function(str, maxLen, doAddEllipsis) { + + str = String(str); + if (str.length <= maxLen) { + return str; // no need to truncate + } + + // If doAddEllipsis, either reduce maxLen to compensate, or else if maxLen is + // too small, just turn off doAddEllipsis. + if (doAddEllipsis) { + if (maxLen > 3) { + maxLen -= 3; + } else { + doAddEllipsis = false; + } + } + + // Make sure truncating at maxLen doesn't cut up a unicode surrogate pair. + if (soy.$$isHighSurrogate_(str.charAt(maxLen - 1)) && + soy.$$isLowSurrogate_(str.charAt(maxLen))) { + maxLen -= 1; + } + + // Truncate. + str = str.substring(0, maxLen); + + // Add ellipsis. + if (doAddEllipsis) { + str += '...'; + } + + return str; +}; + +/** + * Private helper for $$truncate() to check whether a char is a high surrogate. + * @param {string} ch The char to check. + * @return {boolean} Whether the given char is a unicode high surrogate. + * @private + */ +soy.$$isHighSurrogate_ = function(ch) { + return 0xD800 <= ch && ch <= 0xDBFF; +}; + +/** + * Private helper for $$truncate() to check whether a char is a low surrogate. + * @param {string} ch The char to check. + * @return {boolean} Whether the given char is a unicode low surrogate. + * @private + */ +soy.$$isLowSurrogate_ = function(ch) { + return 0xDC00 <= ch && ch <= 0xDFFF; +}; + + +// ----------------------------------------------------------------------------- +// Bidi directives/functions. + + +/** + * Cache of bidi formatter by context directionality, so we don't keep on + * creating new objects. + * @type {!Object.<!goog.i18n.BidiFormatter>} + * @private + */ +soy.$$bidiFormatterCache_ = {}; + + +/** + * Returns cached bidi formatter for bidiGlobalDir, or creates a new one. + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @return {goog.i18n.BidiFormatter} A formatter for bidiGlobalDir. + * @private + */ +soy.$$getBidiFormatterInstance_ = function(bidiGlobalDir) { + return soy.$$bidiFormatterCache_[bidiGlobalDir] || + (soy.$$bidiFormatterCache_[bidiGlobalDir] = + new goog.i18n.BidiFormatter(bidiGlobalDir)); +}; + + +/** + * Estimate the overall directionality of text. If opt_isHtml, makes sure to + * ignore the LTR nature of the mark-up and escapes in text, making the logic + * suitable for HTML and HTML-escaped text. + * @param {string} text The text whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped. + * Default: false. + * @return {number} 1 if text is LTR, -1 if it is RTL, and 0 if it is neutral. + */ +soy.$$bidiTextDir = function(text, opt_isHtml) { + if (!text) { + return 0; + } + return goog.i18n.bidi.detectRtlDirectionality(text, opt_isHtml) ? -1 : 1; +}; + + +/** + * Returns 'dir="ltr"' or 'dir="rtl"', depending on text's estimated + * directionality, if it is not the same as bidiGlobalDir. + * Otherwise, returns the empty string. + * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes + * in text, making the logic suitable for HTML and HTML-escaped text. + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {string} text The text whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped. + * Default: false. + * @return {soydata.SanitizedHtmlAttribute} 'dir="rtl"' for RTL text in non-RTL + * context; 'dir="ltr"' for LTR text in non-LTR context; + * else, the empty string. + */ +soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) { + return soydata.VERY_UNSAFE.ordainSanitizedHtmlAttribute( + soy.$$getBidiFormatterInstance_(bidiGlobalDir).dirAttr(text, opt_isHtml)); +}; + + +/** + * Returns a Unicode BiDi mark matching bidiGlobalDir (LRM or RLM) if the + * directionality or the exit directionality of text are opposite to + * bidiGlobalDir. Otherwise returns the empty string. + * If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes + * in text, making the logic suitable for HTML and HTML-escaped text. + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {string} text The text whose directionality is to be estimated. + * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped. + * Default: false. + * @return {string} A Unicode bidi mark matching bidiGlobalDir, or the empty + * string when text's overall and exit directionalities both match + * bidiGlobalDir, or bidiGlobalDir is 0 (unknown). + */ +soy.$$bidiMarkAfter = function(bidiGlobalDir, text, opt_isHtml) { + var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir); + return formatter.markAfter(text, opt_isHtml); +}; + + +/** + * Returns str wrapped in a <span dir="ltr|rtl"> according to its directionality + * - but only if that is neither neutral nor the same as the global context. + * Otherwise, returns str unchanged. + * Always treats str as HTML/HTML-escaped, i.e. ignores mark-up and escapes when + * estimating str's directionality. + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {*} str The string to be wrapped. Can be other types, but the value + * will be coerced to a string. + * @return {string} The wrapped string. + */ +soy.$$bidiSpanWrap = function(bidiGlobalDir, str) { + var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir); + return formatter.spanWrap(str + '', true); +}; + + +/** + * Returns str wrapped in Unicode BiDi formatting characters according to its + * directionality, i.e. either LRE or RLE at the beginning and PDF at the end - + * but only if str's directionality is neither neutral nor the same as the + * global context. Otherwise, returns str unchanged. + * Always treats str as HTML/HTML-escaped, i.e. ignores mark-up and escapes when + * estimating str's directionality. + * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1 + * if rtl, 0 if unknown. + * @param {*} str The string to be wrapped. Can be other types, but the value + * will be coerced to a string. + * @return {string} The wrapped string. + */ +soy.$$bidiUnicodeWrap = function(bidiGlobalDir, str) { + var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir); + return formatter.unicodeWrap(str + '', true); +}; + + +// ----------------------------------------------------------------------------- +// Generated code. + + + + +// START GENERATED CODE FOR ESCAPERS. + +/** + * @type {function (*) : string} + */ +soy.esc.$$escapeUriHelper = function(v) { + return encodeURIComponent(String(v)); +}; + +/** + * Maps charcters to the escaped versions for the named escape directives. + * @type {Object.<string, string>} + * @private + */ +soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_ = { + '\x00': '\x26#0;', + '\x22': '\x26quot;', + '\x26': '\x26amp;', + '\x27': '\x26#39;', + '\x3c': '\x26lt;', + '\x3e': '\x26gt;', + '\x09': '\x26#9;', + '\x0a': '\x26#10;', + '\x0b': '\x26#11;', + '\x0c': '\x26#12;', + '\x0d': '\x26#13;', + ' ': '\x26#32;', + '-': '\x26#45;', + '\/': '\x26#47;', + '\x3d': '\x26#61;', + '`': '\x26#96;', + '\x85': '\x26#133;', + '\xa0': '\x26#160;', + '\u2028': '\x26#8232;', + '\u2029': '\x26#8233;' +}; + +/** + * A function that can be used with String.replace.. + * @param {string} ch A single character matched by a compatible matcher. + * @return {string} A token in the output language. + * @private + */ +soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_ = function(ch) { + return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_[ch]; +}; + +/** + * Maps charcters to the escaped versions for the named escape directives. + * @type {Object.<string, string>} + * @private + */ +soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = { + '\x00': '\\x00', + '\x08': '\\x08', + '\x09': '\\t', + '\x0a': '\\n', + '\x0b': '\\x0b', + '\x0c': '\\f', + '\x0d': '\\r', + '\x22': '\\x22', + '\x26': '\\x26', + '\x27': '\\x27', + '\/': '\\\/', + '\x3c': '\\x3c', + '\x3d': '\\x3d', + '\x3e': '\\x3e', + '\\': '\\\\', + '\x85': '\\x85', + '\u2028': '\\u2028', + '\u2029': '\\u2029', + '$': '\\x24', + '(': '\\x28', + ')': '\\x29', + '*': '\\x2a', + '+': '\\x2b', + ',': '\\x2c', + '-': '\\x2d', + '.': '\\x2e', + ':': '\\x3a', + '?': '\\x3f', + '[': '\\x5b', + ']': '\\x5d', + '^': '\\x5e', + '{': '\\x7b', + '|': '\\x7c', + '}': '\\x7d' +}; + +/** + * A function that can be used with String.replace.. + * @param {string} ch A single character matched by a compatible matcher. + * @return {string} A token in the output language. + * @private + */ +soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = function(ch) { + return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_[ch]; +}; + +/** + * Maps charcters to the escaped versions for the named escape directives. + * @type {Object.<string, string>} + * @private + */ +soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_ = { + '\x00': '\\0 ', + '\x08': '\\8 ', + '\x09': '\\9 ', + '\x0a': '\\a ', + '\x0b': '\\b ', + '\x0c': '\\c ', + '\x0d': '\\d ', + '\x22': '\\22 ', + '\x26': '\\26 ', + '\x27': '\\27 ', + '(': '\\28 ', + ')': '\\29 ', + '*': '\\2a ', + '\/': '\\2f ', + ':': '\\3a ', + ';': '\\3b ', + '\x3c': '\\3c ', + '\x3d': '\\3d ', + '\x3e': '\\3e ', + '@': '\\40 ', + '\\': '\\5c ', + '{': '\\7b ', + '}': '\\7d ', + '\x85': '\\85 ', + '\xa0': '\\a0 ', + '\u2028': '\\2028 ', + '\u2029': '\\2029 ' +}; + +/** + * A function that can be used with String.replace.. + * @param {string} ch A single character matched by a compatible matcher. + * @return {string} A token in the output language. + * @private + */ +soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_ = function(ch) { + return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_[ch]; +}; + +/** + * Maps charcters to the escaped versions for the named escape directives. + * @type {Object.<string, string>} + * @private + */ +soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = { + '\x00': '%00', + '\x01': '%01', + '\x02': '%02', + '\x03': '%03', + '\x04': '%04', + '\x05': '%05', + '\x06': '%06', + '\x07': '%07', + '\x08': '%08', + '\x09': '%09', + '\x0a': '%0A', + '\x0b': '%0B', + '\x0c': '%0C', + '\x0d': '%0D', + '\x0e': '%0E', + '\x0f': '%0F', + '\x10': '%10', + '\x11': '%11', + '\x12': '%12', + '\x13': '%13', + '\x14': '%14', + '\x15': '%15', + '\x16': '%16', + '\x17': '%17', + '\x18': '%18', + '\x19': '%19', + '\x1a': '%1A', + '\x1b': '%1B', + '\x1c': '%1C', + '\x1d': '%1D', + '\x1e': '%1E', + '\x1f': '%1F', + ' ': '%20', + '\x22': '%22', + '\x27': '%27', + '(': '%28', + ')': '%29', + '\x3c': '%3C', + '\x3e': '%3E', + '\\': '%5C', + '{': '%7B', + '}': '%7D', + '\x7f': '%7F', + '\x85': '%C2%85', + '\xa0': '%C2%A0', + '\u2028': '%E2%80%A8', + '\u2029': '%E2%80%A9', + '\uff01': '%EF%BC%81', + '\uff03': '%EF%BC%83', + '\uff04': '%EF%BC%84', + '\uff06': '%EF%BC%86', + '\uff07': '%EF%BC%87', + '\uff08': '%EF%BC%88', + '\uff09': '%EF%BC%89', + '\uff0a': '%EF%BC%8A', + '\uff0b': '%EF%BC%8B', + '\uff0c': '%EF%BC%8C', + '\uff0f': '%EF%BC%8F', + '\uff1a': '%EF%BC%9A', + '\uff1b': '%EF%BC%9B', + '\uff1d': '%EF%BC%9D', + '\uff1f': '%EF%BC%9F', + '\uff20': '%EF%BC%A0', + '\uff3b': '%EF%BC%BB', + '\uff3d': '%EF%BC%BD' +}; + +/** + * A function that can be used with String.replace.. + * @param {string} ch A single character matched by a compatible matcher. + * @return {string} A token in the output language. + * @private + */ +soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = function(ch) { + return soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_[ch]; +}; + +/** + * Matches characters that need to be escaped for the named directives. + * @type RegExp + * @private + */ +soy.esc.$$MATCHER_FOR_ESCAPE_HTML_ = /[\x00\x22\x26\x27\x3c\x3e]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @type RegExp + * @private + */ +soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_ = /[\x00\x22\x27\x3c\x3e]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @type RegExp + * @private + */ +soy.esc.$$MATCHER_FOR_ESCAPE_HTML_NOSPACE_ = /[\x00\x09-\x0d \x22\x26\x27\x2d\/\x3c-\x3e`\x85\xa0\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @type RegExp + * @private + */ +soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_NOSPACE_ = /[\x00\x09-\x0d \x22\x27\x2d\/\x3c-\x3e`\x85\xa0\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @type RegExp + * @private + */ +soy.esc.$$MATCHER_FOR_ESCAPE_JS_STRING_ = /[\x00\x08-\x0d\x22\x26\x27\/\x3c-\x3e\\\x85\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @type RegExp + * @private + */ +soy.esc.$$MATCHER_FOR_ESCAPE_JS_REGEX_ = /[\x00\x08-\x0d\x22\x24\x26-\/\x3a\x3c-\x3f\x5b-\x5e\x7b-\x7d\x85\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @type RegExp + * @private + */ +soy.esc.$$MATCHER_FOR_ESCAPE_CSS_STRING_ = /[\x00\x08-\x0d\x22\x26-\x2a\/\x3a-\x3e@\\\x7b\x7d\x85\xa0\u2028\u2029]/g; + +/** + * Matches characters that need to be escaped for the named directives. + * @type RegExp + * @private + */ +soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = /[\x00- \x22\x27-\x29\x3c\x3e\\\x7b\x7d\x7f\x85\xa0\u2028\u2029\uff01\uff03\uff04\uff06-\uff0c\uff0f\uff1a\uff1b\uff1d\uff1f\uff20\uff3b\uff3d]/g; + +/** + * A pattern that vets values produced by the named directives. + * @type RegExp + * @private + */ +soy.esc.$$FILTER_FOR_FILTER_CSS_VALUE_ = /^(?!-*(?:expression|(?:moz-)?binding))(?:[.#]?-?(?:[_a-z0-9-]+)(?:-[_a-z0-9-]+)*-?|-?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)(?:[a-z]{1,2}|%)?|!important|)$/i; + +/** + * A pattern that vets values produced by the named directives. + * @type RegExp + * @private + */ +soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_ = /^(?:(?:https?|mailto):|[^&:\/?#]*(?:[\/?#]|$))/i; + +/** + * A pattern that vets values produced by the named directives. + * @type RegExp + * @private + */ +soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTES_ = /^(?!style|on|action|archive|background|cite|classid|codebase|data|dsync|href|longdesc|src|usemap)(?:[a-z0-9_$:-]*)$/i; + +/** + * A pattern that vets values produced by the named directives. + * @type RegExp + * @private + */ +soy.esc.$$FILTER_FOR_FILTER_HTML_ELEMENT_NAME_ = /^(?!script|style|title|textarea|xmp|no)[a-z0-9_$:-]*$/i; + +/** + * A helper for the Soy directive |escapeHtml + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeHtmlHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_HTML_, + soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_); +}; + +/** + * A helper for the Soy directive |normalizeHtml + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$normalizeHtmlHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_, + soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_); +}; + +/** + * A helper for the Soy directive |escapeHtmlNospace + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeHtmlNospaceHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_HTML_NOSPACE_, + soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_); +}; + +/** + * A helper for the Soy directive |normalizeHtmlNospace + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$normalizeHtmlNospaceHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_NOSPACE_, + soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_); +}; + +/** + * A helper for the Soy directive |escapeJsString + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeJsStringHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_JS_STRING_, + soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_); +}; + +/** + * A helper for the Soy directive |escapeJsRegex + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeJsRegexHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_JS_REGEX_, + soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_); +}; + +/** + * A helper for the Soy directive |escapeCssString + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$escapeCssStringHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_ESCAPE_CSS_STRING_, + soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_); +}; + +/** + * A helper for the Soy directive |filterCssValue + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterCssValueHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_CSS_VALUE_.test(str)) { + return 'zSoyz'; + } + return str; +}; + +/** + * A helper for the Soy directive |normalizeUri + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$normalizeUriHelper = function(value) { + var str = String(value); + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_, + soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_); +}; + +/** + * A helper for the Soy directive |filterNormalizeUri + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterNormalizeUriHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_.test(str)) { + return '#zSoyz'; + } + return str.replace( + soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_, + soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_); +}; + +/** + * A helper for the Soy directive |filterHtmlAttributes + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterHtmlAttributesHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTES_.test(str)) { + return 'zSoyz'; + } + return str; +}; + +/** + * A helper for the Soy directive |filterHtmlElementName + * @param {*} value Can be of any type but will be coerced to a string. + * @return {string} The escaped text. + */ +soy.esc.$$filterHtmlElementNameHelper = function(value) { + var str = String(value); + if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ELEMENT_NAME_.test(str)) { + return 'zSoyz'; + } + return str; +}; + +/** + * Matches all tags, HTML comments, and DOCTYPEs in tag soup HTML. + * By removing these, and replacing any '<' or '>' characters with + * entities we guarantee that the result can be embedded into a + * an attribute without introducing a tag boundary. + * + * @type {RegExp} + * @private + */ +soy.esc.$$HTML_TAG_REGEX_ = /<(?:!|\/?([a-zA-Z][a-zA-Z0-9:\-]*))(?:[^>'"]|"[^"]*"|'[^']*')*>/g; + +/** + * Matches all occurrences of '<'. + * + * @type {RegExp} + * @private + */ +soy.esc.$$LT_REGEX_ = /</g; + +/** + * Maps lower-case names of innocuous tags to 1. + * + * @type {Object.<string,number>} + * @private + */ +soy.esc.$$SAFE_TAG_WHITELIST_ = {'b': 1, 'br': 1, 'em': 1, 'i': 1, 's': 1, 'sub': 1, 'sup': 1, 'u': 1}; + +// END GENERATED CODE |