Path: blob/trunk/third_party/closure/goog/soy/soy.js
2868 views
// Copyright 2011 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Provides utility methods to render soy template.16* @author [email protected] (Chris Henry)17*/1819goog.provide('goog.soy');2021goog.require('goog.asserts');22goog.require('goog.dom');23goog.require('goog.dom.NodeType');24goog.require('goog.dom.TagName');25goog.require('goog.html.legacyconversions');26goog.require('goog.soy.data.SanitizedContent');27goog.require('goog.soy.data.SanitizedContentKind');28goog.require('goog.string');293031/**32* @define {boolean} Whether to require all Soy templates to be "strict html".33* Soy templates that use strict autoescaping forbid noAutoescape along with34* many dangerous directives, and return a runtime type SanitizedContent that35* marks them as safe.36*37* If this flag is enabled, Soy templates will fail to render if a template38* returns plain text -- indicating it is a non-strict template.39*/40goog.define('goog.soy.REQUIRE_STRICT_AUTOESCAPE', false);414243/**44* Type definition for strict Soy templates. Very useful when passing a template45* as an argument.46* @typedef {function(?, null=, ?Object<string, *>=):47* !goog.soy.data.SanitizedContent}48*/49goog.soy.StrictTemplate;505152/**53* Type definition for strict Soy HTML templates. Very useful when passing54* a template as an argument.55* @typedef {function(?, null=, ?Object<string, *>=):56* !goog.soy.data.SanitizedHtml}57*/58goog.soy.StrictHtmlTemplate;596061/**62* Sets the processed template as the innerHTML of an element. It is recommended63* to use this helper function instead of directly setting innerHTML in your64* hand-written code, so that it will be easier to audit the code for cross-site65* scripting vulnerabilities.66*67* @param {?Element} element The element whose content we are rendering into.68* @param {!goog.soy.data.SanitizedContent} templateResult The processed69* template of kind HTML or TEXT (which will be escaped).70* @template ARG_TYPES71*/72goog.soy.renderHtml = function(element, templateResult) {73element.innerHTML = goog.soy.ensureTemplateOutputHtml_(templateResult);74};757677/**78* Renders a Soy template and then set the output string as79* the innerHTML of an element. It is recommended to use this helper function80* instead of directly setting innerHTML in your hand-written code, so that it81* will be easier to audit the code for cross-site scripting vulnerabilities.82*83* @param {Element} element The element whose content we are rendering into.84* @param {?function(ARG_TYPES, Object<string, *>=):*|85* ?function(ARG_TYPES, null=, Object<string, *>=):*} template86* The Soy template defining the element's content.87* @param {ARG_TYPES=} opt_templateData The data for the template.88* @param {Object=} opt_injectedData The injected data for the template.89* @template ARG_TYPES90*/91goog.soy.renderElement = function(92element, template, opt_templateData, opt_injectedData) {93// Soy template parameter is only nullable for historical reasons.94goog.asserts.assert(template, 'Soy template may not be null.');95element.innerHTML = goog.soy.ensureTemplateOutputHtml_(96template(97opt_templateData || goog.soy.defaultTemplateData_, undefined,98opt_injectedData));99};100101102/**103* Renders a Soy template into a single node or a document104* fragment. If the rendered HTML string represents a single node, then that105* node is returned (note that this is *not* a fragment, despite them name of106* the method). Otherwise a document fragment is returned containing the107* rendered nodes.108*109* @param {?function(ARG_TYPES, Object<string, *>=):*|110* ?function(ARG_TYPES, null=, Object<string, *>=):*} template111* The Soy template defining the element's content.112* @param {ARG_TYPES=} opt_templateData The data for the template.113* @param {Object=} opt_injectedData The injected data for the template.114* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper used to115* create DOM nodes; defaults to {@code goog.dom.getDomHelper}.116* @return {!Node} The resulting node or document fragment.117* @template ARG_TYPES118*/119goog.soy.renderAsFragment = function(120template, opt_templateData, opt_injectedData, opt_domHelper) {121// Soy template parameter is only nullable for historical reasons.122goog.asserts.assert(template, 'Soy template may not be null.');123var dom = opt_domHelper || goog.dom.getDomHelper();124var output = template(125opt_templateData || goog.soy.defaultTemplateData_, undefined,126opt_injectedData);127var html = goog.soy.ensureTemplateOutputHtml_(output);128goog.soy.assertFirstTagValid_(html);129var safeHtml = output instanceof goog.soy.data.SanitizedContent ?130output.toSafeHtml() :131goog.html.legacyconversions.safeHtmlFromString(html);132return dom.safeHtmlToNode(safeHtml);133};134135136/**137* Renders a Soy template into a single node. If the rendered138* HTML string represents a single node, then that node is returned. Otherwise,139* a DIV element is returned containing the rendered nodes.140*141* @param {?function(ARG_TYPES, Object<string, *>=):*|142* ?function(ARG_TYPES, null=, Object<string, *>=):*} template143* The Soy template defining the element's content.144* @param {ARG_TYPES=} opt_templateData The data for the template.145* @param {Object=} opt_injectedData The injected data for the template.146* @param {goog.dom.DomHelper=} opt_domHelper The DOM helper used to147* create DOM nodes; defaults to {@code goog.dom.getDomHelper}.148* @return {!Element} Rendered template contents, wrapped in a parent DIV149* element if necessary.150* @template ARG_TYPES151*/152goog.soy.renderAsElement = function(153template, opt_templateData, opt_injectedData, opt_domHelper) {154// Soy template parameter is only nullable for historical reasons.155goog.asserts.assert(template, 'Soy template may not be null.');156return goog.soy.convertToElement_(157template(158opt_templateData || goog.soy.defaultTemplateData_, undefined,159opt_injectedData),160opt_domHelper);161};162163164/**165* Converts a processed Soy template into a single node. If the rendered166* HTML string represents a single node, then that node is returned. Otherwise,167* a DIV element is returned containing the rendered nodes.168*169* @param {!goog.soy.data.SanitizedContent} templateResult The processed170* template of kind HTML or TEXT (which will be escaped).171* @param {?goog.dom.DomHelper=} opt_domHelper The DOM helper used to172* create DOM nodes; defaults to {@code goog.dom.getDomHelper}.173* @return {!Element} Rendered template contents, wrapped in a parent DIV174* element if necessary.175*/176goog.soy.convertToElement = function(templateResult, opt_domHelper) {177return goog.soy.convertToElement_(templateResult, opt_domHelper);178};179180181/**182* Non-strict version of {@code goog.soy.convertToElement}.183*184* @param {*} templateResult The processed template.185* @param {?goog.dom.DomHelper=} opt_domHelper The DOM helper used to186* create DOM nodes; defaults to {@code goog.dom.getDomHelper}.187* @return {!Element} Rendered template contents, wrapped in a parent DIV188* element if necessary.189* @private190*/191goog.soy.convertToElement_ = function(templateResult, opt_domHelper) {192var dom = opt_domHelper || goog.dom.getDomHelper();193var wrapper = dom.createElement(goog.dom.TagName.DIV);194var html = goog.soy.ensureTemplateOutputHtml_(templateResult);195goog.soy.assertFirstTagValid_(html);196wrapper.innerHTML = html;197198// If the template renders as a single element, return it.199if (wrapper.childNodes.length == 1) {200var firstChild = wrapper.firstChild;201if (firstChild.nodeType == goog.dom.NodeType.ELEMENT) {202return /** @type {!Element} */ (firstChild);203}204}205206// Otherwise, return the wrapper DIV.207return wrapper;208};209210211/**212* Ensures the result is "safe" to insert as HTML.213*214* Note if the template has non-strict autoescape, the guarantees here are very215* weak. It is recommended applications switch to requiring strict216* autoescaping over time by tweaking goog.soy.REQUIRE_STRICT_AUTOESCAPE.217*218* In the case the argument is a SanitizedContent object, it either must219* already be of kind HTML, or if it is kind="text", the output will be HTML220* escaped.221*222* @param {*} templateResult The template result.223* @return {string} The assumed-safe HTML output string.224* @private225*/226goog.soy.ensureTemplateOutputHtml_ = function(templateResult) {227// Allow strings as long as strict autoescaping is not mandated. Note we228// allow everything that isn't an object, because some non-escaping templates229// end up returning non-strings if their only print statement is a230// non-escaped argument, plus some unit tests spoof templates.231// TODO(gboyer): Track down and fix these cases.232if (!goog.soy.REQUIRE_STRICT_AUTOESCAPE && !goog.isObject(templateResult)) {233return String(templateResult);234}235236// Allow SanitizedContent of kind HTML.237if (templateResult instanceof goog.soy.data.SanitizedContent) {238templateResult =239/** @type {!goog.soy.data.SanitizedContent} */ (templateResult);240var ContentKind = goog.soy.data.SanitizedContentKind;241if (templateResult.contentKind === ContentKind.HTML) {242return goog.asserts.assertString(templateResult.getContent());243}244if (templateResult.contentKind === ContentKind.TEXT) {245// Allow text to be rendered, as long as we escape it. Other content246// kinds will fail, since we don't know what to do with them.247// TODO(gboyer): Perhaps also include URI in this case.248return goog.string.htmlEscape(templateResult.getContent());249}250}251252goog.asserts.fail(253'Soy template output is unsafe for use as HTML: ' + templateResult);254255// In production, return a safe string, rather than failing hard.256return 'zSoyz';257};258259260/**261* Checks that the rendered HTML does not start with an invalid tag that would262* likely cause unexpected output from renderAsElement or renderAsFragment.263* See {@link http://www.w3.org/TR/html5/semantics.html#semantics} for reference264* as to which HTML elements can be parents of each other.265* @param {string} html The output of a template.266* @private267*/268goog.soy.assertFirstTagValid_ = function(html) {269if (goog.asserts.ENABLE_ASSERTS) {270var matches = html.match(goog.soy.INVALID_TAG_TO_RENDER_);271goog.asserts.assert(272!matches, 'This template starts with a %s, which ' +273'cannot be a child of a <div>, as required by soy internals. ' +274'Consider using goog.soy.renderElement instead.\nTemplate output: %s',275matches && matches[0], html);276}277};278279280/**281* A pattern to find templates that cannot be rendered by renderAsElement or282* renderAsFragment, as these elements cannot exist as the child of a <div>.283* @type {!RegExp}284* @private285*/286goog.soy.INVALID_TAG_TO_RENDER_ =287/^<(body|caption|col|colgroup|head|html|tr|td|th|tbody|thead|tfoot)>/i;288289290/**291* Immutable object that is passed into templates that are rendered292* without any data.293* @private @const294*/295goog.soy.defaultTemplateData_ = {};296297298