Commits

Jonathan Doklovic committed 6881862

fixing soyutils.js

  • Participants
  • Parent commits 4e2ac2d

Comments (0)

Files changed (3)

atlassian-plugins-osgi-testrunner-bundle/src/main/resources/atlassian-plugin.xml

         <resource type="download" name="images/" location="/images"/>
 
         <resource type="download" name="it-test-console.css" location="/css/it-test-console.css"/>
+        <resource type="download" name="soyutils.js" location="/js/soyutils.js"/>
         <resource type="download" name="jquery.store.js" location="/js/jquery.store.js"/>
         <resource type="download" name="jquery.ba-bbq.js" location="/js/jquery.ba-bbq.js"/>
         <resource type="download" name="spin.js" location="/js/spin.js"/>
     <web-item name="dev toolbar test console link" key="dev-toolbar-testconsole-link" section="dev-toolbar-menu" weight="20">
         <!--<context-provider class="com.atlassian.plugins.osgi.test.util.ContextPathContextProvider" />-->
         <tooltip key="it.test.console.link.tooltip">UI to run tests in product</tooltip>
-        <label key="it.test.console.link.label">Plugins Test Console</label>
+        <label key="it.test.console.link.label">Plugin Test Console</label>
         <link linkId="dt-test-console-link">${app.baseUrl}/plugins/servlet/it-test-console</link>
     </web-item>
     

atlassian-plugins-osgi-testrunner-bundle/src/main/resources/it-test-console.properties

 it.test.console.actual=actual
 it.test.console.test-class=Test Class
 it.test.console.duration=Duration
-it.test.console.link.label=Plugins Test Console
+it.test.console.link.label=Plugin Test Console
 it.test.console.link.tooltip=UI to run tests in product

atlassian-plugins-osgi-testrunner-bundle/src/main/resources/js/soyutils.js

+/*
+ * 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 '$$'.
+ *
+ */
+
+
+// COPIED FROM nogoog_shim.js
+
+// Create closure namespaces.
+var goog = goog || {};
+
+
+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 {
+      /**
+       * @type {boolean}
+       */
+      HAS_JSCRIPT: typeof 'ScriptEngine' in this,
+      /**
+       * @type {boolean}
+       */
+      IS_OPERA: isOpera,
+      /**
+       * @type {boolean}
+       */
+      IS_IE: !isOpera && userAgent.indexOf('MSIE') != -1,
+      /**
+       * @type {boolean}
+       */
+      IS_WEBKIT: !isOpera && userAgent.indexOf('WebKit') != -1
+    };
+  })();
+}
+
+if (!goog.asserts) {
+  goog.asserts = {
+    /**
+     * @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.IS_WEBKIT
+        ? '<wbr></wbr>' : goog.userAgent.IS_OPERA ? '&shy;' : '<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;
+};
+
+
+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.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.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.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.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);
+  }
+};
+
+
+var soy = { esc: {} };
+var soydata = {};
+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.
+ * @enum {number}
+ */
+soydata.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: 0,
+
+  /**
+   * 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: 1,
+
+  /** A properly encoded portion of a URI. */
+  URI: 2,
+
+  /** An attribute name and value such as {@code dir="ltr"}. */
+  HTML_ATTRIBUTE: 3
+};
+
+
+/**
+ * A string-like object that carries a content-type.
+ * @param {string} content
+ * @constructor
+ * @private
+ */
+soydata.SanitizedContent = function(content) {
+  /**
+   * The textual content.
+   * @type {string}
+   */
+  this.content = content;
+};
+
+/** @type {soydata.SanitizedContentKind} */
+soydata.SanitizedContent.prototype.contentKind;
+
+/** @override */
+soydata.SanitizedContent.prototype.toString = function() {
+  return this.content;
+};
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.HTML}.
+ * @param {string} 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.
+ * @constructor
+ * @extends {soydata.SanitizedContent}
+ */
+soydata.SanitizedHtml = function(content) {
+  soydata.SanitizedContent.call(this, content);
+};
+goog.inherits(soydata.SanitizedHtml, soydata.SanitizedContent);
+
+/** @override */
+soydata.SanitizedHtml.prototype.contentKind = soydata.SanitizedContentKind.HTML;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.JS_STR_CHARS}.
+ * @param {string} content A string of JS that when evaled, produces a
+ *     value that does not depend on any sensitive data and has no side effects
+ *     <b>OR</b> a string of JS that does not reference any variables or have
+ *     any side effects not known statically to the app authors.
+ * @constructor
+ * @extends {soydata.SanitizedContent}
+ */
+soydata.SanitizedJsStrChars = function(content) {
+  soydata.SanitizedContent.call(this, content);
+};
+goog.inherits(soydata.SanitizedJsStrChars, soydata.SanitizedContent);
+
+/** @override */
+soydata.SanitizedJsStrChars.prototype.contentKind =
+    soydata.SanitizedContentKind.JS_STR_CHARS;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.URI}.
+ * @param {string} content A chunk of URI that the caller knows is safe to
+ *     emit in a template.
+ * @constructor
+ * @extends {soydata.SanitizedContent}
+ */
+soydata.SanitizedUri = function(content) {
+  soydata.SanitizedContent.call(this, content);
+};
+goog.inherits(soydata.SanitizedUri, soydata.SanitizedContent);
+
+/** @override */
+soydata.SanitizedUri.prototype.contentKind = soydata.SanitizedContentKind.URI;
+
+
+/**
+ * Content of type {@link soydata.SanitizedContentKind.HTML_ATTRIBUTE}.
+ * @param {string} content An attribute name and value, such as
+ *     {@code dir="ltr"}.
+ * @constructor
+ * @extends {soydata.SanitizedContent}
+ */
+soydata.SanitizedHtmlAttribute = function(content) {
+  soydata.SanitizedContent.call(this, content);
+};
+goog.inherits(soydata.SanitizedHtmlAttribute, soydata.SanitizedContent);
+
+/** @override */
+soydata.SanitizedHtmlAttribute.prototype.contentKind =
+    soydata.SanitizedContentKind.HTML_ATTRIBUTE;
+
+
+// -----------------------------------------------------------------------------
+// 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 data object to be passed when a template calls another,
+ * and needs to pass both original data and additional params. The returned
+ * object will contain both the original data and the additional params. If the
+ * same key appears in both, then the value from the additional params will be
+ * visible, while the value from the original data will be hidden. The original
+ * data object will be used, but not modified.
+ *
+ * @param {!Object} origData The original data to pass.
+ * @param {Object} additionalParams The additional params to pass.
+ * @return {Object} An augmented data object containing both the original data
+ *     and the additional params.
+ */
+soy.$$augmentData = function(origData, additionalParams) {
+
+  // Create a new object whose '__proto__' field is set to origData.
+  /** @constructor */
+  function TempCtor() {}
+  TempCtor.prototype = origData;
+  var newData = new TempCtor();
+
+  // Add the additional params to the new object.
+  for (var key in additionalParams) {
+    newData[key] = additionalParams[key];
+  }
+
+  return newData;
+};
+
+
+/**
+ * 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.$$getDelegateId = function(delTemplateName) {
+  return delTemplateName;
+};
+
+
+/**
+ * Map from registered delegate template id/name to the priority of the
+ * implementation.
+ * @type {Object}
+ * @private
+ */
+soy.$$DELEGATE_REGISTRY_PRIORITIES_ = {};
+
+/**
+ * Map from registered delegate template id/name to the implementation function.
+ * @type {Object}
+ * @private
+ */
+soy.$$DELEGATE_REGISTRY_FUNCTIONS_ = {};
+
+
+/**
+ * Registers a delegate implementation. If the same delegate template id/name
+ * 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/name to register.
+ * @param {number} delPriority The implementation's priority value.
+ * @param {Function} delFn The implementation function.
+ */
+soy.$$registerDelegateFn = function(delTemplateId, delPriority, delFn) {
+  var mapKey = 'key_' + delTemplateId;
+  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 same priority (id/name "' +
+        delTemplateId + '").');
+  } else {
+    // Registering lower-priority function: do nothing.
+  }
+};
+
+
+/**
+ * Retrieves the (highest-priority) implementation that has been registered for
+ * a given delegate template id/name. If no implementation has been registered
+ * for the id/name, 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/name to get.
+ * @return {Function} The retrieved implementation function.
+ */
+soy.$$getDelegateFn = function(delTemplateId) {
+  var delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId];
+  return delFn ? delFn : soy.$$EMPTY_TEMPLATE_FN_;
+};
+
+
+/**
+ * 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) {
+  if (typeof value === 'object' && value &&
+      value.contentKind === soydata.SanitizedContentKind.HTML) {
+    return value.content;
+  }
+  return soy.esc.$$escapeHtmlHelper(value);
+};
+
+
+/**
+ * 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 (typeof value === 'object' && value &&
+      value.contentKind === soydata.SanitizedContentKind.HTML) {
+    return soy.esc.$$normalizeHtmlHelper(value.content);
+  }
+  return soy.esc.$$escapeHtmlHelper(value);
+};
+
+
+/**
+ * Removes HTML tags from a string of known safe HTML so it 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.
+ * @return {string} A representation of value without tags, HTML comments, or
+ *     other content.
+ */
+soy.$$stripHtmlTags = function(value) {
+  return String(value).replace(soy.esc.$$HTML_TAG_REGEX_, '');
+};
+
+
+/**
+ * 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 (typeof value === 'object' && value &&
+      value.contentKind === soydata.SanitizedContentKind.HTML) {
+    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 (typeof value === 'object' && value &&
+      value.contentKind === soydata.SanitizedContentKind.HTML) {
+    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.
+ *
+ * @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.$$filterHtmlAttribute = function(value) {
+  if (typeof value === 'object' && value &&
+      value.contentKind === soydata.SanitizedContentKind.HTML_ATTRIBUTE) {
+    return value.content.replace(/=([^"']*)$/, '="$1"');
+  }
+  return soy.esc.$$filterHtmlAttributeHelper(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) {
+  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 (typeof value === 'object' &&
+      value.contentKind === soydata.SanitizedContentKind.JS_STR_CHARS) {
+    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 ';
+  }
+  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 (typeof value === 'object' &&
+      value.contentKind === soydata.SanitizedContentKind.URI) {
+    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) {
+  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) {
+  // Uses == to intentionally match null and undefined for Java compatibility.
+  if (value == null) {
+    return '';
+  }
+  return soy.esc.$$filterCssValueHelper(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 new soydata.SanitizedHtmlAttribute(
+      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);
+};
+