closure 1.3.1 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. data/README.md +142 -9
  2. data/bin/closure-script +19 -0
  3. data/closure-compiler/README +18 -4
  4. data/closure-compiler/compiler.jar +0 -0
  5. data/closure-templates/SoyToJsSrcCompiler.jar +0 -0
  6. data/closure-templates/soydata.js +163 -0
  7. data/closure-templates/soyutils.js +1191 -159
  8. data/closure-templates/soyutils_usegoog.js +1107 -60
  9. data/docs/closure/Closure.html +58 -52
  10. data/docs/closure/Closure/BeanShell.html +6 -3
  11. data/docs/closure/Closure/Compiler.html +18 -15
  12. data/docs/closure/Closure/Compiler/Compilation.html +9 -3
  13. data/docs/closure/Closure/Compiler/Error.html +3 -3
  14. data/docs/closure/Closure/FileResponse.html +13 -7
  15. data/docs/closure/Closure/Goog.html +49 -85
  16. data/docs/closure/Closure/Middleware.html +5 -3
  17. data/docs/closure/Closure/Script.html +14 -5
  18. data/docs/closure/Closure/Script/NotFound.html +3 -3
  19. data/docs/closure/Closure/Script/RenderStackOverflow.html +3 -3
  20. data/docs/closure/Closure/Server.html +6 -3
  21. data/docs/closure/Closure/ShowExceptions.html +5 -3
  22. data/docs/closure/Closure/Sources.html +145 -37
  23. data/docs/closure/Closure/Templates.html +11 -10
  24. data/docs/closure/Closure/Templates/Error.html +3 -3
  25. data/docs/closure/_index.html +4 -4
  26. data/docs/closure/css/full_list.css +2 -0
  27. data/docs/closure/css/style.css +2 -0
  28. data/docs/closure/file.LICENSE.html +3 -3
  29. data/docs/closure/file.README.html +151 -10
  30. data/docs/closure/frames.html +1 -1
  31. data/docs/closure/index.html +151 -10
  32. data/docs/closure/js/full_list.js +23 -6
  33. data/docs/closure/method_list.html +91 -83
  34. data/docs/closure/top-level-namespace.html +3 -3
  35. data/lib/closure.rb +3 -16
  36. data/lib/closure/compiler.rb +135 -53
  37. data/lib/closure/goog.rb +5 -29
  38. data/lib/closure/sources.rb +22 -9
  39. data/lib/closure/version.rb +1 -1
  40. data/scripts/config.ru +0 -1
  41. data/scripts/hello/compiler_build.js +5 -5
  42. data/scripts/hello/compiler_build.map +518 -522
  43. data/scripts/hello/compiler_debug.js +7 -13
  44. data/scripts/hello/legume.js +2 -2
  45. data/scripts/index.erb +0 -3
  46. data/scripts/modules/compiler_build.js +3 -3
  47. data/scripts/modules/compiler_build.map +11569 -11476
  48. data/scripts/modules/compiler_build_api.js +1 -1
  49. data/scripts/modules/compiler_build_app.js +71 -70
  50. data/scripts/modules/compiler_build_settings.js +2 -2
  51. data/scripts/modules/compiler_debug.js +3 -3
  52. data/scripts/modules/compiler_debug_api.js +2 -2
  53. data/scripts/modules/compiler_debug_app.js +926 -1382
  54. data/scripts/modules/compiler_debug_settings.js +21 -24
  55. metadata +8 -18
  56. data/externs/chrome_extensions.externs +0 -968
  57. data/externs/jquery-1.3.2.externs +0 -718
  58. data/externs/jquery-1.4.3.externs +0 -1289
  59. data/externs/jquery-1.4.4.externs +0 -1302
  60. data/externs/jquery-1.5.externs +0 -1697
  61. data/externs/jquery-ui.externs +0 -10
  62. data/externs/jquery.externs +0 -4
  63. data/scripts/jquery/compiler.js.erb +0 -7
  64. data/scripts/jquery/compiler_out.js +0 -1
  65. data/scripts/jquery/index.erb +0 -25
  66. data/scripts/jquery/jquery_1.4.4.js +0 -167
  67. data/scripts/jquery/jquery_test.js +0 -8
data/README.md CHANGED
@@ -1,14 +1,147 @@
1
- Google Closure Compiler, Library, Script, and Templates.
1
+ # Closure Script
2
+
3
+ A development environment for Google Closure Tools.
2
4
 
3
5
  Licensed under the Apache License, Version 2.0 (the "License");
4
6
  <http://www.apache.org/licenses/LICENSE-2.0>
5
7
 
8
+ # Installing
9
+
10
+ Everything you need for advanced Google Closure development is available in a
11
+ single .jar for the Java Virtual Machine. You may also run the tools on any
12
+ Ruby platform (>=1.8.6) including JRuby (JVM), Rubinius (LLVM), and Ruby 1.9 (YARV).
13
+
14
+ It is generally easier to get started with the .jar distribution, especially
15
+ under Windows. Mac OSX and most Linux will have a compatible Ruby by default.
16
+
17
+ ## Java (.jar)
18
+
19
+ ### Step 1: Download to a new folder
20
+
21
+ cd ~/empty-dir
22
+ curl -LO https://github.com/downloads/dturnbull/closure-script/closure-1.4.2.jar
23
+
24
+ ### Step 2: Start server from the new folder
25
+
26
+ java -jar closure-1.4.2.jar
27
+
28
+ ### Step 3: Open a web browser
29
+
30
+ http://localhost:8080/
31
+
32
+
33
+ ## Ruby (.gem)
34
+
35
+ ### Step 1: Install the gem
36
+
37
+ gem install closure
38
+
39
+ ### Step 2: Start server from a new folder
40
+
41
+ cd ~/empty-dir
42
+ closure-script
43
+
44
+ ### Step 3: Open a web browser
45
+
46
+ http://localhost:8080/
47
+
48
+
49
+ # The Closure Script Method
50
+
51
+ When you start the server for the first time in an empty folder, the home page
52
+ will prompt you to install scaffolding. This includes three example projects to
53
+ demonstrate soy, modules, and unobtrusive markup. Dissecting and working with
54
+ these examples is the fast track to understanding The Closure Script Method.
55
+
56
+ ## The Server
57
+
58
+ Closure Script is a high-performance, multi-threaded web application engineered
59
+ exclusively for the needs of Google Closure Javascript development.
60
+
61
+ You will be freed from the command line. All error output from the compiler
62
+ will show on the Javascript console. This avoids lost time from not being
63
+ in the correct log and missing an important error. Javascript compilation
64
+ is done just-in-time and only when source files have changed.
65
+ No need for a separate build step; just refresh the browser. Not working?
66
+ Check your Javascript console. Then back to your editor.
67
+
68
+ ## Easy Configuration
69
+
70
+ You'll need to supply the directories where you have source Javascript and static files.
71
+ Ruby developers will recognize that Closure Script is Rack middleware. This makes it trivial
72
+ to include the Closure Script build tool in a Rails application. If you're not developing a
73
+ Ruby application, your ```config.ru``` will probably never be more complex than the following:
74
+
75
+ require 'closure'
76
+ Closure.add_source '.', '/'
77
+ use Closure::Middleware, 'index'
78
+ run Rack::File.new '.'
79
+
80
+ The add_source command may be duplicated for each source Javascript folder you want to
81
+ serve. The first argument is the local filesystem path, the second is the mount point
82
+ for the http server. Make sure not to accidentally serve more than one copy of
83
+ Closure Library per Closure Script server or you'll get an error.
84
+
85
+ ## Cut-and-Paste Ruby
86
+
87
+ In practice, all you do with Ruby is adjust the arguments to compiler.jar by
88
+ analyzing options on the URL query string. If you can handle conditionally appending
89
+ strings to an array in Ruby, then you're fully qualified to use Closure Script!
90
+ There's enough example code in the scaffolding to cut-and-paste your way to victory.
91
+
92
+ ### Demo Scripts
93
+
94
+ The Closure Script Method is to create various demo pages to drive development. You may
95
+ also choose to use your main application instead of Closure Script for your demo pages.
96
+
97
+ Files ending with .erb are Closure Scripts and will have their embedded Ruby evaluated
98
+ as they are served. Scripts may also render other Scripts and pass variables if you
99
+ need that complexity. Scripts default to a MIME type of text/html so ```demo.erb``` is
100
+ the same as ```demo.html.erb```.
101
+
102
+ <html>
103
+ <head>
104
+ <script src='compiler.js?<%= query_string %>'></script>
105
+ </head>
106
+
107
+ ### Compiler Scripts
108
+
109
+ Compilation is performed by requesting a file that generates Javascript instead of HTML.
110
+ The goog.compile() function of Closure Script handles everything for you.
111
+
112
+ Note that goog.compile() does not simply call the compiler. It will monitor your source
113
+ files and skip calling the compiler if everything is up to date. The Java process will
114
+ remain running on a REPL so subsequent compilations don't pay the Java startup cost.
115
+ The dependency tree for all your sources is known so you can build from namespaces
116
+ (--ns) as well as files (--js). Modules have been automated to find common dependencies,
117
+ like plovr, and work from namespaces so you don't need to use filenames and counts.
118
+ The luxurious goog.compile() can serve up a loader for the raw, uncompiled files,
119
+ even when working with modules.
120
+
121
+ A very simple compiler.js.erb is as follows. Check the scaffold for practical examples
122
+ that use the query string.
123
+
124
+ <%
125
+ args = %w{
126
+ --compilation_level ADVANCED_OPTIMIZATIONS
127
+ --js_output_file compiler_build.js
128
+ --ns myapp.helloWorld
129
+ }
130
+ @response = goog.compile(args).to_response
131
+ %>
132
+
133
+ ### Testing
134
+
135
+ Closure Script helps with testing because it can see your data in ways that
136
+ browsers are not allowed to. The ```alltests.js``` file in Closure Library is
137
+ generated by a program that scans the filesystem. Here's a replacement in
138
+ Closure Script so that a manual build step never has to be executed again:
6
139
 
7
- Step 1: Download to a new folder:
8
- <https://github.com/dturnbull/closure-script/downloads>
9
-
10
- Step 2: Run from the new folder with Java:
11
- ```java -jar closure.jar```
12
-
13
- Step 3: Open a web browser:
14
- ```http://localhost:8080/```
140
+ <% all_test_files = Dir.glob expand_path '**/*_test.html'
141
+ json_strings = all_test_files.map { |x| relative_src(x).dump }
142
+ -%>var _allTests = [<%= json_strings.join(',') %>];
143
+
144
+ Since all of Ruby is at your disposal, you could even pull fixture data from SQL
145
+ or a web service. Perhaps a fixture refresh happens when the developer pushes a
146
+ form button. The svn.erb tool is a complex example that uses threads and a
147
+ background process. You're only limited by your imagination.
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'rubygems'
4
+ rescue LoadError
5
+ end
6
+ require 'rack'
7
+ closure_lib_path = File.expand_path('../lib', File.dirname(__FILE__))
8
+ if File.exist? File.join closure_lib_path, 'closure.rb'
9
+ $LOAD_PATH.unshift(closure_lib_path) if !$LOAD_PATH.include?(closure_lib_path)
10
+ end
11
+ if File.exist? 'config.ru'
12
+ Rack::Server.start
13
+ else
14
+ require 'closure'
15
+ ENV["CLOSURE_SCRIPT_WELCOME"] = 'true'
16
+ Rack::Server.start :config => File.join(Closure.base_path, 'scripts/config.ru')
17
+ end
18
+
19
+
@@ -152,7 +152,7 @@ system have been added.
152
152
 
153
153
  -----
154
154
  Code in:
155
- lib/libtrunk_rhino_parser_jarjared.jar
155
+ lib/rhino
156
156
 
157
157
  Rhino
158
158
  URL: http://www.mozilla.org/rhino
@@ -161,9 +161,8 @@ License: Netscape Public License and MPL / GPL dual license
161
161
 
162
162
  Description: Mozilla Rhino is an implementation of JavaScript for the JVM.
163
163
 
164
- Local Modifications: None. We've used JarJar to renamespace the code
165
- post-compilation. See:
166
- http://code.google.com/p/jarjar/
164
+ Local Modifications: Minor changes to parsing JSDoc that usually get pushed
165
+ up-stream to Rhino trunk.
167
166
 
168
167
 
169
168
  -----
@@ -210,6 +209,21 @@ Description: Annotations for software defect detection.
210
209
  Local Modifications: None.
211
210
 
212
211
 
212
+ -----
213
+ Code in:
214
+ lib/jarjar.jar
215
+
216
+ Jar Jar Links
217
+ URL: http://jarjar.googlecode.com/
218
+ Version: 1.1
219
+ License: Apache License 2.0
220
+
221
+ Description:
222
+ A utility for repackaging Java libraries.
223
+
224
+ Local Modifications: None.
225
+
226
+
213
227
  ----
214
228
  Code in:
215
229
  lib/junit.jar
@@ -0,0 +1,163 @@
1
+ /*
2
+ * Copyright 2010 Google Inc.
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+
18
+ /**
19
+ * @fileoverview
20
+ * Defines typed strings, e.g. an HTML string {@code "a<b>c"} is
21
+ * semantically distinct from the plain text string {@code "a<b>c"} and smart
22
+ * templates can take that distinction into account.
23
+ *
24
+ * @author Mike Samuel
25
+ */
26
+
27
+
28
+ goog.provide('soydata');
29
+ goog.provide('soydata.SanitizedHtml');
30
+ goog.provide('soydata.SanitizedHtmlAttribute');
31
+ goog.provide('soydata.SanitizedJsStrChars');
32
+ goog.provide('soydata.SanitizedUri');
33
+
34
+
35
+ /**
36
+ * A type of textual content.
37
+ * @enum
38
+ */
39
+ soydata.SanitizedContentKind = {
40
+
41
+ /**
42
+ * A snippet of HTML that does not start or end inside a tag, comment, entity,
43
+ * or DOCTYPE; and that does not contain any executable code
44
+ * (JS, {@code <object>}s, etc.) from a different trust domain.
45
+ */
46
+ HTML: 0,
47
+
48
+ /**
49
+ * A sequence of code units that can appear between quotes (either kind) in a
50
+ * JS program without causing a parse error, and without causing any side
51
+ * effects.
52
+ * <p>
53
+ * The content should not contain unescaped quotes, newlines, or anything else
54
+ * that would cause parsing to fail or to cause a JS parser to finish the
55
+ * string its parsing inside the content.
56
+ * <p>
57
+ * The content must also not end inside an escape sequence ; no partial octal
58
+ * escape sequences or odd number of '{@code \}'s at the end.
59
+ */
60
+ JS_STR_CHARS: 1,
61
+
62
+ /** A properly encoded portion of a URI. */
63
+ URI: 2,
64
+
65
+ /** An attribute name and value such as {@code dir="ltr"}. */
66
+ HTML_ATTRIBUTE: 3
67
+ };
68
+
69
+
70
+ /**
71
+ * A string-like object that carries a content-type.
72
+ * @constructor
73
+ * @private
74
+ */
75
+ soydata.SanitizedContent = function() {};
76
+
77
+ /** The textual content. @type {string} */
78
+ soydata.SanitizedContent.prototype.content;
79
+
80
+ /** @type {soydata.SanitizedContentKind} */
81
+ soydata.SanitizedContent.prototype.contentKind;
82
+
83
+ /**
84
+ * @return {string}
85
+ */
86
+ soydata.SanitizedContent.prototype.toString = function() {
87
+ return this.content;
88
+ };
89
+
90
+
91
+ /**
92
+ * Content of type {@link soydata.SanitizedContentKind.HTML}.
93
+ * @constructor
94
+ * @param {string!} content A string of HTML that can safely be embedded in
95
+ * a PCDATA context in your app. If you would be surprised to find that an
96
+ * HTML sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs)
97
+ * and you wouldn't write a template that produces {@code s} on security or
98
+ * privacy grounds, then don't pass {@code s} here.
99
+ */
100
+ soydata.SanitizedHtml = function(content) {
101
+ this.content = content;
102
+ };
103
+
104
+ /** @override */
105
+ soydata.SanitizedHtml.prototype = new soydata.SanitizedContent();
106
+
107
+ /** @override */
108
+ soydata.SanitizedHtml.prototype.contentKind = soydata.SanitizedContentKind.HTML;
109
+
110
+
111
+ /**
112
+ * Content of type {@link soydata.SanitizedContentKind.JS_STR_CHARS}.
113
+ * @constructor
114
+ * @param {string!} content A string of JS that when evaled, produces a
115
+ * value that does not depend on any sensitive data and has no side effects
116
+ * <b>OR</b> a string of JS that does not reference any variables or have
117
+ * any side effects not known statically to the app authors.
118
+ */
119
+ soydata.SanitizedJsStrChars = function(content) {
120
+ this.content = content;
121
+ };
122
+
123
+ /** @override */
124
+ soydata.SanitizedJsStrChars.prototype = new soydata.SanitizedContent();
125
+
126
+ /** @override */
127
+ soydata.SanitizedJsStrChars.prototype.contentKind =
128
+ soydata.SanitizedContentKind.JS_STR_CHARS;
129
+
130
+
131
+ /**
132
+ * Content of type {@link soydata.SanitizedContentKind.URI}.
133
+ * @constructor
134
+ * @param {string!} content A chunk of URI that the caller knows is safe to
135
+ * emit in a template.
136
+ */
137
+ soydata.SanitizedUri = function(content) {
138
+ this.content = content;
139
+ };
140
+
141
+ /** @override */
142
+ soydata.SanitizedUri.prototype = new soydata.SanitizedContent();
143
+
144
+ /** @override */
145
+ soydata.SanitizedUri.prototype.contentKind = soydata.SanitizedContentKind.URI;
146
+
147
+
148
+ /**
149
+ * Content of type {@link soydata.SanitizedContentKind.HTML_ATTRIBUTE}.
150
+ * @constructor
151
+ * @param {string!} content An attribute name and value, such as
152
+ * {@code dir="ltr"}..
153
+ */
154
+ soydata.SanitizedHtmlAttribute = function(content) {
155
+ this.content = content;
156
+ };
157
+
158
+ /** @override */
159
+ soydata.SanitizedHtmlAttribute.prototype = new soydata.SanitizedContent();
160
+
161
+ /** @override */
162
+ soydata.SanitizedHtmlAttribute.prototype.contentKind =
163
+ soydata.SanitizedContentKind.HTML_ATTRIBUTE;
@@ -14,16 +14,26 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- // Utility functions and classes for Soy.
18
- //
19
- // The top portion of this file contains utilities for Soy users:
20
- // + soy.StringBuilder: Compatible with the 'stringbuilder' code style.
21
- // + soy.renderElement: Render template and set as innerHTML of an element.
22
- // + soy.renderAsFragment: Render template and return as HTML fragment.
23
- //
24
- // The bottom portion of this file contains utilities that should only be called
25
- // by Soy-generated JS code. Please do not use these functions directly from
26
- // your hand-writen code. Their names all start with '$$'.
17
+ /**
18
+ * @fileoverview
19
+ * Utility functions and classes for Soy.
20
+ *
21
+ * <p>
22
+ * The top portion of this file contains utilities for Soy users:<ul>
23
+ * <li> soy.StringBuilder: Compatible with the 'stringbuilder' code style.
24
+ * <li> soy.renderElement: Render template and set as innerHTML of an element.
25
+ * <li> soy.renderAsFragment: Render template and return as HTML fragment.
26
+ * </ul>
27
+ *
28
+ * <p>
29
+ * The bottom portion of this file contains utilities that should only be called
30
+ * by Soy-generated JS code. Please do not use these functions directly from
31
+ * your hand-writen code. Their names all start with '$$'.
32
+ *
33
+ * @author Mike Samuel
34
+ * @author Kai Huang
35
+ * @author Aharon Lenin
36
+ */
27
37
 
28
38
  /**
29
39
  * Base name for the soy utilities, when used outside of Closure Library.
@@ -32,6 +42,7 @@
32
42
  * @type {Object}
33
43
  */
34
44
  var soy = soy || {};
45
+ soy.esc = soy.esc || {};
35
46
 
36
47
 
37
48
  // Just enough browser detection for this file.
@@ -42,17 +53,17 @@ var soy = soy || {};
42
53
  * @type {boolean}
43
54
  * @private
44
55
  */
45
- soy.IS_OPERA_ = isOpera;
56
+ soy.$$IS_OPERA_ = isOpera;
46
57
  /**
47
58
  * @type {boolean}
48
59
  * @private
49
60
  */
50
- soy.IS_IE_ = !isOpera && ua.indexOf('MSIE') != -1;
61
+ soy.$$IS_IE_ = !isOpera && ua.indexOf('MSIE') != -1;
51
62
  /**
52
63
  * @type {boolean}
53
64
  * @private
54
65
  */
55
- soy.IS_WEBKIT_ = !isOpera && ua.indexOf('WebKit') != -1;
66
+ soy.$$IS_WEBKIT_ = !isOpera && ua.indexOf('WebKit') != -1;
56
67
  })();
57
68
 
58
69
 
@@ -67,7 +78,7 @@ var soy = soy || {};
67
78
  *
68
79
  * @param {Object|number|string|boolean=} opt_a1 Optional first initial item
69
80
  * to append.
70
- * @param {Object|number|string|boolean} var_args Other initial items to
81
+ * @param {...Object|number|string|boolean} var_args Other initial items to
71
82
  * append, e.g., new soy.StringBuilder('foo', 'bar').
72
83
  * @constructor
73
84
  */
@@ -78,7 +89,7 @@ soy.StringBuilder = function(opt_a1, var_args) {
78
89
  * @type {string|Array}
79
90
  * @private
80
91
  */
81
- this.buffer_ = soy.IS_IE_ ? [] : '';
92
+ this.buffer_ = soy.$$IS_IE_ ? [] : '';
82
93
 
83
94
  if (opt_a1 != null) {
84
95
  this.append.apply(this, arguments);
@@ -102,19 +113,20 @@ soy.StringBuilder.prototype.bufferLength_ = 0;
102
113
  *
103
114
  * @param {Object|number|string|boolean} a1 Required first string.
104
115
  * @param {Object|number|string|boolean=} opt_a2 Optional second string.
105
- * @param {Object|number|string|boolean} var_args Other items to append,
116
+ * @param {...Object|number|string|boolean} var_args Other items to append,
106
117
  * e.g., sb.append('foo', 'bar', 'baz').
107
118
  * @return {soy.StringBuilder} This same StringBuilder object.
108
119
  */
109
120
  soy.StringBuilder.prototype.append = function(a1, opt_a2, var_args) {
110
121
 
111
- if (soy.IS_IE_) {
122
+ if (soy.$$IS_IE_) {
112
123
  if (opt_a2 == null) { // no second argument (note: undefined == null)
113
124
  // Array assignment is 2x faster than Array push. Also, use a1
114
125
  // directly to avoid arguments instantiation, another 2x improvement.
115
126
  this.buffer_[this.bufferLength_++] = a1;
116
127
  } else {
117
- this.buffer_.push.apply(this.buffer_, arguments);
128
+ var arr = /**@type {Array.<number|string|boolean>}*/this.buffer_;
129
+ arr.push.apply(arr, arguments);
118
130
  this.bufferLength_ = this.buffer_.length;
119
131
  }
120
132
 
@@ -138,7 +150,7 @@ soy.StringBuilder.prototype.append = function(a1, opt_a2, var_args) {
138
150
  */
139
151
  soy.StringBuilder.prototype.clear = function() {
140
152
 
141
- if (soy.IS_IE_) {
153
+ if (soy.$$IS_IE_) {
142
154
  this.buffer_.length = 0; // reuse array to avoid creating new object
143
155
  this.bufferLength_ = 0;
144
156
 
@@ -155,7 +167,7 @@ soy.StringBuilder.prototype.clear = function() {
155
167
  */
156
168
  soy.StringBuilder.prototype.toString = function() {
157
169
 
158
- if (soy.IS_IE_) {
170
+ if (soy.$$IS_IE_) {
159
171
  var str = this.buffer_.join('');
160
172
  // Given a string with the entire contents, simplify the StringBuilder by
161
173
  // setting its contents to only be this string, rather than many fragments.
@@ -175,6 +187,16 @@ soy.StringBuilder.prototype.toString = function() {
175
187
  // Public utilities.
176
188
 
177
189
 
190
+ /**
191
+ * Immutable object that is passed into templates that are rendered
192
+ * without any data.
193
+ *
194
+ * @type {Object}
195
+ * @private
196
+ */
197
+ soy.$$DEFAULT_TEMPLATE_DATA_ = {};
198
+
199
+
178
200
  /**
179
201
  * Helper function to render a Soy template and then set the output string as
180
202
  * the innerHTML of an element. It is recommended to use this helper function
@@ -184,35 +206,104 @@ soy.StringBuilder.prototype.toString = function() {
184
206
  * @param {Element} element The element whose content we are rendering.
185
207
  * @param {Function} template The Soy template defining the element's content.
186
208
  * @param {Object=} opt_templateData The data for the template.
209
+ * @param {Object=} opt_injectedData The injected data for the template.
187
210
  */
188
- soy.renderElement = function(element, template, opt_templateData) {
189
- element.innerHTML = template(opt_templateData);
211
+ soy.renderElement = function(
212
+ element, template, opt_templateData, opt_injectedData) {
213
+ element.innerHTML = template(
214
+ opt_templateData || soy.$$DEFAULT_TEMPLATE_DATA_, undefined,
215
+ opt_injectedData);
190
216
  };
191
217
 
192
218
 
193
219
  /**
194
220
  * Helper function to render a Soy template into a single node or a document
195
221
  * fragment. If the rendered HTML string represents a single node, then that
196
- * node is returned. Otherwise a document fragment is returned containing the
222
+ * node is returned (note that this is *not* a fragment, despite them name of
223
+ * the method). Otherwise a document fragment is returned containing the
197
224
  * rendered nodes.
198
225
  *
199
226
  * @param {Function} template The Soy template defining the element's content.
200
227
  * @param {Object=} opt_templateData The data for the template.
201
- * @return {Node} The resulting node or document fragment.
228
+ * @param {Document=} opt_document The document used to create DOM nodes. If not
229
+ * specified, global document object is used.
230
+ * @param {Object=} opt_injectedData The injected data for the template.
231
+ * @return {!Node} The resulting node or document fragment.
202
232
  */
203
- soy.renderAsFragment = function(template, opt_templateData) {
233
+ soy.renderAsFragment = function(
234
+ template, opt_templateData, opt_document, opt_injectedData) {
235
+ return soy.$$renderWithWrapper_(
236
+ template, opt_templateData, opt_document, false /* asElement */,
237
+ opt_injectedData);
238
+ };
204
239
 
205
- var tempDiv = document.createElement('div');
206
- tempDiv.innerHTML = template(opt_templateData);
207
- if (tempDiv.childNodes.length == 1) {
208
- return tempDiv.firstChild;
209
- } else {
210
- var fragment = document.createDocumentFragment();
211
- while (tempDiv.firstChild) {
212
- fragment.appendChild(tempDiv.firstChild);
240
+
241
+ /**
242
+ * Helper function to render a Soy template into a single node. If the rendered
243
+ * HTML string represents a single node, then that node is returned. Otherwise,
244
+ * a DIV element is returned containing the rendered nodes.
245
+ *
246
+ * @param {Function} template The Soy template defining the element's content.
247
+ * @param {Object=} opt_templateData The data for the template.
248
+ * @param {Document=} opt_document The document used to create DOM nodes. If not
249
+ * specified, global document object is used.
250
+ * @param {Object=} opt_injectedData The injected data for the template.
251
+ * @return {!Element} Rendered template contents, wrapped in a parent DIV
252
+ * element if necessary.
253
+ */
254
+ soy.renderAsElement = function(
255
+ template, opt_templateData, opt_document, opt_injectedData) {
256
+ return /** @type {!Element} */ (soy.$$renderWithWrapper_(
257
+ template, opt_templateData, opt_document, true /* asElement */,
258
+ opt_injectedData));
259
+ };
260
+
261
+
262
+ /**
263
+ * Helper function to render a Soy template into a single node or a document
264
+ * fragment. If the rendered HTML string represents a single node, then that
265
+ * node is returned. Otherwise a document fragment is created and returned
266
+ * (wrapped in a DIV element if #opt_singleNode is true).
267
+ *
268
+ * @param {Function} template The Soy template defining the element's content.
269
+ * @param {Object=} opt_templateData The data for the template.
270
+ * @param {Document=} opt_document The document used to create DOM nodes. If not
271
+ * specified, global document object is used.
272
+ * @param {boolean=} opt_asElement Whether to wrap the fragment in an
273
+ * element if the template does not render a single element. If true, result
274
+ * is always an Element.
275
+ * @param {Object=} opt_injectedData The injected data for the template.
276
+ * @return {!Node} The resulting node or document fragment.
277
+ * @private
278
+ */
279
+ soy.$$renderWithWrapper_ = function(
280
+ template, opt_templateData, opt_document, opt_asElement, opt_injectedData) {
281
+
282
+ var doc = opt_document || document;
283
+ var wrapper = doc.createElement('div');
284
+ wrapper.innerHTML = template(
285
+ opt_templateData || soy.$$DEFAULT_TEMPLATE_DATA_, undefined,
286
+ opt_injectedData);
287
+
288
+ // If the template renders as a single element, return it.
289
+ if (wrapper.childNodes.length == 1) {
290
+ var firstChild = wrapper.firstChild;
291
+ if (!opt_asElement || firstChild.nodeType == 1 /* Element */) {
292
+ return /** @type {!Node} */ (firstChild);
213
293
  }
214
- return fragment;
215
294
  }
295
+
296
+ // If we're forcing it to be a single element, return the wrapper DIV.
297
+ if (opt_asElement) {
298
+ return wrapper;
299
+ }
300
+
301
+ // Otherwise, create and return a fragment.
302
+ var fragment = doc.createDocumentFragment();
303
+ while (wrapper.firstChild) {
304
+ fragment.appendChild(wrapper.firstChild);
305
+ }
306
+ return fragment;
216
307
  };
217
308
 
218
309
 
@@ -237,9 +328,9 @@ soy.$$augmentData = function(origData, additionalParams) {
237
328
 
238
329
  // Create a new object whose '__proto__' field is set to origData.
239
330
  /** @constructor */
240
- function tempCtor() {};
241
- tempCtor.prototype = origData;
242
- var newData = new tempCtor();
331
+ function TempCtor() {}
332
+ TempCtor.prototype = origData;
333
+ var newData = new TempCtor();
243
334
 
244
335
  // Add the additional params to the new object.
245
336
  for (var key in additionalParams) {
@@ -251,151 +342,530 @@ soy.$$augmentData = function(origData, additionalParams) {
251
342
 
252
343
 
253
344
  /**
254
- * Escapes HTML special characters in a string. Escapes double quote '"' in
345
+ * Gets the keys in a map as an array. There are no guarantees on the order.
346
+ * @param {Object} map The map to get the keys of.
347
+ * @return {Array.<string>} The array of keys in the given map.
348
+ */
349
+ soy.$$getMapKeys = function(map) {
350
+ var mapKeys = [];
351
+ for (var key in map) {
352
+ mapKeys.push(key);
353
+ }
354
+ return mapKeys;
355
+ };
356
+
357
+
358
+ /**
359
+ * Gets a consistent unique id for the given delegate template name. Two calls
360
+ * to this function will return the same id if and only if the input names are
361
+ * the same.
362
+ *
363
+ * <p> Important: This function must always be called with a string constant.
364
+ *
365
+ * <p> If Closure Compiler is not being used, then this is just this identity
366
+ * function. If Closure Compiler is being used, then each call to this function
367
+ * will be replaced with a short string constant, which will be consistent per
368
+ * input name.
369
+ *
370
+ * @param {string} delTemplateName The delegate template name for which to get a
371
+ * consistent unique id.
372
+ * @return {string} A unique id that is consistent per input name.
373
+ *
374
+ * @consistentIdGenerator
375
+ */
376
+ soy.$$getDelegateId = function(delTemplateName) {
377
+ return delTemplateName;
378
+ };
379
+
380
+
381
+ /**
382
+ * Map from registered delegate template id/name to the priority of the
383
+ * implementation.
384
+ * @type {Object}
385
+ * @private
386
+ */
387
+ soy.$$DELEGATE_REGISTRY_PRIORITIES_ = {};
388
+
389
+ /**
390
+ * Map from registered delegate template id/name to the implementation function.
391
+ * @type {Object}
392
+ * @private
393
+ */
394
+ soy.$$DELEGATE_REGISTRY_FUNCTIONS_ = {};
395
+
396
+
397
+ /**
398
+ * Registers a delegate implementation. If the same delegate template id/name
399
+ * has been registered previously, then priority values are compared and only
400
+ * the higher priority implementation is stored (if priorities are equal, an
401
+ * error is thrown).
402
+ *
403
+ * @param {string} delTemplateId The delegate template id/name to register.
404
+ * @param {number} delPriority The implementation's priority value.
405
+ * @param {Function} delFn The implementation function.
406
+ */
407
+ soy.$$registerDelegateFn = function(delTemplateId, delPriority, delFn) {
408
+ var mapKey = 'key_' + delTemplateId;
409
+ var currPriority = soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey];
410
+ if (currPriority === undefined || delPriority > currPriority) {
411
+ // Registering new or higher-priority function: replace registry entry.
412
+ soy.$$DELEGATE_REGISTRY_PRIORITIES_[mapKey] = delPriority;
413
+ soy.$$DELEGATE_REGISTRY_FUNCTIONS_[mapKey] = delFn;
414
+ } else if (delPriority == currPriority) {
415
+ // Registering same-priority function: error.
416
+ throw Error(
417
+ 'Encountered two active delegates with same priority (id/name "' +
418
+ delTemplateId + '").');
419
+ } else {
420
+ // Registering lower-priority function: do nothing.
421
+ }
422
+ };
423
+
424
+
425
+ /**
426
+ * Retrieves the (highest-priority) implementation that has been registered for
427
+ * a given delegate template id/name. If no implementation has been registered
428
+ * for the id/name, then returns an implementation that is equivalent to an
429
+ * empty template (i.e. rendered output would be empty string).
430
+ *
431
+ * @param {string} delTemplateId The delegate template id/name to get.
432
+ * @return {Function} The retrieved implementation function.
433
+ */
434
+ soy.$$getDelegateFn = function(delTemplateId) {
435
+ var delFn = soy.$$DELEGATE_REGISTRY_FUNCTIONS_['key_' + delTemplateId];
436
+ return delFn ? delFn : soy.$$EMPTY_TEMPLATE_FN_;
437
+ };
438
+
439
+
440
+ /**
441
+ * Private helper soy.$$getDelegateFn(). This is the empty template function
442
+ * that is returned whenever there's no delegate implementation found.
443
+ *
444
+ * @param {Object.<string, *>=} opt_data
445
+ * @param {soy.StringBuilder=} opt_sb
446
+ * @param {Object.<string, *>=} opt_ijData
447
+ * @return {string}
448
+ * @private
449
+ */
450
+ soy.$$EMPTY_TEMPLATE_FN_ = function(opt_data, opt_sb, opt_ijData) {
451
+ return '';
452
+ };
453
+
454
+
455
+ /**
456
+ * Used for temporary fix. See GenJsCodeVisitor.java.
457
+ * TODO: Remove when i18n plurals team provides a better # processing option.
458
+ * @param {string} str The string to escape.
459
+ * @return {string} The escaped string.
460
+ */
461
+ soy.$$tempHashEscape = function(str) {
462
+ return str.replace(soy.$$HASH_RE_, '__HashLit__');
463
+ };
464
+
465
+ /**
466
+ * Used by soy.$$tempHashEscape().
467
+ * @type {RegExp}
468
+ * @private
469
+ */
470
+ soy.$$HASH_RE_ = /#/g;
471
+
472
+
473
+ /**
474
+ * Used for temporary fix. See GenJsCodeVisitor.java.
475
+ * TODO: Remove when i18n plurals team provides a better # processing option.
476
+ * @param {string} str The string to unescape.
477
+ * @return {string} The unescaped string.
478
+ */
479
+ soy.$$tempHashUnescape = function(str) {
480
+ return str.replace(soy.$$HASH_ESCAPED_RE_, '#');
481
+ };
482
+
483
+ /**
484
+ * Used by soy.$$tempHashUnescape().
485
+ * @type {RegExp}
486
+ * @private
487
+ */
488
+ soy.$$HASH_ESCAPED_RE_ = /__HashLit__/g;
489
+
490
+
491
+ // -----------------------------------------------------------------------------
492
+ // Escape/filter/normalize.
493
+
494
+
495
+ /**
496
+ * Escapes HTML special characters in a string. Escapes double quote '"' in
255
497
  * addition to '&', '<', and '>' so that a string can be included in an HTML
256
498
  * tag attribute value within double quotes.
499
+ * Will emit known safe HTML as-is.
257
500
  *
258
- * @param {*} str The string to be escaped. Can be other types, but the value
259
- * will be coerced to a string.
260
- * @return {string} An escaped copy of the string.
261
- */
262
- soy.$$escapeHtml = function(str) {
501
+ * @param {*} value The string-like value to be escaped. May not be a string,
502
+ * but the value will be coerced to a string.
503
+ * @return {string} An escaped version of value.
504
+ */
505
+ soy.$$escapeHtml = function(value) {
506
+ if (typeof value === 'object' && value &&
507
+ value.contentKind === soydata.SanitizedContentKind.HTML) {
508
+ return value.content;
509
+ }
510
+ return soy.esc.$$escapeHtmlHelper(value);
511
+ };
263
512
 
264
- str = String(str);
265
513
 
266
- // This quick test helps in the case when there are no chars to replace, in
267
- // the worst case this makes barely a difference to the time taken.
268
- if (!soy.$$EscapeHtmlRe_.ALL_SPECIAL_CHARS.test(str)) {
269
- return str;
514
+ /**
515
+ * Escapes HTML special characters in a string so that it can be embedded in
516
+ * RCDATA.
517
+ * <p>
518
+ * Escapes HTML special characters so that the value will not prematurely end
519
+ * the body of a tag like {@code <textarea>} or {@code <title>}. RCDATA tags
520
+ * cannot contain other HTML entities, so it is not strictly necessary to escape
521
+ * HTML special characters except when part of that text looks like an HTML
522
+ * entity or like a close tag : {@code </textarea>}.
523
+ * <p>
524
+ * Will normalize known safe HTML to make sure that sanitized HTML (which could
525
+ * contain an innocuous {@code </textarea>} don't prematurely end an RCDATA
526
+ * element.
527
+ *
528
+ * @param {*} value The string-like value to be escaped. May not be a string,
529
+ * but the value will be coerced to a string.
530
+ * @return {string} An escaped version of value.
531
+ */
532
+ soy.$$escapeHtmlRcdata = function(value) {
533
+ if (typeof value === 'object' && value &&
534
+ value.contentKind === soydata.SanitizedContentKind.HTML) {
535
+ return soy.esc.$$normalizeHtmlHelper(value.content);
270
536
  }
537
+ return soy.esc.$$escapeHtmlHelper(value);
538
+ };
271
539
 
272
- // Since we're only checking one char at a time, we use String.indexOf(),
273
- // which is faster than RegExp.test(). Important: Must replace '&' first!
274
- if (str.indexOf('&') != -1) {
275
- str = str.replace(soy.$$EscapeHtmlRe_.AMP, '&amp;');
276
- }
277
- if (str.indexOf('<') != -1) {
278
- str = str.replace(soy.$$EscapeHtmlRe_.LT, '&lt;');
540
+
541
+ /**
542
+ * Removes HTML tags from a string of known safe HTML so it can be used as an
543
+ * attribute value.
544
+ *
545
+ * @param {*} value The HTML to be escaped. May not be a string, but the
546
+ * value will be coerced to a string.
547
+ * @return {string} A representation of value without tags, HTML comments, or
548
+ * other content.
549
+ */
550
+ soy.$$stripHtmlTags = function(value) {
551
+ return String(value).replace(soy.esc.$$HTML_TAG_REGEX_, '');
552
+ };
553
+
554
+
555
+ /**
556
+ * Escapes HTML special characters in an HTML attribute value.
557
+ *
558
+ * @param {*} value The HTML to be escaped. May not be a string, but the
559
+ * value will be coerced to a string.
560
+ * @return {string} An escaped version of value.
561
+ */
562
+ soy.$$escapeHtmlAttribute = function(value) {
563
+ if (typeof value === 'object' && value &&
564
+ value.contentKind === soydata.SanitizedContentKind.HTML) {
565
+ return soy.esc.$$normalizeHtmlHelper(soy.$$stripHtmlTags(value.content));
279
566
  }
280
- if (str.indexOf('>') != -1) {
281
- str = str.replace(soy.$$EscapeHtmlRe_.GT, '&gt;');
567
+ return soy.esc.$$escapeHtmlHelper(value);
568
+ };
569
+
570
+
571
+ /**
572
+ * Escapes HTML special characters in a string including space and other
573
+ * characters that can end an unquoted HTML attribute value.
574
+ *
575
+ * @param {*} value The HTML to be escaped. May not be a string, but the
576
+ * value will be coerced to a string.
577
+ * @return {string} An escaped version of value.
578
+ */
579
+ soy.$$escapeHtmlAttributeNospace = function(value) {
580
+ if (typeof value === 'object' && value &&
581
+ value.contentKind === soydata.SanitizedContentKind.HTML) {
582
+ return soy.esc.$$normalizeHtmlNospaceHelper(
583
+ soy.$$stripHtmlTags(value.content));
282
584
  }
283
- if (str.indexOf('"') != -1) {
284
- str = str.replace(soy.$$EscapeHtmlRe_.QUOT, '&quot;');
585
+ return soy.esc.$$escapeHtmlNospaceHelper(value);
586
+ };
587
+
588
+
589
+ /**
590
+ * Filters out strings that cannot be a substring of a valid HTML attribute.
591
+ *
592
+ * @param {*} value The value to escape. May not be a string, but the value
593
+ * will be coerced to a string.
594
+ * @return {string} A valid HTML attribute name part or name/value pair.
595
+ * {@code "zSoyz"} if the input is invalid.
596
+ */
597
+ soy.$$filterHtmlAttribute = function(value) {
598
+ if (typeof value === 'object' && value &&
599
+ value.contentKind === soydata.SanitizedContentKind.HTML_ATTRIBUTE) {
600
+ return value.content.replace(/=([^"']*)$/, '="$1"');
285
601
  }
286
- return str;
602
+ return soy.esc.$$filterHtmlAttributeHelper(value);
287
603
  };
288
604
 
605
+
289
606
  /**
290
- * Regular expressions used within escapeHtml().
291
- * @enum {RegExp}
292
- * @private
607
+ * Filters out strings that cannot be a substring of a valid HTML element name.
608
+ *
609
+ * @param {*} value The value to escape. May not be a string, but the value
610
+ * will be coerced to a string.
611
+ * @return {string} A valid HTML element name part.
612
+ * {@code "zSoyz"} if the input is invalid.
613
+ */
614
+ soy.$$filterHtmlElementName = function(value) {
615
+ return soy.esc.$$filterHtmlElementNameHelper(value);
616
+ };
617
+
618
+
619
+ /**
620
+ * Escapes characters in the value to make it valid content for a JS string
621
+ * literal.
622
+ *
623
+ * @param {*} value The value to escape. May not be a string, but the value
624
+ * will be coerced to a string.
625
+ * @return {string} An escaped version of value.
626
+ * @deprecated
293
627
  */
294
- soy.$$EscapeHtmlRe_ = {
295
- ALL_SPECIAL_CHARS: /[&<>\"]/,
296
- AMP: /&/g,
297
- LT: /</g,
298
- GT: />/g,
299
- QUOT: /\"/g
628
+ soy.$$escapeJs = function(value) {
629
+ return soy.$$escapeJsString(value);
300
630
  };
301
631
 
302
632
 
303
633
  /**
304
- * Escapes characters in the string to make it a valid content for a JS string literal.
634
+ * Escapes characters in the value to make it valid content for a JS string
635
+ * literal.
305
636
  *
306
- * @param {*} s The string to be escaped. Can be other types, but the value
637
+ * @param {*} value The value to escape. May not be a string, but the value
307
638
  * will be coerced to a string.
308
- * @return {string} An escaped copy of the string.
309
- */
310
- soy.$$escapeJs = function(s) {
311
- s = String(s);
312
- var sb = [];
313
- for (var i = 0; i < s.length; i++) {
314
- sb[i] = soy.$$escapeChar(s.charAt(i));
639
+ * @return {string} An escaped version of value.
640
+ */
641
+ soy.$$escapeJsString = function(value) {
642
+ if (typeof value === 'object' &&
643
+ value.contentKind === soydata.SanitizedContentKind.JS_STR_CHARS) {
644
+ return value.content;
315
645
  }
316
- return sb.join('');
646
+ return soy.esc.$$escapeJsStringHelper(value);
317
647
  };
318
648
 
319
649
 
320
650
  /**
321
- * Takes a character and returns the escaped string for that character. For
322
- * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
323
- * @param {string} c The character to escape.
324
- * @return {string} An escaped string representing {@code c}.
651
+ * Encodes a value as a JavaScript literal.
652
+ *
653
+ * @param {*} value The value to escape. May not be a string, but the value
654
+ * will be coerced to a string.
655
+ * @return {string} A JavaScript code representation of the input.
325
656
  */
326
- soy.$$escapeChar = function(c) {
327
- if (c in soy.$$escapeCharJs_) {
328
- return soy.$$escapeCharJs_[c];
657
+ soy.$$escapeJsValue = function(value) {
658
+ // We surround values with spaces so that they can't be interpolated into
659
+ // identifiers by accident.
660
+ // We could use parentheses but those might be interpreted as a function call.
661
+ if (value == null) { // Intentionally matches undefined.
662
+ // Java returns null from maps where there is no corresponding key while
663
+ // JS returns undefined.
664
+ // We always output null for compatibility with Java which does not have a
665
+ // distinct undefined value.
666
+ return ' null ';
329
667
  }
330
- var rv = c;
331
- var cc = c.charCodeAt(0);
332
- if (cc > 31 && cc < 127) {
333
- rv = c;
334
- } else {
335
- // tab is 9 but handled above
336
- if (cc < 256) {
337
- rv = '\\x';
338
- if (cc < 16 || cc > 256) {
339
- rv += '0';
340
- }
341
- } else {
342
- rv = '\\u';
343
- if (cc < 4096) { // \u1000
344
- rv += '0';
345
- }
346
- }
347
- rv += cc.toString(16).toUpperCase();
668
+ switch (typeof value) {
669
+ case 'boolean': case 'number':
670
+ return ' ' + value + ' ';
671
+ default:
672
+ return "'" + soy.esc.$$escapeJsStringHelper(String(value)) + "'";
348
673
  }
674
+ };
349
675
 
350
- return soy.$$escapeCharJs_[c] = rv;
676
+
677
+ /**
678
+ * Escapes characters in the string to make it valid content for a JS regular
679
+ * expression literal.
680
+ *
681
+ * @param {*} value The value to escape. May not be a string, but the value
682
+ * will be coerced to a string.
683
+ * @return {string} An escaped version of value.
684
+ */
685
+ soy.$$escapeJsRegex = function(value) {
686
+ return soy.esc.$$escapeJsRegexHelper(value);
351
687
  };
352
688
 
689
+
690
+ /**
691
+ * Takes a character and returns the escaped string for that character. For
692
+ * example escapeChar(String.fromCharCode(15)) -> "\\x0E".
693
+ * @param {string} c The character to escape.
694
+ * @return {string} An escaped string representing {@code c}.
695
+ */
696
+ soy.$$escapeChar = function(c) {
697
+ if (c in soy.$$escapeCharJs_) {
698
+ return soy.$$escapeCharJs_[c];
699
+ }
700
+ var rv = c;
701
+ var cc = c.charCodeAt(0);
702
+ if (cc > 31 && cc < 127) {
703
+ rv = c;
704
+ } else {
705
+ // tab is 9 but handled above
706
+ if (cc < 256) {
707
+ rv = '\\x';
708
+ if (cc < 16 || cc > 256) {
709
+ rv += '0';
710
+ }
711
+ } else {
712
+ rv = '\\u';
713
+ if (cc < 4096) { // \u1000
714
+ rv += '0';
715
+ }
716
+ }
717
+ rv += cc.toString(16).toUpperCase();
718
+ }
719
+
720
+ return soy.$$escapeCharJs_[c] = rv;
721
+ };
722
+
723
+ /**
724
+ * Character mappings used internally for soy.$$escapeJs
725
+ * @private
726
+ * @type {Object}
727
+ */
728
+ soy.$$escapeCharJs_ = {
729
+ '\b': '\\b',
730
+ '\f': '\\f',
731
+ '\n': '\\n',
732
+ '\r': '\\r',
733
+ '\t': '\\t',
734
+ '\x0B': '\\x0B', // '\v' is not supported in JScript
735
+ '"': '\\"',
736
+ '\'': '\\\'',
737
+ '\\': '\\\\'
738
+ };
739
+
740
+
353
741
  /**
354
- * Character mappings used internally for soy.$$escapeJs
742
+ * Matches all URI mark characters that conflict with HTML attribute delimiters
743
+ * or that cannot appear in a CSS uri.
744
+ * From <a href="http://www.w3.org/TR/CSS2/grammar.html">G.2: CSS grammar</a>
745
+ * <pre>
746
+ * url ([!#$%&*-~]|{nonascii}|{escape})*
747
+ * </pre>
748
+ *
749
+ * @type {RegExp}
355
750
  * @private
356
- * @type {Object}
357
751
  */
358
- soy.$$escapeCharJs_ = {
359
- '\b': '\\b',
360
- '\f': '\\f',
361
- '\n': '\\n',
362
- '\r': '\\r',
363
- '\t': '\\t',
364
- '\x0B': '\\x0B', // '\v' is not supported in JScript
365
- '"': '\\"',
366
- '\'': '\\\'',
367
- '\\': '\\\\'
368
- };
752
+ soy.$$problematicUriMarks_ = /['()]/g;
369
753
 
754
+ /**
755
+ * @param {string} ch A single character in {@link soy.$$problematicUriMarks_}.
756
+ * @return {string}
757
+ * @private
758
+ */
759
+ soy.$$pctEncode_ = function(ch) {
760
+ return '%' + ch.charCodeAt(0).toString(16);
761
+ };
370
762
 
371
763
  /**
372
764
  * Escapes a string so that it can be safely included in a URI.
373
765
  *
374
- * @param {*} str The string to be escaped. Can be other types, but the value
766
+ * @param {*} value The value to escape. May not be a string, but the value
767
+ * will be coerced to a string.
768
+ * @return {string} An escaped version of value.
769
+ */
770
+ soy.$$escapeUri = function(value) {
771
+ if (typeof value === 'object' &&
772
+ value.contentKind === soydata.SanitizedContentKind.URI) {
773
+ return soy.$$normalizeUri(value);
774
+ }
775
+ // Apostophes and parentheses are not matched by encodeURIComponent.
776
+ // They are technically special in URIs, but only appear in the obsolete mark
777
+ // production in Appendix D.2 of RFC 3986, so can be encoded without changing
778
+ // semantics.
779
+ var encoded = soy.esc.$$escapeUriHelper(value);
780
+ soy.$$problematicUriMarks_.lastIndex = 0;
781
+ if (soy.$$problematicUriMarks_.test(encoded)) {
782
+ return encoded.replace(soy.$$problematicUriMarks_, soy.$$pctEncode_);
783
+ }
784
+ return encoded;
785
+ };
786
+
787
+
788
+ /**
789
+ * Removes rough edges from a URI by escaping any raw HTML/JS string delimiters.
790
+ *
791
+ * @param {*} value The value to escape. May not be a string, but the value
792
+ * will be coerced to a string.
793
+ * @return {string} An escaped version of value.
794
+ */
795
+ soy.$$normalizeUri = function(value) {
796
+ return soy.esc.$$normalizeUriHelper(value);
797
+ };
798
+
799
+
800
+ /**
801
+ * Vets a URI's protocol and removes rough edges from a URI by escaping
802
+ * any raw HTML/JS string delimiters.
803
+ *
804
+ * @param {*} value The value to escape. May not be a string, but the value
805
+ * will be coerced to a string.
806
+ * @return {string} An escaped version of value.
807
+ */
808
+ soy.$$filterNormalizeUri = function(value) {
809
+ return soy.esc.$$filterNormalizeUriHelper(value);
810
+ };
811
+
812
+
813
+ /**
814
+ * Escapes a string so it can safely be included inside a quoted CSS string.
815
+ *
816
+ * @param {*} value The value to escape. May not be a string, but the value
817
+ * will be coerced to a string.
818
+ * @return {string} An escaped version of value.
819
+ */
820
+ soy.$$escapeCssString = function(value) {
821
+ return soy.esc.$$escapeCssStringHelper(value);
822
+ };
823
+
824
+
825
+ /**
826
+ * Encodes a value as a CSS identifier part, keyword, or quantity.
827
+ *
828
+ * @param {*} value The value to escape. May not be a string, but the value
375
829
  * will be coerced to a string.
376
- * @return {string} An escaped copy of the string.
377
- */
378
- soy.$$escapeUri = function(str) {
830
+ * @return {string} A safe CSS identifier part, keyword, or quanitity.
831
+ */
832
+ soy.$$filterCssValue = function(value) {
833
+ // Uses == to intentionally match null and undefined for Java compatibility.
834
+ if (value == null) {
835
+ return '';
836
+ }
837
+ return soy.esc.$$filterCssValueHelper(value);
838
+ };
839
+
840
+
841
+ // -----------------------------------------------------------------------------
842
+ // Basic directives/functions.
843
+
844
+
845
+ /**
846
+ * Converts \r\n, \r, and \n to <br>s
847
+ * @param {*} str The string in which to convert newlines.
848
+ * @return {string} A copy of {@code str} with converted newlines.
849
+ */
850
+ soy.$$changeNewlineToBr = function(str) {
379
851
 
380
852
  str = String(str);
381
853
 
382
- // Checking if the search matches before calling encodeURIComponent avoids an
383
- // extra allocation in IE6. This adds about 10us time in FF and a similiar
384
- // over head in IE6 for lower working set apps, but for large working set
385
- // apps, it saves about 70us per call.
386
- if (!soy.$$ENCODE_URI_REGEXP_.test(str)) {
387
- return encodeURIComponent(str);
388
- } else {
854
+ // This quick test helps in the case when there are no chars to replace, in
855
+ // the worst case this makes barely a difference to the time taken.
856
+ if (!soy.$$CHANGE_NEWLINE_TO_BR_RE_.test(str)) {
389
857
  return str;
390
858
  }
859
+
860
+ return str.replace(/(\r\n|\r|\n)/g, '<br>');
391
861
  };
392
862
 
393
863
  /**
394
- * Regular expression used for determining if a string needs to be encoded.
864
+ * Regular expression used within $$changeNewlineToBr().
395
865
  * @type {RegExp}
396
866
  * @private
397
867
  */
398
- soy.$$ENCODE_URI_REGEXP_ = /^[a-zA-Z0-9\-_.!~*'()]*$/;
868
+ soy.$$CHANGE_NEWLINE_TO_BR_RE_ = /[\r\n]/;
399
869
 
400
870
 
401
871
  /**
@@ -431,7 +901,7 @@ soy.$$insertWordBreaks = function(str, maxCharsBetweenWordBreaks) {
431
901
  charCode != soy.$$CharCode_.SPACE) {
432
902
  resultArr[resultArrLen++] = str.substring(flushIndex, i);
433
903
  flushIndex = i;
434
- resultArr[resultArrLen++] = soy.WORD_BREAK_;
904
+ resultArr[resultArrLen++] = soy.$$WORD_BREAK_;
435
905
  numCharsWithoutBreak = 0;
436
906
  }
437
907
 
@@ -496,7 +966,7 @@ soy.$$insertWordBreaks = function(str, maxCharsBetweenWordBreaks) {
496
966
  };
497
967
 
498
968
  /**
499
- * Special characters used within insertWordBreaks().
969
+ * Special characters used within $$insertWordBreaks().
500
970
  * @enum {number}
501
971
  * @private
502
972
  */
@@ -515,34 +985,103 @@ soy.$$CharCode_ = {
515
985
  * @type {string}
516
986
  * @private
517
987
  */
518
- soy.WORD_BREAK_ =
519
- soy.IS_WEBKIT_ ? '<wbr></wbr>' : soy.IS_OPERA_ ? '&shy;' : '<wbr>';
988
+ soy.$$WORD_BREAK_ =
989
+ soy.$$IS_WEBKIT_ ? '<wbr></wbr>' : soy.$$IS_OPERA_ ? '&shy;' : '<wbr>';
520
990
 
521
991
 
522
992
  /**
523
- * Converts \r\n, \r, and \n to <br>s
524
- * @param {*} str The string in which to convert newlines.
525
- * @return {string} A copy of {@code str} with converted newlines.
993
+ * Truncates a string to a given max length (if it's currently longer),
994
+ * optionally adding ellipsis at the end.
995
+ *
996
+ * @param {*} str The string to truncate. Can be other types, but the value will
997
+ * be coerced to a string.
998
+ * @param {number} maxLen The maximum length of the string after truncation
999
+ * (including ellipsis, if applicable).
1000
+ * @param {boolean} doAddEllipsis Whether to add ellipsis if the string needs
1001
+ * truncation.
1002
+ * @return {string} The string after truncation.
526
1003
  */
527
- soy.$$changeNewlineToBr = function(str) {
1004
+ soy.$$truncate = function(str, maxLen, doAddEllipsis) {
528
1005
 
529
1006
  str = String(str);
1007
+ if (str.length <= maxLen) {
1008
+ return str; // no need to truncate
1009
+ }
530
1010
 
531
- // This quick test helps in the case when there are no chars to replace, in
532
- // the worst case this makes barely a difference to the time taken.
533
- if (!soy.$$CHANGE_NEWLINE_TO_BR_RE_.test(str)) {
534
- return str;
1011
+ // If doAddEllipsis, either reduce maxLen to compensate, or else if maxLen is
1012
+ // too small, just turn off doAddEllipsis.
1013
+ if (doAddEllipsis) {
1014
+ if (maxLen > 3) {
1015
+ maxLen -= 3;
1016
+ } else {
1017
+ doAddEllipsis = false;
1018
+ }
535
1019
  }
536
1020
 
537
- return str.replace(/(\r\n|\r|\n)/g, '<br>');
1021
+ // Make sure truncating at maxLen doesn't cut up a unicode surrogate pair.
1022
+ if (soy.$$isHighSurrogate_(str.charAt(maxLen - 1)) &&
1023
+ soy.$$isLowSurrogate_(str.charAt(maxLen))) {
1024
+ maxLen -= 1;
1025
+ }
1026
+
1027
+ // Truncate.
1028
+ str = str.substring(0, maxLen);
1029
+
1030
+ // Add ellipsis.
1031
+ if (doAddEllipsis) {
1032
+ str += '...';
1033
+ }
1034
+
1035
+ return str;
538
1036
  };
539
1037
 
540
1038
  /**
541
- * Regular expression used within $$changeNewlineToBr().
542
- * @type {RegExp}
1039
+ * Private helper for $$truncate() to check whether a char is a high surrogate.
1040
+ * @param {string} ch The char to check.
1041
+ * @return {boolean} Whether the given char is a unicode high surrogate.
543
1042
  * @private
544
1043
  */
545
- soy.$$CHANGE_NEWLINE_TO_BR_RE_ = /[\r\n]/;
1044
+ soy.$$isHighSurrogate_ = function(ch) {
1045
+ return 0xD800 <= ch && ch <= 0xDBFF;
1046
+ };
1047
+
1048
+ /**
1049
+ * Private helper for $$truncate() to check whether a char is a low surrogate.
1050
+ * @param {string} ch The char to check.
1051
+ * @return {boolean} Whether the given char is a unicode low surrogate.
1052
+ * @private
1053
+ */
1054
+ soy.$$isLowSurrogate_ = function(ch) {
1055
+ return 0xDC00 <= ch && ch <= 0xDFFF;
1056
+ };
1057
+
1058
+
1059
+ // -----------------------------------------------------------------------------
1060
+ // Bidi directives/functions.
1061
+
1062
+
1063
+ /**
1064
+ * Returns the leading horizontal edge, i.e. "left" or "right", depending on
1065
+ * bidiGlobalDir.
1066
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
1067
+ * if rtl, 0 if unknown.
1068
+ * @return {string} "right" for RTL context and "left" otherwise.
1069
+ */
1070
+ soy.$$bidiStartEdge = function(bidiGlobalDir) {
1071
+ return bidiGlobalDir < 0 ? 'right' : 'left';
1072
+ };
1073
+
1074
+
1075
+ /**
1076
+ * Returns the trailing horizontal edge, i.e. "right" or "left", depending on
1077
+ * bidiGlobalDir.
1078
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
1079
+ * if rtl, 0 if unknown.
1080
+ * @return {string} "left" for RTL context and "right" otherwise.
1081
+ */
1082
+ soy.$$bidiEndEdge = function(bidiGlobalDir) {
1083
+ return bidiGlobalDir < 0 ? 'left' : 'right';
1084
+ };
546
1085
 
547
1086
 
548
1087
  /**
@@ -579,10 +1118,24 @@ soy.$$bidiTextDir = function(text, opt_isHtml) {
579
1118
  */
580
1119
  soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) {
581
1120
  var dir = soy.$$bidiTextDir(text, opt_isHtml);
582
- if (dir != bidiGlobalDir) {
583
- return dir < 0 ? 'dir=rtl' : dir > 0 ? 'dir=ltr' : '';
584
- }
585
- return '';
1121
+ return new soydata.SanitizedHtmlAttribute(
1122
+ dir && dir != bidiGlobalDir ? dir < 0 ? 'dir=rtl' : 'dir=ltr' : '');
1123
+ };
1124
+
1125
+
1126
+ /**
1127
+ * Returns a Unicode BiDi mark matching bidiGlobalDir (LRM or RLM), or an empty
1128
+ * string if bidiGlobalDir is 0 (unknown).
1129
+ * @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
1130
+ * if rtl, 0 if unknown.
1131
+ * @return {string} A Unicode bidi mark matching bidiGlobalDir, or the empty
1132
+ * string when bidiGlobalDir is 0 (unknown).
1133
+ */
1134
+ soy.$$bidiMark = function(bidiGlobalDir) {
1135
+ return (
1136
+ (bidiGlobalDir > 0) ? '\u200E' /*LRM*/ :
1137
+ (bidiGlobalDir < 0) ? '\u200F' /*RLM*/ :
1138
+ '');
586
1139
  };
587
1140
 
588
1141
 
@@ -597,13 +1150,13 @@ soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) {
597
1150
  * @param {string} text The text whose directionality is to be estimated.
598
1151
  * @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
599
1152
  * Default: false.
600
- * @return {string} A Unicode bidi mark matching bidiGlobalDir, or
601
- * the empty string when text's overall and exit directionalities both match
602
- * bidiGlobalDir.
1153
+ * @return {string} A Unicode bidi mark matching bidiGlobalDir, or the empty
1154
+ * string when text's overall and exit directionalities both match
1155
+ * bidiGlobalDir, or bidiGlobalDir is 0 (unknown).
603
1156
  */
604
1157
  soy.$$bidiMarkAfter = function(bidiGlobalDir, text, opt_isHtml) {
605
1158
  var dir = soy.$$bidiTextDir(text, opt_isHtml);
606
- return soy.$$bidiMarkAfterKnownDir(bidiGlobalDir, dir, text, opt_isHtml);
1159
+ return soy.$$bidiMarkAfterKnownDir_(bidiGlobalDir, dir, text, opt_isHtml);
607
1160
  };
608
1161
 
609
1162
 
@@ -621,9 +1174,10 @@ soy.$$bidiMarkAfter = function(bidiGlobalDir, text, opt_isHtml) {
621
1174
  * Default: false.
622
1175
  * @return {string} A Unicode bidi mark matching bidiGlobalDir, or
623
1176
  * the empty string when text's overall and exit directionalities both match
624
- * bidiGlobalDir.
1177
+ * bidiGlobalDir, or bidiGlobalDir is 0 (unknown).
1178
+ * @private
625
1179
  */
626
- soy.$$bidiMarkAfterKnownDir = function(bidiGlobalDir, dir, text, opt_isHtml) {
1180
+ soy.$$bidiMarkAfterKnownDir_ = function(bidiGlobalDir, dir, text, opt_isHtml) {
627
1181
  return (
628
1182
  bidiGlobalDir > 0 && (dir < 0 ||
629
1183
  soy.$$bidiIsRtlExitText_(text, opt_isHtml)) ? '\u200E' : // LRM
@@ -673,7 +1227,7 @@ soy.$$BIDI_HTML_SKIP_RE_ = /<[^>]*>|&[^;]+;/g;
673
1227
  soy.$$bidiSpanWrap = function(bidiGlobalDir, str) {
674
1228
  str = String(str);
675
1229
  var textDir = soy.$$bidiTextDir(str, true);
676
- var reset = soy.$$bidiMarkAfterKnownDir(bidiGlobalDir, textDir, str, true);
1230
+ var reset = soy.$$bidiMarkAfterKnownDir_(bidiGlobalDir, textDir, str, true);
677
1231
  if (textDir > 0 && bidiGlobalDir <= 0) {
678
1232
  str = '<span dir=ltr>' + str + '</span>';
679
1233
  } else if (textDir < 0 && bidiGlobalDir >= 0) {
@@ -699,7 +1253,7 @@ soy.$$bidiSpanWrap = function(bidiGlobalDir, str) {
699
1253
  soy.$$bidiUnicodeWrap = function(bidiGlobalDir, str) {
700
1254
  str = String(str);
701
1255
  var textDir = soy.$$bidiTextDir(str, true);
702
- var reset = soy.$$bidiMarkAfterKnownDir(bidiGlobalDir, textDir, str, true);
1256
+ var reset = soy.$$bidiMarkAfterKnownDir_(bidiGlobalDir, textDir, str, true);
703
1257
  if (textDir > 0 && bidiGlobalDir <= 0) {
704
1258
  str = '\u202A' + str + '\u202C';
705
1259
  } else if (textDir < 0 && bidiGlobalDir >= 0) {
@@ -878,3 +1432,481 @@ soy.$$bidiIsRtlExitText_ = function(str, opt_isHtml) {
878
1432
  str = soy.$$bidiStripHtmlIfNecessary_(str, opt_isHtml);
879
1433
  return soy.$$bidiRtlExitDirCheckRe_.test(str);
880
1434
  };
1435
+
1436
+
1437
+ // -----------------------------------------------------------------------------
1438
+ // Generated code.
1439
+
1440
+
1441
+
1442
+
1443
+ // START GENERATED CODE FOR ESCAPERS.
1444
+
1445
+ /**
1446
+ * @type {function (*) : string}
1447
+ */
1448
+ soy.esc.$$escapeUriHelper = function(v) {
1449
+ return encodeURIComponent(String(v));
1450
+ };
1451
+
1452
+ /**
1453
+ * Maps charcters to the escaped versions for the named escape directives.
1454
+ * @type {Object.<string, string>}
1455
+ * @private
1456
+ */
1457
+ soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_ = {
1458
+ '\x00': '\x26#0;',
1459
+ '\x22': '\x26quot;',
1460
+ '\x26': '\x26amp;',
1461
+ '\x27': '\x26#39;',
1462
+ '\x3c': '\x26lt;',
1463
+ '\x3e': '\x26gt;',
1464
+ '\x09': '\x26#9;',
1465
+ '\x0a': '\x26#10;',
1466
+ '\x0b': '\x26#11;',
1467
+ '\x0c': '\x26#12;',
1468
+ '\x0d': '\x26#13;',
1469
+ ' ': '\x26#32;',
1470
+ '-': '\x26#45;',
1471
+ '\/': '\x26#47;',
1472
+ '\x3d': '\x26#61;',
1473
+ '`': '\x26#96;',
1474
+ '\x85': '\x26#133;',
1475
+ '\xa0': '\x26#160;',
1476
+ '\u2028': '\x26#8232;',
1477
+ '\u2029': '\x26#8233;'
1478
+ };
1479
+
1480
+ /**
1481
+ * A function that can be used with String.replace..
1482
+ * @param {string} ch A single character matched by a compatible matcher.
1483
+ * @return {string} A token in the output language.
1484
+ * @private
1485
+ */
1486
+ soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_ = function(ch) {
1487
+ return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_[ch];
1488
+ };
1489
+
1490
+ /**
1491
+ * Maps charcters to the escaped versions for the named escape directives.
1492
+ * @type {Object.<string, string>}
1493
+ * @private
1494
+ */
1495
+ soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = {
1496
+ '\x00': '\\x00',
1497
+ '\x08': '\\x08',
1498
+ '\x09': '\\t',
1499
+ '\x0a': '\\n',
1500
+ '\x0b': '\\x0b',
1501
+ '\x0c': '\\f',
1502
+ '\x0d': '\\r',
1503
+ '\x22': '\\x22',
1504
+ '\x26': '\\x26',
1505
+ '\x27': '\\x27',
1506
+ '\/': '\\\/',
1507
+ '\x3c': '\\x3c',
1508
+ '\x3d': '\\x3d',
1509
+ '\x3e': '\\x3e',
1510
+ '\\': '\\\\',
1511
+ '\x85': '\\x85',
1512
+ '\u2028': '\\u2028',
1513
+ '\u2029': '\\u2029',
1514
+ '$': '\\x24',
1515
+ '(': '\\x28',
1516
+ ')': '\\x29',
1517
+ '*': '\\x2a',
1518
+ '+': '\\x2b',
1519
+ ',': '\\x2c',
1520
+ '-': '\\x2d',
1521
+ '.': '\\x2e',
1522
+ ':': '\\x3a',
1523
+ '?': '\\x3f',
1524
+ '[': '\\x5b',
1525
+ ']': '\\x5d',
1526
+ '^': '\\x5e',
1527
+ '{': '\\x7b',
1528
+ '|': '\\x7c',
1529
+ '}': '\\x7d'
1530
+ };
1531
+
1532
+ /**
1533
+ * A function that can be used with String.replace..
1534
+ * @param {string} ch A single character matched by a compatible matcher.
1535
+ * @return {string} A token in the output language.
1536
+ * @private
1537
+ */
1538
+ soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_ = function(ch) {
1539
+ return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_[ch];
1540
+ };
1541
+
1542
+ /**
1543
+ * Maps charcters to the escaped versions for the named escape directives.
1544
+ * @type {Object.<string, string>}
1545
+ * @private
1546
+ */
1547
+ soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_ = {
1548
+ '\x00': '\\0 ',
1549
+ '\x08': '\\8 ',
1550
+ '\x09': '\\9 ',
1551
+ '\x0a': '\\a ',
1552
+ '\x0b': '\\b ',
1553
+ '\x0c': '\\c ',
1554
+ '\x0d': '\\d ',
1555
+ '\x22': '\\22 ',
1556
+ '\x26': '\\26 ',
1557
+ '\x27': '\\27 ',
1558
+ '(': '\\28 ',
1559
+ ')': '\\29 ',
1560
+ '*': '\\2a ',
1561
+ '\/': '\\2f ',
1562
+ ':': '\\3a ',
1563
+ ';': '\\3b ',
1564
+ '\x3c': '\\3c ',
1565
+ '\x3d': '\\3d ',
1566
+ '\x3e': '\\3e ',
1567
+ '@': '\\40 ',
1568
+ '\\': '\\5c ',
1569
+ '{': '\\7b ',
1570
+ '}': '\\7d ',
1571
+ '\x85': '\\85 ',
1572
+ '\xa0': '\\a0 ',
1573
+ '\u2028': '\\2028 ',
1574
+ '\u2029': '\\2029 '
1575
+ };
1576
+
1577
+ /**
1578
+ * A function that can be used with String.replace..
1579
+ * @param {string} ch A single character matched by a compatible matcher.
1580
+ * @return {string} A token in the output language.
1581
+ * @private
1582
+ */
1583
+ soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_ = function(ch) {
1584
+ return soy.esc.$$ESCAPE_MAP_FOR_ESCAPE_CSS_STRING_[ch];
1585
+ };
1586
+
1587
+ /**
1588
+ * Maps charcters to the escaped versions for the named escape directives.
1589
+ * @type {Object.<string, string>}
1590
+ * @private
1591
+ */
1592
+ soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = {
1593
+ '\x00': '%00',
1594
+ '\x01': '%01',
1595
+ '\x02': '%02',
1596
+ '\x03': '%03',
1597
+ '\x04': '%04',
1598
+ '\x05': '%05',
1599
+ '\x06': '%06',
1600
+ '\x07': '%07',
1601
+ '\x08': '%08',
1602
+ '\x09': '%09',
1603
+ '\x0a': '%0A',
1604
+ '\x0b': '%0B',
1605
+ '\x0c': '%0C',
1606
+ '\x0d': '%0D',
1607
+ '\x0e': '%0E',
1608
+ '\x0f': '%0F',
1609
+ '\x10': '%10',
1610
+ '\x11': '%11',
1611
+ '\x12': '%12',
1612
+ '\x13': '%13',
1613
+ '\x14': '%14',
1614
+ '\x15': '%15',
1615
+ '\x16': '%16',
1616
+ '\x17': '%17',
1617
+ '\x18': '%18',
1618
+ '\x19': '%19',
1619
+ '\x1a': '%1A',
1620
+ '\x1b': '%1B',
1621
+ '\x1c': '%1C',
1622
+ '\x1d': '%1D',
1623
+ '\x1e': '%1E',
1624
+ '\x1f': '%1F',
1625
+ ' ': '%20',
1626
+ '\x22': '%22',
1627
+ '\x27': '%27',
1628
+ '(': '%28',
1629
+ ')': '%29',
1630
+ '\x3c': '%3C',
1631
+ '\x3e': '%3E',
1632
+ '\\': '%5C',
1633
+ '{': '%7B',
1634
+ '}': '%7D',
1635
+ '\x7f': '%7F',
1636
+ '\x85': '%C2%85',
1637
+ '\xa0': '%C2%A0',
1638
+ '\u2028': '%E2%80%A8',
1639
+ '\u2029': '%E2%80%A9',
1640
+ '\uff01': '%EF%BC%81',
1641
+ '\uff03': '%EF%BC%83',
1642
+ '\uff04': '%EF%BC%84',
1643
+ '\uff06': '%EF%BC%86',
1644
+ '\uff07': '%EF%BC%87',
1645
+ '\uff08': '%EF%BC%88',
1646
+ '\uff09': '%EF%BC%89',
1647
+ '\uff0a': '%EF%BC%8A',
1648
+ '\uff0b': '%EF%BC%8B',
1649
+ '\uff0c': '%EF%BC%8C',
1650
+ '\uff0f': '%EF%BC%8F',
1651
+ '\uff1a': '%EF%BC%9A',
1652
+ '\uff1b': '%EF%BC%9B',
1653
+ '\uff1d': '%EF%BC%9D',
1654
+ '\uff1f': '%EF%BC%9F',
1655
+ '\uff20': '%EF%BC%A0',
1656
+ '\uff3b': '%EF%BC%BB',
1657
+ '\uff3d': '%EF%BC%BD'
1658
+ };
1659
+
1660
+ /**
1661
+ * A function that can be used with String.replace..
1662
+ * @param {string} ch A single character matched by a compatible matcher.
1663
+ * @return {string} A token in the output language.
1664
+ * @private
1665
+ */
1666
+ soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_ = function(ch) {
1667
+ return soy.esc.$$ESCAPE_MAP_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_[ch];
1668
+ };
1669
+
1670
+ /**
1671
+ * Matches characters that need to be escaped for the named directives.
1672
+ * @type RegExp
1673
+ * @private
1674
+ */
1675
+ soy.esc.$$MATCHER_FOR_ESCAPE_HTML_ = /[\x00\x22\x26\x27\x3c\x3e]/g;
1676
+
1677
+ /**
1678
+ * Matches characters that need to be escaped for the named directives.
1679
+ * @type RegExp
1680
+ * @private
1681
+ */
1682
+ soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_ = /[\x00\x22\x27\x3c\x3e]/g;
1683
+
1684
+ /**
1685
+ * Matches characters that need to be escaped for the named directives.
1686
+ * @type RegExp
1687
+ * @private
1688
+ */
1689
+ soy.esc.$$MATCHER_FOR_ESCAPE_HTML_NOSPACE_ = /[\x00\x09-\x0d \x22\x26\x27\x2d\/\x3c-\x3e`\x85\xa0\u2028\u2029]/g;
1690
+
1691
+ /**
1692
+ * Matches characters that need to be escaped for the named directives.
1693
+ * @type RegExp
1694
+ * @private
1695
+ */
1696
+ soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_NOSPACE_ = /[\x00\x09-\x0d \x22\x27\x2d\/\x3c-\x3e`\x85\xa0\u2028\u2029]/g;
1697
+
1698
+ /**
1699
+ * Matches characters that need to be escaped for the named directives.
1700
+ * @type RegExp
1701
+ * @private
1702
+ */
1703
+ soy.esc.$$MATCHER_FOR_ESCAPE_JS_STRING_ = /[\x00\x08-\x0d\x22\x26\x27\/\x3c-\x3e\\\x85\u2028\u2029]/g;
1704
+
1705
+ /**
1706
+ * Matches characters that need to be escaped for the named directives.
1707
+ * @type RegExp
1708
+ * @private
1709
+ */
1710
+ soy.esc.$$MATCHER_FOR_ESCAPE_JS_REGEX_ = /[\x00\x08-\x0d\x22\x24\x26-\/\x3a\x3c-\x3f\x5b-\x5e\x7b-\x7d\x85\u2028\u2029]/g;
1711
+
1712
+ /**
1713
+ * Matches characters that need to be escaped for the named directives.
1714
+ * @type RegExp
1715
+ * @private
1716
+ */
1717
+ soy.esc.$$MATCHER_FOR_ESCAPE_CSS_STRING_ = /[\x00\x08-\x0d\x22\x26-\x2a\/\x3a-\x3e@\\\x7b\x7d\x85\xa0\u2028\u2029]/g;
1718
+
1719
+ /**
1720
+ * Matches characters that need to be escaped for the named directives.
1721
+ * @type RegExp
1722
+ * @private
1723
+ */
1724
+ 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;
1725
+
1726
+ /**
1727
+ * A pattern that vets values produced by the named directives.
1728
+ * @type RegExp
1729
+ * @private
1730
+ */
1731
+ 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;
1732
+
1733
+ /**
1734
+ * A pattern that vets values produced by the named directives.
1735
+ * @type RegExp
1736
+ * @private
1737
+ */
1738
+ soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_ = /^(?:(?:https?|mailto):|[^&:\/?#]*(?:[\/?#]|$))/i;
1739
+
1740
+ /**
1741
+ * A pattern that vets values produced by the named directives.
1742
+ * @type RegExp
1743
+ * @private
1744
+ */
1745
+ soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTE_ = /^(?!style|on|action|archive|background|cite|classid|codebase|data|dsync|href|longdesc|src|usemap)(?:[a-z0-9_$:-]*)$/i;
1746
+
1747
+ /**
1748
+ * A pattern that vets values produced by the named directives.
1749
+ * @type RegExp
1750
+ * @private
1751
+ */
1752
+ soy.esc.$$FILTER_FOR_FILTER_HTML_ELEMENT_NAME_ = /^(?!script|style|title|textarea|xmp|no)[a-z0-9_$:-]*$/i;
1753
+
1754
+ /**
1755
+ * A helper for the Soy directive |escapeHtml
1756
+ * @param {*} value Can be of any type but will be coerced to a string.
1757
+ * @return {string} The escaped text.
1758
+ */
1759
+ soy.esc.$$escapeHtmlHelper = function(value) {
1760
+ var str = String(value);
1761
+ return str.replace(
1762
+ soy.esc.$$MATCHER_FOR_ESCAPE_HTML_,
1763
+ soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_);
1764
+ };
1765
+
1766
+ /**
1767
+ * A helper for the Soy directive |normalizeHtml
1768
+ * @param {*} value Can be of any type but will be coerced to a string.
1769
+ * @return {string} The escaped text.
1770
+ */
1771
+ soy.esc.$$normalizeHtmlHelper = function(value) {
1772
+ var str = String(value);
1773
+ return str.replace(
1774
+ soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_,
1775
+ soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_);
1776
+ };
1777
+
1778
+ /**
1779
+ * A helper for the Soy directive |escapeHtmlNospace
1780
+ * @param {*} value Can be of any type but will be coerced to a string.
1781
+ * @return {string} The escaped text.
1782
+ */
1783
+ soy.esc.$$escapeHtmlNospaceHelper = function(value) {
1784
+ var str = String(value);
1785
+ return str.replace(
1786
+ soy.esc.$$MATCHER_FOR_ESCAPE_HTML_NOSPACE_,
1787
+ soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_);
1788
+ };
1789
+
1790
+ /**
1791
+ * A helper for the Soy directive |normalizeHtmlNospace
1792
+ * @param {*} value Can be of any type but will be coerced to a string.
1793
+ * @return {string} The escaped text.
1794
+ */
1795
+ soy.esc.$$normalizeHtmlNospaceHelper = function(value) {
1796
+ var str = String(value);
1797
+ return str.replace(
1798
+ soy.esc.$$MATCHER_FOR_NORMALIZE_HTML_NOSPACE_,
1799
+ soy.esc.$$REPLACER_FOR_ESCAPE_HTML__AND__NORMALIZE_HTML__AND__ESCAPE_HTML_NOSPACE__AND__NORMALIZE_HTML_NOSPACE_);
1800
+ };
1801
+
1802
+ /**
1803
+ * A helper for the Soy directive |escapeJsString
1804
+ * @param {*} value Can be of any type but will be coerced to a string.
1805
+ * @return {string} The escaped text.
1806
+ */
1807
+ soy.esc.$$escapeJsStringHelper = function(value) {
1808
+ var str = String(value);
1809
+ return str.replace(
1810
+ soy.esc.$$MATCHER_FOR_ESCAPE_JS_STRING_,
1811
+ soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_);
1812
+ };
1813
+
1814
+ /**
1815
+ * A helper for the Soy directive |escapeJsRegex
1816
+ * @param {*} value Can be of any type but will be coerced to a string.
1817
+ * @return {string} The escaped text.
1818
+ */
1819
+ soy.esc.$$escapeJsRegexHelper = function(value) {
1820
+ var str = String(value);
1821
+ return str.replace(
1822
+ soy.esc.$$MATCHER_FOR_ESCAPE_JS_REGEX_,
1823
+ soy.esc.$$REPLACER_FOR_ESCAPE_JS_STRING__AND__ESCAPE_JS_REGEX_);
1824
+ };
1825
+
1826
+ /**
1827
+ * A helper for the Soy directive |escapeCssString
1828
+ * @param {*} value Can be of any type but will be coerced to a string.
1829
+ * @return {string} The escaped text.
1830
+ */
1831
+ soy.esc.$$escapeCssStringHelper = function(value) {
1832
+ var str = String(value);
1833
+ return str.replace(
1834
+ soy.esc.$$MATCHER_FOR_ESCAPE_CSS_STRING_,
1835
+ soy.esc.$$REPLACER_FOR_ESCAPE_CSS_STRING_);
1836
+ };
1837
+
1838
+ /**
1839
+ * A helper for the Soy directive |filterCssValue
1840
+ * @param {*} value Can be of any type but will be coerced to a string.
1841
+ * @return {string} The escaped text.
1842
+ */
1843
+ soy.esc.$$filterCssValueHelper = function(value) {
1844
+ var str = String(value);
1845
+ if (!soy.esc.$$FILTER_FOR_FILTER_CSS_VALUE_.test(str)) {
1846
+ return 'zSoyz';
1847
+ }
1848
+ return str;
1849
+ };
1850
+
1851
+ /**
1852
+ * A helper for the Soy directive |normalizeUri
1853
+ * @param {*} value Can be of any type but will be coerced to a string.
1854
+ * @return {string} The escaped text.
1855
+ */
1856
+ soy.esc.$$normalizeUriHelper = function(value) {
1857
+ var str = String(value);
1858
+ return str.replace(
1859
+ soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_,
1860
+ soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_);
1861
+ };
1862
+
1863
+ /**
1864
+ * A helper for the Soy directive |filterNormalizeUri
1865
+ * @param {*} value Can be of any type but will be coerced to a string.
1866
+ * @return {string} The escaped text.
1867
+ */
1868
+ soy.esc.$$filterNormalizeUriHelper = function(value) {
1869
+ var str = String(value);
1870
+ if (!soy.esc.$$FILTER_FOR_FILTER_NORMALIZE_URI_.test(str)) {
1871
+ return 'zSoyz';
1872
+ }
1873
+ return str.replace(
1874
+ soy.esc.$$MATCHER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_,
1875
+ soy.esc.$$REPLACER_FOR_NORMALIZE_URI__AND__FILTER_NORMALIZE_URI_);
1876
+ };
1877
+
1878
+ /**
1879
+ * A helper for the Soy directive |filterHtmlAttribute
1880
+ * @param {*} value Can be of any type but will be coerced to a string.
1881
+ * @return {string} The escaped text.
1882
+ */
1883
+ soy.esc.$$filterHtmlAttributeHelper = function(value) {
1884
+ var str = String(value);
1885
+ if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ATTRIBUTE_.test(str)) {
1886
+ return 'zSoyz';
1887
+ }
1888
+ return str;
1889
+ };
1890
+
1891
+ /**
1892
+ * A helper for the Soy directive |filterHtmlElementName
1893
+ * @param {*} value Can be of any type but will be coerced to a string.
1894
+ * @return {string} The escaped text.
1895
+ */
1896
+ soy.esc.$$filterHtmlElementNameHelper = function(value) {
1897
+ var str = String(value);
1898
+ if (!soy.esc.$$FILTER_FOR_FILTER_HTML_ELEMENT_NAME_.test(str)) {
1899
+ return 'zSoyz';
1900
+ }
1901
+ return str;
1902
+ };
1903
+
1904
+ /**
1905
+ * Matches all tags, HTML comments, and DOCTYPEs in tag soup HTML.
1906
+ *
1907
+ * @type {RegExp}
1908
+ * @private
1909
+ */
1910
+ soy.esc.$$HTML_TAG_REGEX_ = /<(?:!|\/?[a-zA-Z])(?:[^>'"]|"[^"]*"|'[^']*')*>/g;
1911
+
1912
+ // END GENERATED CODE