closure 1.4.2 → 1.4.3
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.
- data/closure-compiler/compiler.jar +0 -0
- data/closure-templates/SoyToJsSrcCompiler.jar +0 -0
- data/closure-templates/soyutils.js +873 -611
- data/closure-templates/soyutils_usegoog.js +139 -74
- data/lib/closure/sources.rb +10 -5
- data/lib/closure/version.rb +1 -1
- data/lib/closure.rb +1 -0
- metadata +4 -5
- data/closure-templates/soydata.js +0 -163
@@ -35,65 +35,370 @@
|
|
35
35
|
* @author Aharon Lenin
|
36
36
|
*/
|
37
37
|
|
38
|
+
|
39
|
+
// COPIED FROM nogoog_shim.js
|
40
|
+
|
41
|
+
// Create closure namespaces.
|
42
|
+
var goog;
|
43
|
+
if (typeof goog == "undefined") {
|
44
|
+
goog = {};
|
45
|
+
}
|
46
|
+
|
47
|
+
goog.inherits = function(childCtor, parentCtor) {
|
48
|
+
/** @constructor */
|
49
|
+
function tempCtor() {}
|
50
|
+
tempCtor.prototype = parentCtor.prototype;
|
51
|
+
childCtor.superClass_ = parentCtor.prototype;
|
52
|
+
childCtor.prototype = new tempCtor();
|
53
|
+
childCtor.prototype.constructor = childCtor;
|
54
|
+
};
|
55
|
+
|
56
|
+
|
57
|
+
// Just enough browser detection for this file.
|
58
|
+
if (!goog.userAgent) {
|
59
|
+
goog.userAgent = (function() {
|
60
|
+
var userAgent = "";
|
61
|
+
if ("undefined" !== typeof navigator && navigator
|
62
|
+
&& "string" == typeof navigator.userAgent) {
|
63
|
+
userAgent = navigator.userAgent;
|
64
|
+
}
|
65
|
+
var isOpera = userAgent.indexOf('Opera') == 0;
|
66
|
+
return {
|
67
|
+
/**
|
68
|
+
* @type {boolean}
|
69
|
+
*/
|
70
|
+
HAS_JSCRIPT: typeof 'ScriptEngine' in this,
|
71
|
+
/**
|
72
|
+
* @type {boolean}
|
73
|
+
*/
|
74
|
+
IS_OPERA: isOpera,
|
75
|
+
/**
|
76
|
+
* @type {boolean}
|
77
|
+
*/
|
78
|
+
IS_IE: !isOpera && userAgent.indexOf('MSIE') != -1,
|
79
|
+
/**
|
80
|
+
* @type {boolean}
|
81
|
+
*/
|
82
|
+
IS_WEBKIT: !isOpera && userAgent.indexOf('WebKit') != -1
|
83
|
+
};
|
84
|
+
})();
|
85
|
+
}
|
86
|
+
|
87
|
+
if (!goog.asserts) {
|
88
|
+
goog.asserts = {
|
89
|
+
fail: function () {}
|
90
|
+
};
|
91
|
+
}
|
92
|
+
|
93
|
+
|
94
|
+
// Stub out the document wrapper used by renderAs*.
|
95
|
+
if (!goog.dom) {
|
96
|
+
goog.dom = {
|
97
|
+
DomHelper: function (d) {
|
98
|
+
d = d || document;
|
99
|
+
return {
|
100
|
+
createElement: function (name) { return d.createElement(name); },
|
101
|
+
createDocumentFragment: function () {
|
102
|
+
return d.createDocumentFragment();
|
103
|
+
}
|
104
|
+
};
|
105
|
+
}
|
106
|
+
};
|
107
|
+
}
|
108
|
+
|
109
|
+
|
110
|
+
if (!goog.format) {
|
111
|
+
goog.format = {
|
112
|
+
insertWordBreaks: function(str, maxCharsBetweenWordBreaks) {
|
113
|
+
str = String(str);
|
114
|
+
|
115
|
+
var resultArr = [];
|
116
|
+
var resultArrLen = 0;
|
117
|
+
|
118
|
+
// These variables keep track of important state inside str.
|
119
|
+
var isInTag = false; // whether we're inside an HTML tag
|
120
|
+
var isMaybeInEntity = false; // whether we might be inside an HTML entity
|
121
|
+
var numCharsWithoutBreak = 0; // number of chars since last word break
|
122
|
+
var flushIndex = 0; // index of first char not yet flushed to resultArr
|
123
|
+
|
124
|
+
for (var i = 0, n = str.length; i < n; ++i) {
|
125
|
+
var charCode = str.charCodeAt(i);
|
126
|
+
|
127
|
+
// If hit maxCharsBetweenWordBreaks, and not space next, then add <wbr>.
|
128
|
+
if (numCharsWithoutBreak >= maxCharsBetweenWordBreaks &&
|
129
|
+
// space
|
130
|
+
charCode != 32) {
|
131
|
+
resultArr[resultArrLen++] = str.substring(flushIndex, i);
|
132
|
+
flushIndex = i;
|
133
|
+
resultArr[resultArrLen++] = goog.format.WORD_BREAK;
|
134
|
+
numCharsWithoutBreak = 0;
|
135
|
+
}
|
136
|
+
|
137
|
+
if (isInTag) {
|
138
|
+
// If inside an HTML tag and we see '>', it's the end of the tag.
|
139
|
+
if (charCode == 62) {
|
140
|
+
isInTag = false;
|
141
|
+
}
|
142
|
+
|
143
|
+
} else if (isMaybeInEntity) {
|
144
|
+
switch (charCode) {
|
145
|
+
// Inside an entity, a ';' is the end of the entity.
|
146
|
+
// The entity that just ended counts as one char, so increment
|
147
|
+
// numCharsWithoutBreak.
|
148
|
+
case 59: // ';'
|
149
|
+
isMaybeInEntity = false;
|
150
|
+
++numCharsWithoutBreak;
|
151
|
+
break;
|
152
|
+
// If maybe inside an entity and we see '<', we weren't actually in
|
153
|
+
// an entity. But now we're inside and HTML tag.
|
154
|
+
case 60: // '<'
|
155
|
+
isMaybeInEntity = false;
|
156
|
+
isInTag = true;
|
157
|
+
break;
|
158
|
+
// If maybe inside an entity and we see ' ', we weren't actually in
|
159
|
+
// an entity. Just correct the state and reset the
|
160
|
+
// numCharsWithoutBreak since we just saw a space.
|
161
|
+
case 32: // ' '
|
162
|
+
isMaybeInEntity = false;
|
163
|
+
numCharsWithoutBreak = 0;
|
164
|
+
break;
|
165
|
+
}
|
166
|
+
|
167
|
+
} else { // !isInTag && !isInEntity
|
168
|
+
switch (charCode) {
|
169
|
+
// When not within a tag or an entity and we see '<', we're now
|
170
|
+
// inside an HTML tag.
|
171
|
+
case 60: // '<'
|
172
|
+
isInTag = true;
|
173
|
+
break;
|
174
|
+
// When not within a tag or an entity and we see '&', we might be
|
175
|
+
// inside an entity.
|
176
|
+
case 38: // '&'
|
177
|
+
isMaybeInEntity = true;
|
178
|
+
break;
|
179
|
+
// When we see a space, reset the numCharsWithoutBreak count.
|
180
|
+
case 32: // ' '
|
181
|
+
numCharsWithoutBreak = 0;
|
182
|
+
break;
|
183
|
+
// When we see a non-space, increment the numCharsWithoutBreak.
|
184
|
+
default:
|
185
|
+
++numCharsWithoutBreak;
|
186
|
+
break;
|
187
|
+
}
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
// Flush the remaining chars at the end of the string.
|
192
|
+
resultArr[resultArrLen++] = str.substring(flushIndex);
|
193
|
+
|
194
|
+
return resultArr.join('');
|
195
|
+
},
|
196
|
+
/**
|
197
|
+
* String inserted as a word break by insertWordBreaks(). Safari requires
|
198
|
+
* <wbr></wbr>, Opera needs the 'shy' entity, though this will give a
|
199
|
+
* visible hyphen at breaks. Other browsers just use <wbr>.
|
200
|
+
* @type {string}
|
201
|
+
* @private
|
202
|
+
*/
|
203
|
+
WORD_BREAK: goog.userAgent.IS_WEBKIT
|
204
|
+
? '<wbr></wbr>' : goog.userAgent.IS_OPERA ? '­' : '<wbr>'
|
205
|
+
};
|
206
|
+
}
|
207
|
+
|
208
|
+
|
209
|
+
if (!goog.i18n) {
|
210
|
+
goog.i18n = {
|
211
|
+
/**
|
212
|
+
* Utility class for formatting text for display in a potentially
|
213
|
+
* opposite-directionality context without garbling. Provides the following
|
214
|
+
* functionality:
|
215
|
+
*
|
216
|
+
* @param {goog.i18n.bidi.Dir|number|boolean} contextDir The context
|
217
|
+
* directionality as a number
|
218
|
+
* (positive = LRT, negative = RTL, 0 = unknown).
|
219
|
+
* @constructor
|
220
|
+
*/
|
221
|
+
BidiFormatter: function (dir) {
|
222
|
+
this.dir_ = dir;
|
223
|
+
},
|
224
|
+
bidi: {
|
225
|
+
/**
|
226
|
+
* Check the directionality of a piece of text, return true if the piece
|
227
|
+
* of text should be laid out in RTL direction.
|
228
|
+
* @param {string} text The piece of text that need to be detected.
|
229
|
+
* @param {boolean=} opt_isHtml Whether {@code text} is HTML/HTML-escaped.
|
230
|
+
* Default: false.
|
231
|
+
* @return {boolean}
|
232
|
+
* @private
|
233
|
+
*/
|
234
|
+
detectRtlDirectionality: function(text, opt_isHtml) {
|
235
|
+
text = soyshim.$$bidiStripHtmlIfNecessary_(text, opt_isHtml);
|
236
|
+
return soyshim.$$bidiRtlWordRatio_(text)
|
237
|
+
> soyshim.$$bidiRtlDetectionThreshold_;
|
238
|
+
}
|
239
|
+
}
|
240
|
+
};
|
241
|
+
}
|
242
|
+
|
243
|
+
|
38
244
|
/**
|
39
|
-
*
|
40
|
-
*
|
41
|
-
*
|
42
|
-
*
|
245
|
+
* Returns "dir=ltr" or "dir=rtl", depending on {@code text}'s estimated
|
246
|
+
* directionality, if it is not the same as the context directionality.
|
247
|
+
* Otherwise, returns the empty string.
|
248
|
+
*
|
249
|
+
* @param {string} text Text whose directionality is to be estimated.
|
250
|
+
* @param {boolean=} opt_isHtml Whether {@code text} is HTML / HTML-escaped.
|
251
|
+
* Default: false.
|
252
|
+
* @return {string} "dir=rtl" for RTL text in non-RTL context; "dir=ltr" for LTR
|
253
|
+
* text in non-LTR context; else, the empty string.
|
43
254
|
*/
|
44
|
-
|
45
|
-
|
255
|
+
goog.i18n.BidiFormatter.prototype.dirAttr = function (text, opt_isHtml) {
|
256
|
+
var dir = soy.$$bidiTextDir(text, opt_isHtml);
|
257
|
+
return dir && dir != this.dir_ ? dir < 0 ? 'dir=rtl' : 'dir=ltr' : '';
|
258
|
+
};
|
46
259
|
|
260
|
+
/**
|
261
|
+
* Returns the trailing horizontal edge, i.e. "right" or "left", depending on
|
262
|
+
* the global bidi directionality.
|
263
|
+
* @return {string} "left" for RTL context and "right" otherwise.
|
264
|
+
*/
|
265
|
+
goog.i18n.BidiFormatter.prototype.endEdge = function () {
|
266
|
+
return this.dir_ < 0 ? 'left' : 'right';
|
267
|
+
};
|
47
268
|
|
48
|
-
|
49
|
-
(
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
/**
|
63
|
-
* @type {boolean}
|
64
|
-
* @private
|
65
|
-
*/
|
66
|
-
soy.$$IS_WEBKIT_ = !isOpera && ua.indexOf('WebKit') != -1;
|
67
|
-
})();
|
269
|
+
/**
|
270
|
+
* Returns the Unicode BiDi mark matching the context directionality (LRM for
|
271
|
+
* LTR context directionality, RLM for RTL context directionality), or the
|
272
|
+
* empty string for neutral / unknown context directionality.
|
273
|
+
*
|
274
|
+
* @return {string} LRM for LTR context directionality and RLM for RTL context
|
275
|
+
* directionality.
|
276
|
+
*/
|
277
|
+
goog.i18n.BidiFormatter.prototype.mark = function () {
|
278
|
+
return (
|
279
|
+
(this.dir_ > 0) ? '\u200E' /*LRM*/ :
|
280
|
+
(this.dir_ < 0) ? '\u200F' /*RLM*/ :
|
281
|
+
'');
|
282
|
+
};
|
68
283
|
|
284
|
+
/**
|
285
|
+
* Returns a Unicode BiDi mark matching the context directionality (LRM or RLM)
|
286
|
+
* if the directionality or the exit directionality of {@code text} are opposite
|
287
|
+
* to the context directionality. Otherwise returns the empty string.
|
288
|
+
*
|
289
|
+
* @param {string} text The input text.
|
290
|
+
* @param {boolean=} opt_isHtml Whether {@code text} is HTML / HTML-escaped.
|
291
|
+
* Default: false.
|
292
|
+
* @return {string} A Unicode bidi mark matching the global directionality or
|
293
|
+
* the empty string.
|
294
|
+
*/
|
295
|
+
goog.i18n.BidiFormatter.prototype.markAfter = function (text, opt_isHtml) {
|
296
|
+
var dir = soy.$$bidiTextDir(text, opt_isHtml);
|
297
|
+
return soyshim.$$bidiMarkAfterKnownDir_(this.dir_, dir, text, opt_isHtml);
|
298
|
+
};
|
69
299
|
|
70
|
-
|
71
|
-
|
300
|
+
/**
|
301
|
+
* Formats a string of unknown directionality for use in HTML output of the
|
302
|
+
* context directionality, so an opposite-directionality string is neither
|
303
|
+
* garbled nor garbles what follows it.
|
304
|
+
*
|
305
|
+
* @param {string} str The input text.
|
306
|
+
* @return {string} Input text after applying the above processing.
|
307
|
+
*/
|
308
|
+
goog.i18n.BidiFormatter.prototype.spanWrap = function(str) {
|
309
|
+
str = String(str);
|
310
|
+
var textDir = soy.$$bidiTextDir(str, true);
|
311
|
+
var reset = soyshim.$$bidiMarkAfterKnownDir_(this.dir_, textDir, str, true);
|
312
|
+
if (textDir > 0 && this.dir_ <= 0) {
|
313
|
+
str = '<span dir=ltr>' + str + '</span>';
|
314
|
+
} else if (textDir < 0 && this.dir_ >= 0) {
|
315
|
+
str = '<span dir=rtl>' + str + '</span>';
|
316
|
+
}
|
317
|
+
return str + reset;
|
318
|
+
};
|
72
319
|
|
320
|
+
/**
|
321
|
+
* Returns the leading horizontal edge, i.e. "left" or "right", depending on
|
322
|
+
* the global bidi directionality.
|
323
|
+
* @return {string} "right" for RTL context and "left" otherwise.
|
324
|
+
*/
|
325
|
+
goog.i18n.BidiFormatter.prototype.startEdge = function () {
|
326
|
+
return this.dir_ < 0 ? 'right' : 'left';
|
327
|
+
};
|
73
328
|
|
74
329
|
/**
|
75
|
-
*
|
76
|
-
*
|
77
|
-
*
|
330
|
+
* Formats a string of unknown directionality for use in plain-text output of
|
331
|
+
* the context directionality, so an opposite-directionality string is neither
|
332
|
+
* garbled nor garbles what follows it.
|
333
|
+
* As opposed to {@link #spanWrap}, this makes use of unicode BiDi formatting
|
334
|
+
* characters. In HTML, its *only* valid use is inside of elements that do not
|
335
|
+
* allow mark-up, e.g. an 'option' tag.
|
78
336
|
*
|
79
|
-
* @param {
|
80
|
-
*
|
81
|
-
* @param {...Object|number|string|boolean} var_args Other initial items to
|
82
|
-
* append, e.g., new soy.StringBuilder('foo', 'bar').
|
83
|
-
* @constructor
|
337
|
+
* @param {string} str The input text.
|
338
|
+
* @return {string} Input text after applying the above processing.
|
84
339
|
*/
|
85
|
-
|
340
|
+
goog.i18n.BidiFormatter.prototype.unicodeWrap = function(str) {
|
341
|
+
str = String(str);
|
342
|
+
var textDir = soy.$$bidiTextDir(str, true);
|
343
|
+
var reset = soyshim.$$bidiMarkAfterKnownDir_(this.dir_, textDir, str, true);
|
344
|
+
if (textDir > 0 && this.dir_ <= 0) {
|
345
|
+
str = '\u202A' + str + '\u202C';
|
346
|
+
} else if (textDir < 0 && this.dir_ >= 0) {
|
347
|
+
str = '\u202B' + str + '\u202C';
|
348
|
+
}
|
349
|
+
return str + reset;
|
350
|
+
};
|
351
|
+
|
86
352
|
|
353
|
+
goog.string = {
|
87
354
|
/**
|
88
|
-
*
|
89
|
-
*
|
90
|
-
*
|
355
|
+
* Utility class to facilitate much faster string concatenation in IE,
|
356
|
+
* using Array.join() rather than the '+' operator. For other browsers
|
357
|
+
* we simply use the '+' operator.
|
358
|
+
*
|
359
|
+
* @param {Object|number|string|boolean=} opt_a1 Optional first initial item
|
360
|
+
* to append.
|
361
|
+
* @param {...Object|number|string|boolean} var_args Other initial items to
|
362
|
+
* append, e.g., new goog.string.StringBuffer('foo', 'bar').
|
363
|
+
* @constructor
|
91
364
|
*/
|
92
|
-
|
365
|
+
StringBuffer: function(opt_a1, var_args) {
|
93
366
|
|
94
|
-
|
95
|
-
|
96
|
-
|
367
|
+
/**
|
368
|
+
* Internal buffer for the string to be concatenated.
|
369
|
+
* @type {string|Array}
|
370
|
+
* @private
|
371
|
+
*/
|
372
|
+
this.buffer_ = goog.userAgent.HAS_JSCRIPT ? [] : '';
|
373
|
+
|
374
|
+
if (opt_a1 != null) {
|
375
|
+
this.append.apply(this, arguments);
|
376
|
+
}
|
377
|
+
},
|
378
|
+
/**
|
379
|
+
* Converts \r\n, \r, and \n to <br>s
|
380
|
+
* @param {*} str The string in which to convert newlines.
|
381
|
+
* @return {string} A copy of {@code str} with converted newlines.
|
382
|
+
*/
|
383
|
+
newlineToBr: function(str) {
|
384
|
+
|
385
|
+
str = String(str);
|
386
|
+
|
387
|
+
// This quick test helps in the case when there are no chars to replace,
|
388
|
+
// in the worst case this makes barely a difference to the time taken.
|
389
|
+
if (!goog.string.NEWLINE_TO_BR_RE_.test(str)) {
|
390
|
+
return str;
|
391
|
+
}
|
392
|
+
|
393
|
+
return str.replace(/(\r\n|\r|\n)/g, '<br>');
|
394
|
+
},
|
395
|
+
urlEncode: encodeURIComponent,
|
396
|
+
/**
|
397
|
+
* Regular expression used within newlineToBr().
|
398
|
+
* @type {RegExp}
|
399
|
+
* @private
|
400
|
+
*/
|
401
|
+
NEWLINE_TO_BR_RE: /[\r\n]/
|
97
402
|
};
|
98
403
|
|
99
404
|
|
@@ -103,8 +408,7 @@ soy.StringBuilder = function(opt_a1, var_args) {
|
|
103
408
|
* @type {number}
|
104
409
|
* @private
|
105
410
|
*/
|
106
|
-
|
107
|
-
|
411
|
+
goog.string.StringBuffer.prototype.bufferLength_ = 0;
|
108
412
|
|
109
413
|
/**
|
110
414
|
* Appends one or more items to the string.
|
@@ -115,11 +419,11 @@ soy.StringBuilder.prototype.bufferLength_ = 0;
|
|
115
419
|
* @param {Object|number|string|boolean=} opt_a2 Optional second string.
|
116
420
|
* @param {...Object|number|string|boolean} var_args Other items to append,
|
117
421
|
* e.g., sb.append('foo', 'bar', 'baz').
|
118
|
-
* @return {
|
422
|
+
* @return {goog.string.StringBuffer} This same StringBuilder object.
|
119
423
|
*/
|
120
|
-
|
424
|
+
goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
|
121
425
|
|
122
|
-
if (
|
426
|
+
if (goog.userAgent.HAS_JSCRIPT) {
|
123
427
|
if (opt_a2 == null) { // no second argument (note: undefined == null)
|
124
428
|
// Array assignment is 2x faster than Array push. Also, use a1
|
125
429
|
// directly to avoid arguments instantiation, another 2x improvement.
|
@@ -148,9 +452,9 @@ soy.StringBuilder.prototype.append = function(a1, opt_a2, var_args) {
|
|
148
452
|
/**
|
149
453
|
* Clears the string.
|
150
454
|
*/
|
151
|
-
|
455
|
+
goog.string.StringBuffer.prototype.clear = function() {
|
152
456
|
|
153
|
-
if (
|
457
|
+
if (goog.userAgent.HAS_JSCRIPT) {
|
154
458
|
this.buffer_.length = 0; // reuse array to avoid creating new object
|
155
459
|
this.bufferLength_ = 0;
|
156
460
|
|
@@ -165,9 +469,9 @@ soy.StringBuilder.prototype.clear = function() {
|
|
165
469
|
*
|
166
470
|
* @return {string} The concatenated string.
|
167
471
|
*/
|
168
|
-
|
472
|
+
goog.string.StringBuffer.prototype.toString = function() {
|
169
473
|
|
170
|
-
if (
|
474
|
+
if (goog.userAgent.HAS_JSCRIPT) {
|
171
475
|
var str = this.buffer_.join('');
|
172
476
|
// Given a string with the entire contents, simplify the StringBuilder by
|
173
477
|
// setting its contents to only be this string, rather than many fragments.
|
@@ -183,18 +487,483 @@ soy.StringBuilder.prototype.toString = function() {
|
|
183
487
|
};
|
184
488
|
|
185
489
|
|
490
|
+
if (!goog.soy) goog.soy = {
|
491
|
+
/**
|
492
|
+
* Helper function to render a Soy template and then set the
|
493
|
+
* output string as the innerHTML of an element. It is recommended
|
494
|
+
* to use this helper function instead of directly setting
|
495
|
+
* innerHTML in your hand-written code, so that it will be easier
|
496
|
+
* to audit the code for cross-site scripting vulnerabilities.
|
497
|
+
*
|
498
|
+
* @param {Element} element The element whose content we are rendering.
|
499
|
+
* @param {Function} template The Soy template defining element's content.
|
500
|
+
* @param {Object=} opt_templateData The data for the template.
|
501
|
+
* @param {Object=} opt_injectedData The injected data for the template.
|
502
|
+
*/
|
503
|
+
renderAsElement: function(
|
504
|
+
template, opt_templateData, opt_injectedData, opt_document) {
|
505
|
+
return /** @type {!Element} */ (soyshim.$$renderWithWrapper_(
|
506
|
+
template, opt_templateData, opt_document, true /* asElement */,
|
507
|
+
opt_injectedData));
|
508
|
+
},
|
509
|
+
/**
|
510
|
+
* Helper function to render a Soy template into a single node or
|
511
|
+
* a document fragment. If the rendered HTML string represents a
|
512
|
+
* single node, then that node is returned (note that this is
|
513
|
+
* *not* a fragment, despite them name of the method). Otherwise a
|
514
|
+
* document fragment is returned containing the rendered nodes.
|
515
|
+
*
|
516
|
+
* @param {Function} template The Soy template defining element's content.
|
517
|
+
* @param {Object=} opt_templateData The data for the template.
|
518
|
+
* @param {Document=} opt_document The document used to create DOM nodes.
|
519
|
+
* If not specified, global document object is used.
|
520
|
+
* @param {Object=} opt_injectedData The injected data for the template.
|
521
|
+
* @return {!Node} The resulting node or document fragment.
|
522
|
+
*/
|
523
|
+
renderAsFragment: function(
|
524
|
+
template, opt_templateData, opt_injectedData, opt_document) {
|
525
|
+
return soyshim.$$renderWithWrapper_(
|
526
|
+
template, opt_templateData, opt_document, false /* asElement */,
|
527
|
+
opt_injectedData);
|
528
|
+
},
|
529
|
+
/**
|
530
|
+
* Helper function to render a Soy template and then set the output string as
|
531
|
+
* the innerHTML of an element. It is recommended to use this helper function
|
532
|
+
* instead of directly setting innerHTML in your hand-written code, so that it
|
533
|
+
* will be easier to audit the code for cross-site scripting vulnerabilities.
|
534
|
+
*
|
535
|
+
* NOTE: New code should consider using goog.soy.renderElement instead.
|
536
|
+
*
|
537
|
+
* @param {Element} element The element whose content we are rendering.
|
538
|
+
* @param {Function} template The Soy template defining the element's content.
|
539
|
+
* @param {Object=} opt_templateData The data for the template.
|
540
|
+
* @param {Object=} opt_injectedData The injected data for the template.
|
541
|
+
*/
|
542
|
+
renderElement: function(
|
543
|
+
element, template, opt_templateData, opt_injectedData) {
|
544
|
+
element.innerHTML = template(opt_templateData, null, opt_injectedData);
|
545
|
+
}
|
546
|
+
};
|
547
|
+
|
548
|
+
|
549
|
+
var soy = { esc: {} };
|
550
|
+
var soydata = {};
|
551
|
+
var soyshim = {};
|
552
|
+
/**
|
553
|
+
* Helper function to render a Soy template into a single node or a document
|
554
|
+
* fragment. If the rendered HTML string represents a single node, then that
|
555
|
+
* node is returned. Otherwise a document fragment is created and returned
|
556
|
+
* (wrapped in a DIV element if #opt_singleNode is true).
|
557
|
+
*
|
558
|
+
* @param {Function} template The Soy template defining the element's content.
|
559
|
+
* @param {Object=} opt_templateData The data for the template.
|
560
|
+
* @param {Document=} opt_document The document used to create DOM nodes. If
|
561
|
+
* not specified, global document object is used.
|
562
|
+
* @param {boolean=} opt_asElement Whether to wrap the fragment in an
|
563
|
+
* element if the template does not render a single element. If true,
|
564
|
+
* result is always an Element.
|
565
|
+
* @param {Object=} opt_injectedData The injected data for the template.
|
566
|
+
* @return {!Node} The resulting node or document fragment.
|
567
|
+
* @private
|
568
|
+
*/
|
569
|
+
soyshim.$$renderWithWrapper_ = function(
|
570
|
+
template, opt_templateData, opt_document, opt_asElement, opt_injectedData) {
|
571
|
+
|
572
|
+
var doc = opt_document || document;
|
573
|
+
var wrapper = doc.createElement('div');
|
574
|
+
wrapper.innerHTML = template(
|
575
|
+
opt_templateData || soyshim.$$DEFAULT_TEMPLATE_DATA_, undefined,
|
576
|
+
opt_injectedData);
|
577
|
+
|
578
|
+
// If the template renders as a single element, return it.
|
579
|
+
if (wrapper.childNodes.length == 1) {
|
580
|
+
var firstChild = wrapper.firstChild;
|
581
|
+
if (!opt_asElement || firstChild.nodeType == 1 /* Element */) {
|
582
|
+
return /** @type {!Node} */ (firstChild);
|
583
|
+
}
|
584
|
+
}
|
585
|
+
|
586
|
+
// If we're forcing it to be a single element, return the wrapper DIV.
|
587
|
+
if (opt_asElement) {
|
588
|
+
return wrapper;
|
589
|
+
}
|
590
|
+
|
591
|
+
// Otherwise, create and return a fragment.
|
592
|
+
var fragment = doc.createDocumentFragment();
|
593
|
+
while (wrapper.firstChild) {
|
594
|
+
fragment.appendChild(wrapper.firstChild);
|
595
|
+
}
|
596
|
+
return fragment;
|
597
|
+
};
|
598
|
+
|
599
|
+
|
600
|
+
/**
|
601
|
+
* Returns a Unicode BiDi mark matching bidiGlobalDir (LRM or RLM) if the
|
602
|
+
* directionality or the exit directionality of text are opposite to
|
603
|
+
* bidiGlobalDir. Otherwise returns the empty string.
|
604
|
+
* If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes
|
605
|
+
* in text, making the logic suitable for HTML and HTML-escaped text.
|
606
|
+
* @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
|
607
|
+
* if rtl, 0 if unknown.
|
608
|
+
* @param {number} dir text's directionality: 1 if ltr, -1 if rtl, 0 if unknown.
|
609
|
+
* @param {string} text The text whose directionality is to be estimated.
|
610
|
+
* @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
|
611
|
+
* Default: false.
|
612
|
+
* @return {string} A Unicode bidi mark matching bidiGlobalDir, or
|
613
|
+
* the empty string when text's overall and exit directionalities both match
|
614
|
+
* bidiGlobalDir, or bidiGlobalDir is 0 (unknown).
|
615
|
+
* @private
|
616
|
+
*/
|
617
|
+
soyshim.$$bidiMarkAfterKnownDir_ = function(
|
618
|
+
bidiGlobalDir, dir, text, opt_isHtml) {
|
619
|
+
return (
|
620
|
+
bidiGlobalDir > 0 && (dir < 0 ||
|
621
|
+
soyshim.$$bidiIsRtlExitText_(text, opt_isHtml)) ? '\u200E' : // LRM
|
622
|
+
bidiGlobalDir < 0 && (dir > 0 ||
|
623
|
+
soyshim.$$bidiIsLtrExitText_(text, opt_isHtml)) ? '\u200F' : // RLM
|
624
|
+
'');
|
625
|
+
};
|
626
|
+
|
627
|
+
|
628
|
+
/**
|
629
|
+
* Strips str of any HTML mark-up and escapes. Imprecise in several ways, but
|
630
|
+
* precision is not very important, since the result is only meant to be used
|
631
|
+
* for directionality detection.
|
632
|
+
* @param {string} str The string to be stripped.
|
633
|
+
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
634
|
+
* Default: false.
|
635
|
+
* @return {string} The stripped string.
|
636
|
+
* @private
|
637
|
+
*/
|
638
|
+
soyshim.$$bidiStripHtmlIfNecessary_ = function(str, opt_isHtml) {
|
639
|
+
return opt_isHtml ? str.replace(soyshim.$$BIDI_HTML_SKIP_RE_, ' ') : str;
|
640
|
+
};
|
641
|
+
|
642
|
+
|
643
|
+
/**
|
644
|
+
* Simplified regular expression for am HTML tag (opening or closing) or an HTML
|
645
|
+
* escape - the things we want to skip over in order to ignore their ltr
|
646
|
+
* characters.
|
647
|
+
* @type {RegExp}
|
648
|
+
* @private
|
649
|
+
*/
|
650
|
+
soyshim.$$BIDI_HTML_SKIP_RE_ = /<[^>]*>|&[^;]+;/g;
|
651
|
+
|
652
|
+
|
653
|
+
/**
|
654
|
+
* A practical pattern to identify strong LTR character. This pattern is not
|
655
|
+
* theoretically correct according to unicode standard. It is simplified for
|
656
|
+
* performance and small code size.
|
657
|
+
* @type {string}
|
658
|
+
* @private
|
659
|
+
*/
|
660
|
+
soyshim.$$bidiLtrChars_ =
|
661
|
+
'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
|
662
|
+
'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
|
663
|
+
|
664
|
+
|
665
|
+
/**
|
666
|
+
* A practical pattern to identify strong neutral and weak character. This
|
667
|
+
* pattern is not theoretically correct according to unicode standard. It is
|
668
|
+
* simplified for performance and small code size.
|
669
|
+
* @type {string}
|
670
|
+
* @private
|
671
|
+
*/
|
672
|
+
soyshim.$$bidiNeutralChars_ =
|
673
|
+
'\u0000-\u0020!-@[-`{-\u00BF\u00D7\u00F7\u02B9-\u02FF\u2000-\u2BFF';
|
674
|
+
|
675
|
+
|
676
|
+
/**
|
677
|
+
* A practical pattern to identify strong RTL character. This pattern is not
|
678
|
+
* theoretically correct according to unicode standard. It is simplified for
|
679
|
+
* performance and small code size.
|
680
|
+
* @type {string}
|
681
|
+
* @private
|
682
|
+
*/
|
683
|
+
soyshim.$$bidiRtlChars_ = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
|
684
|
+
|
685
|
+
|
686
|
+
/**
|
687
|
+
* Regular expressions to check if a piece of text is of RTL directionality
|
688
|
+
* on first character with strong directionality.
|
689
|
+
* @type {RegExp}
|
690
|
+
* @private
|
691
|
+
*/
|
692
|
+
soyshim.$$bidiRtlDirCheckRe_ = new RegExp(
|
693
|
+
'^[^' + soyshim.$$bidiLtrChars_ + ']*[' + soyshim.$$bidiRtlChars_ + ']');
|
694
|
+
|
695
|
+
|
696
|
+
/**
|
697
|
+
* Regular expressions to check if a piece of text is of neutral directionality.
|
698
|
+
* Url are considered as neutral.
|
699
|
+
* @type {RegExp}
|
700
|
+
* @private
|
701
|
+
*/
|
702
|
+
soyshim.$$bidiNeutralDirCheckRe_ = new RegExp(
|
703
|
+
'^[' + soyshim.$$bidiNeutralChars_ + ']*$|^http://');
|
704
|
+
|
705
|
+
|
706
|
+
/**
|
707
|
+
* Check the directionality of the a piece of text based on the first character
|
708
|
+
* with strong directionality.
|
709
|
+
* @param {string} str string being checked.
|
710
|
+
* @return {boolean} return true if rtl directionality is being detected.
|
711
|
+
* @private
|
712
|
+
*/
|
713
|
+
soyshim.$$bidiIsRtlText_ = function(str) {
|
714
|
+
return soyshim.$$bidiRtlDirCheckRe_.test(str);
|
715
|
+
};
|
716
|
+
|
717
|
+
|
718
|
+
/**
|
719
|
+
* Check the directionality of the a piece of text based on the first character
|
720
|
+
* with strong directionality.
|
721
|
+
* @param {string} str string being checked.
|
722
|
+
* @return {boolean} true if all characters have neutral directionality.
|
723
|
+
* @private
|
724
|
+
*/
|
725
|
+
soyshim.$$bidiIsNeutralText_ = function(str) {
|
726
|
+
return soyshim.$$bidiNeutralDirCheckRe_.test(str);
|
727
|
+
};
|
728
|
+
|
729
|
+
|
730
|
+
/**
|
731
|
+
* This constant controls threshold of rtl directionality.
|
732
|
+
* @type {number}
|
733
|
+
* @private
|
734
|
+
*/
|
735
|
+
soyshim.$$bidiRtlDetectionThreshold_ = 0.40;
|
736
|
+
|
737
|
+
|
738
|
+
/**
|
739
|
+
* Returns the RTL ratio based on word count.
|
740
|
+
* @param {string} str the string that need to be checked.
|
741
|
+
* @return {number} the ratio of RTL words among all words with directionality.
|
742
|
+
* @private
|
743
|
+
*/
|
744
|
+
soyshim.$$bidiRtlWordRatio_ = function(str) {
|
745
|
+
var rtlCount = 0;
|
746
|
+
var totalCount = 0;
|
747
|
+
var tokens = str.split(' ');
|
748
|
+
for (var i = 0; i < tokens.length; i++) {
|
749
|
+
if (soyshim.$$bidiIsRtlText_(tokens[i])) {
|
750
|
+
rtlCount++;
|
751
|
+
totalCount++;
|
752
|
+
} else if (!soyshim.$$bidiIsNeutralText_(tokens[i])) {
|
753
|
+
totalCount++;
|
754
|
+
}
|
755
|
+
}
|
756
|
+
|
757
|
+
return totalCount == 0 ? 0 : rtlCount / totalCount;
|
758
|
+
};
|
759
|
+
|
760
|
+
|
761
|
+
/**
|
762
|
+
* Regular expressions to check if the last strongly-directional character in a
|
763
|
+
* piece of text is LTR.
|
764
|
+
* @type {RegExp}
|
765
|
+
* @private
|
766
|
+
*/
|
767
|
+
soyshim.$$bidiLtrExitDirCheckRe_ = new RegExp(
|
768
|
+
'[' + soyshim.$$bidiLtrChars_ + '][^' + soyshim.$$bidiRtlChars_ + ']*$');
|
769
|
+
|
770
|
+
|
771
|
+
/**
|
772
|
+
* Regular expressions to check if the last strongly-directional character in a
|
773
|
+
* piece of text is RTL.
|
774
|
+
* @type {RegExp}
|
775
|
+
* @private
|
776
|
+
*/
|
777
|
+
soyshim.$$bidiRtlExitDirCheckRe_ = new RegExp(
|
778
|
+
'[' + soyshim.$$bidiRtlChars_ + '][^' + soyshim.$$bidiLtrChars_ + ']*$');
|
779
|
+
|
780
|
+
|
781
|
+
/**
|
782
|
+
* Check if the exit directionality a piece of text is LTR, i.e. if the last
|
783
|
+
* strongly-directional character in the string is LTR.
|
784
|
+
* @param {string} str string being checked.
|
785
|
+
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
786
|
+
* Default: false.
|
787
|
+
* @return {boolean} Whether LTR exit directionality was detected.
|
788
|
+
* @private
|
789
|
+
*/
|
790
|
+
soyshim.$$bidiIsLtrExitText_ = function(str, opt_isHtml) {
|
791
|
+
str = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml);
|
792
|
+
return soyshim.$$bidiLtrExitDirCheckRe_.test(str);
|
793
|
+
};
|
794
|
+
|
795
|
+
|
796
|
+
/**
|
797
|
+
* Check if the exit directionality a piece of text is RTL, i.e. if the last
|
798
|
+
* strongly-directional character in the string is RTL.
|
799
|
+
* @param {string} str string being checked.
|
800
|
+
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
801
|
+
* Default: false.
|
802
|
+
* @return {boolean} Whether RTL exit directionality was detected.
|
803
|
+
* @private
|
804
|
+
*/
|
805
|
+
soyshim.$$bidiIsRtlExitText_ = function(str, opt_isHtml) {
|
806
|
+
str = soyshim.$$bidiStripHtmlIfNecessary_(str, opt_isHtml);
|
807
|
+
return soyshim.$$bidiRtlExitDirCheckRe_.test(str);
|
808
|
+
};
|
809
|
+
|
810
|
+
|
811
|
+
// =============================================================================
|
812
|
+
// COPIED FROM soyutils_usegoog.js
|
813
|
+
|
814
|
+
|
186
815
|
// -----------------------------------------------------------------------------
|
187
|
-
//
|
816
|
+
// StringBuilder (compatible with the 'stringbuilder' code style).
|
188
817
|
|
189
818
|
|
190
819
|
/**
|
191
|
-
*
|
192
|
-
*
|
820
|
+
* Utility class to facilitate much faster string concatenation in IE,
|
821
|
+
* using Array.join() rather than the '+' operator. For other browsers
|
822
|
+
* we simply use the '+' operator.
|
193
823
|
*
|
194
|
-
* @
|
824
|
+
* @param {Object} var_args Initial items to append,
|
825
|
+
* e.g., new soy.StringBuilder('foo', 'bar').
|
826
|
+
* @constructor
|
827
|
+
*/
|
828
|
+
soy.StringBuilder = goog.string.StringBuffer;
|
829
|
+
|
830
|
+
|
831
|
+
// -----------------------------------------------------------------------------
|
832
|
+
// soydata: Defines typed strings, e.g. an HTML string {@code "a<b>c"} is
|
833
|
+
// semantically distinct from the plain text string {@code "a<b>c"} and smart
|
834
|
+
// templates can take that distinction into account.
|
835
|
+
|
836
|
+
/**
|
837
|
+
* A type of textual content.
|
838
|
+
* @enum {number}
|
839
|
+
*/
|
840
|
+
soydata.SanitizedContentKind = {
|
841
|
+
|
842
|
+
/**
|
843
|
+
* A snippet of HTML that does not start or end inside a tag, comment, entity,
|
844
|
+
* or DOCTYPE; and that does not contain any executable code
|
845
|
+
* (JS, {@code <object>}s, etc.) from a different trust domain.
|
846
|
+
*/
|
847
|
+
HTML: 0,
|
848
|
+
|
849
|
+
/**
|
850
|
+
* A sequence of code units that can appear between quotes (either kind) in a
|
851
|
+
* JS program without causing a parse error, and without causing any side
|
852
|
+
* effects.
|
853
|
+
* <p>
|
854
|
+
* The content should not contain unescaped quotes, newlines, or anything else
|
855
|
+
* that would cause parsing to fail or to cause a JS parser to finish the
|
856
|
+
* string its parsing inside the content.
|
857
|
+
* <p>
|
858
|
+
* The content must also not end inside an escape sequence ; no partial octal
|
859
|
+
* escape sequences or odd number of '{@code \}'s at the end.
|
860
|
+
*/
|
861
|
+
JS_STR_CHARS: 1,
|
862
|
+
|
863
|
+
/** A properly encoded portion of a URI. */
|
864
|
+
URI: 2,
|
865
|
+
|
866
|
+
/** An attribute name and value such as {@code dir="ltr"}. */
|
867
|
+
HTML_ATTRIBUTE: 3
|
868
|
+
};
|
869
|
+
|
870
|
+
|
871
|
+
/**
|
872
|
+
* A string-like object that carries a content-type.
|
873
|
+
* @param {string} content
|
874
|
+
* @constructor
|
195
875
|
* @private
|
196
876
|
*/
|
197
|
-
|
877
|
+
soydata.SanitizedContent = function(content) {
|
878
|
+
/**
|
879
|
+
* The textual content.
|
880
|
+
* @type {string}
|
881
|
+
*/
|
882
|
+
this.content = content;
|
883
|
+
};
|
884
|
+
|
885
|
+
/** @type {soydata.SanitizedContentKind} */
|
886
|
+
soydata.SanitizedContent.prototype.contentKind;
|
887
|
+
|
888
|
+
/** @override */
|
889
|
+
soydata.SanitizedContent.prototype.toString = function() {
|
890
|
+
return this.content;
|
891
|
+
};
|
892
|
+
|
893
|
+
|
894
|
+
/**
|
895
|
+
* Content of type {@link soydata.SanitizedContentKind.HTML}.
|
896
|
+
* @param {string} content A string of HTML that can safely be embedded in
|
897
|
+
* a PCDATA context in your app. If you would be surprised to find that an
|
898
|
+
* HTML sanitizer produced {@code s} (e.g. it runs code or fetches bad URLs)
|
899
|
+
* and you wouldn't write a template that produces {@code s} on security or
|
900
|
+
* privacy grounds, then don't pass {@code s} here.
|
901
|
+
* @constructor
|
902
|
+
* @extends {soydata.SanitizedContent}
|
903
|
+
*/
|
904
|
+
soydata.SanitizedHtml = function(content) {
|
905
|
+
soydata.SanitizedContent.call(this, content);
|
906
|
+
};
|
907
|
+
goog.inherits(soydata.SanitizedHtml, soydata.SanitizedContent);
|
908
|
+
|
909
|
+
/** @override */
|
910
|
+
soydata.SanitizedHtml.prototype.contentKind = soydata.SanitizedContentKind.HTML;
|
911
|
+
|
912
|
+
|
913
|
+
/**
|
914
|
+
* Content of type {@link soydata.SanitizedContentKind.JS_STR_CHARS}.
|
915
|
+
* @param {string} content A string of JS that when evaled, produces a
|
916
|
+
* value that does not depend on any sensitive data and has no side effects
|
917
|
+
* <b>OR</b> a string of JS that does not reference any variables or have
|
918
|
+
* any side effects not known statically to the app authors.
|
919
|
+
* @constructor
|
920
|
+
* @extends {soydata.SanitizedContent}
|
921
|
+
*/
|
922
|
+
soydata.SanitizedJsStrChars = function(content) {
|
923
|
+
soydata.SanitizedContent.call(this, content);
|
924
|
+
};
|
925
|
+
goog.inherits(soydata.SanitizedJsStrChars, soydata.SanitizedContent);
|
926
|
+
|
927
|
+
/** @override */
|
928
|
+
soydata.SanitizedJsStrChars.prototype.contentKind =
|
929
|
+
soydata.SanitizedContentKind.JS_STR_CHARS;
|
930
|
+
|
931
|
+
|
932
|
+
/**
|
933
|
+
* Content of type {@link soydata.SanitizedContentKind.URI}.
|
934
|
+
* @param {string} content A chunk of URI that the caller knows is safe to
|
935
|
+
* emit in a template.
|
936
|
+
* @constructor
|
937
|
+
* @extends {soydata.SanitizedContent}
|
938
|
+
*/
|
939
|
+
soydata.SanitizedUri = function(content) {
|
940
|
+
soydata.SanitizedContent.call(this, content);
|
941
|
+
};
|
942
|
+
goog.inherits(soydata.SanitizedUri, soydata.SanitizedContent);
|
943
|
+
|
944
|
+
/** @override */
|
945
|
+
soydata.SanitizedUri.prototype.contentKind = soydata.SanitizedContentKind.URI;
|
946
|
+
|
947
|
+
|
948
|
+
/**
|
949
|
+
* Content of type {@link soydata.SanitizedContentKind.HTML_ATTRIBUTE}.
|
950
|
+
* @param {string} content An attribute name and value, such as
|
951
|
+
* {@code dir="ltr"}.
|
952
|
+
* @constructor
|
953
|
+
* @extends {soydata.SanitizedContent}
|
954
|
+
*/
|
955
|
+
soydata.SanitizedHtmlAttribute = function(content) {
|
956
|
+
soydata.SanitizedContent.call(this, content);
|
957
|
+
};
|
958
|
+
goog.inherits(soydata.SanitizedHtmlAttribute, soydata.SanitizedContent);
|
959
|
+
|
960
|
+
/** @override */
|
961
|
+
soydata.SanitizedHtmlAttribute.prototype.contentKind =
|
962
|
+
soydata.SanitizedContentKind.HTML_ATTRIBUTE;
|
963
|
+
|
964
|
+
|
965
|
+
// -----------------------------------------------------------------------------
|
966
|
+
// Public utilities.
|
198
967
|
|
199
968
|
|
200
969
|
/**
|
@@ -203,17 +972,14 @@ soy.$$DEFAULT_TEMPLATE_DATA_ = {};
|
|
203
972
|
* instead of directly setting innerHTML in your hand-written code, so that it
|
204
973
|
* will be easier to audit the code for cross-site scripting vulnerabilities.
|
205
974
|
*
|
975
|
+
* NOTE: New code should consider using goog.soy.renderElement instead.
|
976
|
+
*
|
206
977
|
* @param {Element} element The element whose content we are rendering.
|
207
978
|
* @param {Function} template The Soy template defining the element's content.
|
208
979
|
* @param {Object=} opt_templateData The data for the template.
|
209
980
|
* @param {Object=} opt_injectedData The injected data for the template.
|
210
981
|
*/
|
211
|
-
soy.renderElement =
|
212
|
-
element, template, opt_templateData, opt_injectedData) {
|
213
|
-
element.innerHTML = template(
|
214
|
-
opt_templateData || soy.$$DEFAULT_TEMPLATE_DATA_, undefined,
|
215
|
-
opt_injectedData);
|
216
|
-
};
|
982
|
+
soy.renderElement = goog.soy.renderElement;
|
217
983
|
|
218
984
|
|
219
985
|
/**
|
@@ -223,6 +989,9 @@ soy.renderElement = function(
|
|
223
989
|
* the method). Otherwise a document fragment is returned containing the
|
224
990
|
* rendered nodes.
|
225
991
|
*
|
992
|
+
* NOTE: New code should consider using goog.soy.renderAsFragment
|
993
|
+
* instead (note that the arguments are different).
|
994
|
+
*
|
226
995
|
* @param {Function} template The Soy template defining the element's content.
|
227
996
|
* @param {Object=} opt_templateData The data for the template.
|
228
997
|
* @param {Document=} opt_document The document used to create DOM nodes. If not
|
@@ -232,9 +1001,9 @@ soy.renderElement = function(
|
|
232
1001
|
*/
|
233
1002
|
soy.renderAsFragment = function(
|
234
1003
|
template, opt_templateData, opt_document, opt_injectedData) {
|
235
|
-
return soy
|
236
|
-
template, opt_templateData,
|
237
|
-
|
1004
|
+
return goog.soy.renderAsFragment(
|
1005
|
+
template, opt_templateData, opt_injectedData,
|
1006
|
+
new goog.dom.DomHelper(opt_document));
|
238
1007
|
};
|
239
1008
|
|
240
1009
|
|
@@ -243,6 +1012,9 @@ soy.renderAsFragment = function(
|
|
243
1012
|
* HTML string represents a single node, then that node is returned. Otherwise,
|
244
1013
|
* a DIV element is returned containing the rendered nodes.
|
245
1014
|
*
|
1015
|
+
* NOTE: New code should consider using goog.soy.renderAsElement
|
1016
|
+
* instead (note that the arguments are different).
|
1017
|
+
*
|
246
1018
|
* @param {Function} template The Soy template defining the element's content.
|
247
1019
|
* @param {Object=} opt_templateData The data for the template.
|
248
1020
|
* @param {Document=} opt_document The document used to create DOM nodes. If not
|
@@ -253,57 +1025,9 @@ soy.renderAsFragment = function(
|
|
253
1025
|
*/
|
254
1026
|
soy.renderAsElement = function(
|
255
1027
|
template, opt_templateData, opt_document, opt_injectedData) {
|
256
|
-
return
|
257
|
-
template, opt_templateData,
|
258
|
-
|
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);
|
293
|
-
}
|
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;
|
1028
|
+
return goog.soy.renderAsElement(
|
1029
|
+
template, opt_templateData, opt_injectedData,
|
1030
|
+
new goog.dom.DomHelper(opt_document));
|
307
1031
|
};
|
308
1032
|
|
309
1033
|
|
@@ -452,42 +1176,6 @@ soy.$$EMPTY_TEMPLATE_FN_ = function(opt_data, opt_sb, opt_ijData) {
|
|
452
1176
|
};
|
453
1177
|
|
454
1178
|
|
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
1179
|
// -----------------------------------------------------------------------------
|
492
1180
|
// Escape/filter/normalize.
|
493
1181
|
|
@@ -687,57 +1375,6 @@ soy.$$escapeJsRegex = function(value) {
|
|
687
1375
|
};
|
688
1376
|
|
689
1377
|
|
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
|
-
|
741
1378
|
/**
|
742
1379
|
* Matches all URI mark characters that conflict with HTML attribute delimiters
|
743
1380
|
* or that cannot appear in a CSS uri.
|
@@ -839,33 +1476,17 @@ soy.$$filterCssValue = function(value) {
|
|
839
1476
|
|
840
1477
|
|
841
1478
|
// -----------------------------------------------------------------------------
|
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) {
|
851
|
-
|
852
|
-
str = String(str);
|
853
|
-
|
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)) {
|
857
|
-
return str;
|
858
|
-
}
|
1479
|
+
// Basic directives/functions.
|
859
1480
|
|
860
|
-
return str.replace(/(\r\n|\r|\n)/g, '<br>');
|
861
|
-
};
|
862
1481
|
|
863
1482
|
/**
|
864
|
-
*
|
865
|
-
* @
|
866
|
-
* @
|
1483
|
+
* Converts \r\n, \r, and \n to <br>s
|
1484
|
+
* @param {*} str The string in which to convert newlines.
|
1485
|
+
* @return {string} A copy of {@code str} with converted newlines.
|
867
1486
|
*/
|
868
|
-
soy.$$
|
1487
|
+
soy.$$changeNewlineToBr = function(str) {
|
1488
|
+
return goog.string.newLineToBr(String(str), false);
|
1489
|
+
};
|
869
1490
|
|
870
1491
|
|
871
1492
|
/**
|
@@ -881,113 +1502,9 @@ soy.$$CHANGE_NEWLINE_TO_BR_RE_ = /[\r\n]/;
|
|
881
1502
|
* @return {string} The string including word breaks.
|
882
1503
|
*/
|
883
1504
|
soy.$$insertWordBreaks = function(str, maxCharsBetweenWordBreaks) {
|
884
|
-
|
885
|
-
str = String(str);
|
886
|
-
|
887
|
-
var resultArr = [];
|
888
|
-
var resultArrLen = 0;
|
889
|
-
|
890
|
-
// These variables keep track of important state while looping through str.
|
891
|
-
var isInTag = false; // whether we're inside an HTML tag
|
892
|
-
var isMaybeInEntity = false; // whether we might be inside an HTML entity
|
893
|
-
var numCharsWithoutBreak = 0; // number of characters since last word break
|
894
|
-
var flushIndex = 0; // index of first char not yet flushed to resultArr
|
895
|
-
|
896
|
-
for (var i = 0, n = str.length; i < n; ++i) {
|
897
|
-
var charCode = str.charCodeAt(i);
|
898
|
-
|
899
|
-
// If hit maxCharsBetweenWordBreaks, and not space next, then add <wbr>.
|
900
|
-
if (numCharsWithoutBreak >= maxCharsBetweenWordBreaks &&
|
901
|
-
charCode != soy.$$CharCode_.SPACE) {
|
902
|
-
resultArr[resultArrLen++] = str.substring(flushIndex, i);
|
903
|
-
flushIndex = i;
|
904
|
-
resultArr[resultArrLen++] = soy.$$WORD_BREAK_;
|
905
|
-
numCharsWithoutBreak = 0;
|
906
|
-
}
|
907
|
-
|
908
|
-
if (isInTag) {
|
909
|
-
// If inside an HTML tag and we see '>', it's the end of the tag.
|
910
|
-
if (charCode == soy.$$CharCode_.GREATER_THAN) {
|
911
|
-
isInTag = false;
|
912
|
-
}
|
913
|
-
|
914
|
-
} else if (isMaybeInEntity) {
|
915
|
-
switch (charCode) {
|
916
|
-
// If maybe inside an entity and we see ';', it's the end of the entity.
|
917
|
-
// The entity that just ended counts as one char, so increment
|
918
|
-
// numCharsWithoutBreak.
|
919
|
-
case soy.$$CharCode_.SEMI_COLON:
|
920
|
-
isMaybeInEntity = false;
|
921
|
-
++numCharsWithoutBreak;
|
922
|
-
break;
|
923
|
-
// If maybe inside an entity and we see '<', we weren't actually in an
|
924
|
-
// entity. But now we're inside and HTML tag.
|
925
|
-
case soy.$$CharCode_.LESS_THAN:
|
926
|
-
isMaybeInEntity = false;
|
927
|
-
isInTag = true;
|
928
|
-
break;
|
929
|
-
// If maybe inside an entity and we see ' ', we weren't actually in an
|
930
|
-
// entity. Just correct the state and reset the numCharsWithoutBreak
|
931
|
-
// since we just saw a space.
|
932
|
-
case soy.$$CharCode_.SPACE:
|
933
|
-
isMaybeInEntity = false;
|
934
|
-
numCharsWithoutBreak = 0;
|
935
|
-
break;
|
936
|
-
}
|
937
|
-
|
938
|
-
} else { // !isInTag && !isInEntity
|
939
|
-
switch (charCode) {
|
940
|
-
// When not within a tag or an entity and we see '<', we're now inside
|
941
|
-
// an HTML tag.
|
942
|
-
case soy.$$CharCode_.LESS_THAN:
|
943
|
-
isInTag = true;
|
944
|
-
break;
|
945
|
-
// When not within a tag or an entity and we see '&', we might be inside
|
946
|
-
// an entity.
|
947
|
-
case soy.$$CharCode_.AMPERSAND:
|
948
|
-
isMaybeInEntity = true;
|
949
|
-
break;
|
950
|
-
// When we see a space, reset the numCharsWithoutBreak count.
|
951
|
-
case soy.$$CharCode_.SPACE:
|
952
|
-
numCharsWithoutBreak = 0;
|
953
|
-
break;
|
954
|
-
// When we see a non-space, increment the numCharsWithoutBreak.
|
955
|
-
default:
|
956
|
-
++numCharsWithoutBreak;
|
957
|
-
break;
|
958
|
-
}
|
959
|
-
}
|
960
|
-
}
|
961
|
-
|
962
|
-
// Flush the remaining chars at the end of the string.
|
963
|
-
resultArr[resultArrLen++] = str.substring(flushIndex);
|
964
|
-
|
965
|
-
return resultArr.join('');
|
966
|
-
};
|
967
|
-
|
968
|
-
/**
|
969
|
-
* Special characters used within $$insertWordBreaks().
|
970
|
-
* @enum {number}
|
971
|
-
* @private
|
972
|
-
*/
|
973
|
-
soy.$$CharCode_ = {
|
974
|
-
SPACE: 32, // ' '.charCodeAt(0)
|
975
|
-
AMPERSAND: 38, // '&'.charCodeAt(0)
|
976
|
-
SEMI_COLON: 59, // ';'.charCodeAt(0)
|
977
|
-
LESS_THAN: 60, // '<'.charCodeAt(0)
|
978
|
-
GREATER_THAN: 62 // '>'.charCodeAt(0)
|
1505
|
+
return goog.format.insertWordBreaks(String(str), maxCharsBetweenWordBreaks);
|
979
1506
|
};
|
980
1507
|
|
981
|
-
/**
|
982
|
-
* String inserted as a word break by insertWordBreaks(). Safari requires
|
983
|
-
* <wbr></wbr>, Opera needs the 'shy' entity, though this will give a visible
|
984
|
-
* hyphen at breaks. Other browsers just use <wbr>.
|
985
|
-
* @type {string}
|
986
|
-
* @private
|
987
|
-
*/
|
988
|
-
soy.$$WORD_BREAK_ =
|
989
|
-
soy.$$IS_WEBKIT_ ? '<wbr></wbr>' : soy.$$IS_OPERA_ ? '­' : '<wbr>';
|
990
|
-
|
991
1508
|
|
992
1509
|
/**
|
993
1510
|
* Truncates a string to a given max length (if it's currently longer),
|
@@ -1061,26 +1578,25 @@ soy.$$isLowSurrogate_ = function(ch) {
|
|
1061
1578
|
|
1062
1579
|
|
1063
1580
|
/**
|
1064
|
-
*
|
1065
|
-
*
|
1066
|
-
* @
|
1067
|
-
*
|
1068
|
-
* @return {string} "right" for RTL context and "left" otherwise.
|
1581
|
+
* Cache of bidi formatter by context directionality, so we don't keep on
|
1582
|
+
* creating new objects.
|
1583
|
+
* @type {!Object.<!goog.i18n.BidiFormatter>}
|
1584
|
+
* @private
|
1069
1585
|
*/
|
1070
|
-
soy.$$
|
1071
|
-
return bidiGlobalDir < 0 ? 'right' : 'left';
|
1072
|
-
};
|
1586
|
+
soy.$$bidiFormatterCache_ = {};
|
1073
1587
|
|
1074
1588
|
|
1075
1589
|
/**
|
1076
|
-
* Returns
|
1077
|
-
* bidiGlobalDir.
|
1590
|
+
* Returns cached bidi formatter for bidiGlobalDir, or creates a new one.
|
1078
1591
|
* @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
|
1079
1592
|
* if rtl, 0 if unknown.
|
1080
|
-
* @return {
|
1593
|
+
* @return {goog.i18n.BidiFormatter} A formatter for bidiGlobalDir.
|
1594
|
+
* @private
|
1081
1595
|
*/
|
1082
|
-
soy.$$
|
1083
|
-
return bidiGlobalDir
|
1596
|
+
soy.$$getBidiFormatterInstance_ = function(bidiGlobalDir) {
|
1597
|
+
return soy.$$bidiFormatterCache_[bidiGlobalDir] ||
|
1598
|
+
(soy.$$bidiFormatterCache_[bidiGlobalDir] =
|
1599
|
+
new goog.i18n.BidiFormatter(bidiGlobalDir));
|
1084
1600
|
};
|
1085
1601
|
|
1086
1602
|
|
@@ -1094,11 +1610,10 @@ soy.$$bidiEndEdge = function(bidiGlobalDir) {
|
|
1094
1610
|
* @return {number} 1 if text is LTR, -1 if it is RTL, and 0 if it is neutral.
|
1095
1611
|
*/
|
1096
1612
|
soy.$$bidiTextDir = function(text, opt_isHtml) {
|
1097
|
-
text = soy.$$bidiStripHtmlIfNecessary_(text, opt_isHtml);
|
1098
1613
|
if (!text) {
|
1099
1614
|
return 0;
|
1100
1615
|
}
|
1101
|
-
return
|
1616
|
+
return goog.i18n.bidi.detectRtlDirectionality(text, opt_isHtml) ? -1 : 1;
|
1102
1617
|
};
|
1103
1618
|
|
1104
1619
|
|
@@ -1113,29 +1628,13 @@ soy.$$bidiTextDir = function(text, opt_isHtml) {
|
|
1113
1628
|
* @param {string} text The text whose directionality is to be estimated.
|
1114
1629
|
* @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
|
1115
1630
|
* Default: false.
|
1116
|
-
* @return {
|
1117
|
-
* text in non-LTR context;
|
1631
|
+
* @return {soydata.SanitizedHtmlAttribute} "dir=rtl" for RTL text in non-RTL
|
1632
|
+
* context; "dir=ltr" for LTR text in non-LTR context;
|
1633
|
+
* else, the empty string.
|
1118
1634
|
*/
|
1119
1635
|
soy.$$bidiDirAttr = function(bidiGlobalDir, text, opt_isHtml) {
|
1120
|
-
var dir = soy.$$bidiTextDir(text, opt_isHtml);
|
1121
1636
|
return new soydata.SanitizedHtmlAttribute(
|
1122
|
-
|
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
|
-
'');
|
1637
|
+
soy.$$getBidiFormatterInstance_(bidiGlobalDir).dirAttr(text, opt_isHtml));
|
1139
1638
|
};
|
1140
1639
|
|
1141
1640
|
|
@@ -1155,63 +1654,11 @@ soy.$$bidiMark = function(bidiGlobalDir) {
|
|
1155
1654
|
* bidiGlobalDir, or bidiGlobalDir is 0 (unknown).
|
1156
1655
|
*/
|
1157
1656
|
soy.$$bidiMarkAfter = function(bidiGlobalDir, text, opt_isHtml) {
|
1158
|
-
var
|
1159
|
-
return
|
1160
|
-
};
|
1161
|
-
|
1162
|
-
|
1163
|
-
/**
|
1164
|
-
* Returns a Unicode BiDi mark matching bidiGlobalDir (LRM or RLM) if the
|
1165
|
-
* directionality or the exit directionality of text are opposite to
|
1166
|
-
* bidiGlobalDir. Otherwise returns the empty string.
|
1167
|
-
* If opt_isHtml, makes sure to ignore the LTR nature of the mark-up and escapes
|
1168
|
-
* in text, making the logic suitable for HTML and HTML-escaped text.
|
1169
|
-
* @param {number} bidiGlobalDir The global directionality context: 1 if ltr, -1
|
1170
|
-
* if rtl, 0 if unknown.
|
1171
|
-
* @param {number} dir text's directionality: 1 if ltr, -1 if rtl, 0 if unknown.
|
1172
|
-
* @param {string} text The text whose directionality is to be estimated.
|
1173
|
-
* @param {boolean=} opt_isHtml Whether text is HTML/HTML-escaped.
|
1174
|
-
* Default: false.
|
1175
|
-
* @return {string} A Unicode bidi mark matching bidiGlobalDir, or
|
1176
|
-
* the empty string when text's overall and exit directionalities both match
|
1177
|
-
* bidiGlobalDir, or bidiGlobalDir is 0 (unknown).
|
1178
|
-
* @private
|
1179
|
-
*/
|
1180
|
-
soy.$$bidiMarkAfterKnownDir_ = function(bidiGlobalDir, dir, text, opt_isHtml) {
|
1181
|
-
return (
|
1182
|
-
bidiGlobalDir > 0 && (dir < 0 ||
|
1183
|
-
soy.$$bidiIsRtlExitText_(text, opt_isHtml)) ? '\u200E' : // LRM
|
1184
|
-
bidiGlobalDir < 0 && (dir > 0 ||
|
1185
|
-
soy.$$bidiIsLtrExitText_(text, opt_isHtml)) ? '\u200F' : // RLM
|
1186
|
-
'');
|
1187
|
-
};
|
1188
|
-
|
1189
|
-
|
1190
|
-
/**
|
1191
|
-
* Strips str of any HTML mark-up and escapes. Imprecise in several ways, but
|
1192
|
-
* precision is not very important, since the result is only meant to be used
|
1193
|
-
* for directionality detection.
|
1194
|
-
* @param {string} str The string to be stripped.
|
1195
|
-
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
1196
|
-
* Default: false.
|
1197
|
-
* @return {string} The stripped string.
|
1198
|
-
* @private
|
1199
|
-
*/
|
1200
|
-
soy.$$bidiStripHtmlIfNecessary_ = function(str, opt_isHtml) {
|
1201
|
-
return opt_isHtml ? str.replace(soy.$$BIDI_HTML_SKIP_RE_, ' ') : str;
|
1657
|
+
var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
|
1658
|
+
return formatter.markAfter(text, opt_isHtml);
|
1202
1659
|
};
|
1203
1660
|
|
1204
1661
|
|
1205
|
-
/**
|
1206
|
-
* Simplified regular expression for am HTML tag (opening or closing) or an HTML
|
1207
|
-
* escape - the things we want to skip over in order to ignore their ltr
|
1208
|
-
* characters.
|
1209
|
-
* @type {RegExp}
|
1210
|
-
* @private
|
1211
|
-
*/
|
1212
|
-
soy.$$BIDI_HTML_SKIP_RE_ = /<[^>]*>|&[^;]+;/g;
|
1213
|
-
|
1214
|
-
|
1215
1662
|
/**
|
1216
1663
|
* Returns str wrapped in a <span dir=ltr|rtl> according to its directionality -
|
1217
1664
|
* but only if that is neither neutral nor the same as the global context.
|
@@ -1225,15 +1672,8 @@ soy.$$BIDI_HTML_SKIP_RE_ = /<[^>]*>|&[^;]+;/g;
|
|
1225
1672
|
* @return {string} The wrapped string.
|
1226
1673
|
*/
|
1227
1674
|
soy.$$bidiSpanWrap = function(bidiGlobalDir, str) {
|
1228
|
-
|
1229
|
-
|
1230
|
-
var reset = soy.$$bidiMarkAfterKnownDir_(bidiGlobalDir, textDir, str, true);
|
1231
|
-
if (textDir > 0 && bidiGlobalDir <= 0) {
|
1232
|
-
str = '<span dir=ltr>' + str + '</span>';
|
1233
|
-
} else if (textDir < 0 && bidiGlobalDir >= 0) {
|
1234
|
-
str = '<span dir=rtl>' + str + '</span>';
|
1235
|
-
}
|
1236
|
-
return str + reset;
|
1675
|
+
var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
|
1676
|
+
return formatter.spanWrap(str + '', true);
|
1237
1677
|
};
|
1238
1678
|
|
1239
1679
|
|
@@ -1251,186 +1691,8 @@ soy.$$bidiSpanWrap = function(bidiGlobalDir, str) {
|
|
1251
1691
|
* @return {string} The wrapped string.
|
1252
1692
|
*/
|
1253
1693
|
soy.$$bidiUnicodeWrap = function(bidiGlobalDir, str) {
|
1254
|
-
|
1255
|
-
|
1256
|
-
var reset = soy.$$bidiMarkAfterKnownDir_(bidiGlobalDir, textDir, str, true);
|
1257
|
-
if (textDir > 0 && bidiGlobalDir <= 0) {
|
1258
|
-
str = '\u202A' + str + '\u202C';
|
1259
|
-
} else if (textDir < 0 && bidiGlobalDir >= 0) {
|
1260
|
-
str = '\u202B' + str + '\u202C';
|
1261
|
-
}
|
1262
|
-
return str + reset;
|
1263
|
-
};
|
1264
|
-
|
1265
|
-
|
1266
|
-
/**
|
1267
|
-
* A practical pattern to identify strong LTR character. This pattern is not
|
1268
|
-
* theoretically correct according to unicode standard. It is simplified for
|
1269
|
-
* performance and small code size.
|
1270
|
-
* @type {string}
|
1271
|
-
* @private
|
1272
|
-
*/
|
1273
|
-
soy.$$bidiLtrChars_ =
|
1274
|
-
'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
|
1275
|
-
'\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
|
1276
|
-
|
1277
|
-
|
1278
|
-
/**
|
1279
|
-
* A practical pattern to identify strong neutral and weak character. This
|
1280
|
-
* pattern is not theoretically correct according to unicode standard. It is
|
1281
|
-
* simplified for performance and small code size.
|
1282
|
-
* @type {string}
|
1283
|
-
* @private
|
1284
|
-
*/
|
1285
|
-
soy.$$bidiNeutralChars_ =
|
1286
|
-
'\u0000-\u0020!-@[-`{-\u00BF\u00D7\u00F7\u02B9-\u02FF\u2000-\u2BFF';
|
1287
|
-
|
1288
|
-
|
1289
|
-
/**
|
1290
|
-
* A practical pattern to identify strong RTL character. This pattern is not
|
1291
|
-
* theoretically correct according to unicode standard. It is simplified for
|
1292
|
-
* performance and small code size.
|
1293
|
-
* @type {string}
|
1294
|
-
* @private
|
1295
|
-
*/
|
1296
|
-
soy.$$bidiRtlChars_ = '\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
|
1297
|
-
|
1298
|
-
|
1299
|
-
/**
|
1300
|
-
* Regular expressions to check if a piece of text is of RTL directionality
|
1301
|
-
* on first character with strong directionality.
|
1302
|
-
* @type {RegExp}
|
1303
|
-
* @private
|
1304
|
-
*/
|
1305
|
-
soy.$$bidiRtlDirCheckRe_ = new RegExp(
|
1306
|
-
'^[^' + soy.$$bidiLtrChars_ + ']*[' + soy.$$bidiRtlChars_ + ']');
|
1307
|
-
|
1308
|
-
|
1309
|
-
/**
|
1310
|
-
* Regular expressions to check if a piece of text is of neutral directionality.
|
1311
|
-
* Url are considered as neutral.
|
1312
|
-
* @type {RegExp}
|
1313
|
-
* @private
|
1314
|
-
*/
|
1315
|
-
soy.$$bidiNeutralDirCheckRe_ = new RegExp(
|
1316
|
-
'^[' + soy.$$bidiNeutralChars_ + ']*$|^http://');
|
1317
|
-
|
1318
|
-
|
1319
|
-
/**
|
1320
|
-
* Check the directionality of the a piece of text based on the first character
|
1321
|
-
* with strong directionality.
|
1322
|
-
* @param {string} str string being checked.
|
1323
|
-
* @return {boolean} return true if rtl directionality is being detected.
|
1324
|
-
* @private
|
1325
|
-
*/
|
1326
|
-
soy.$$bidiIsRtlText_ = function(str) {
|
1327
|
-
return soy.$$bidiRtlDirCheckRe_.test(str);
|
1328
|
-
};
|
1329
|
-
|
1330
|
-
|
1331
|
-
/**
|
1332
|
-
* Check the directionality of the a piece of text based on the first character
|
1333
|
-
* with strong directionality.
|
1334
|
-
* @param {string} str string being checked.
|
1335
|
-
* @return {boolean} true if all characters have neutral directionality.
|
1336
|
-
* @private
|
1337
|
-
*/
|
1338
|
-
soy.$$bidiIsNeutralText_ = function(str) {
|
1339
|
-
return soy.$$bidiNeutralDirCheckRe_.test(str);
|
1340
|
-
};
|
1341
|
-
|
1342
|
-
|
1343
|
-
/**
|
1344
|
-
* This constant controls threshold of rtl directionality.
|
1345
|
-
* @type {number}
|
1346
|
-
* @private
|
1347
|
-
*/
|
1348
|
-
soy.$$bidiRtlDetectionThreshold_ = 0.40;
|
1349
|
-
|
1350
|
-
|
1351
|
-
/**
|
1352
|
-
* Returns the RTL ratio based on word count.
|
1353
|
-
* @param {string} str the string that need to be checked.
|
1354
|
-
* @return {number} the ratio of RTL words among all words with directionality.
|
1355
|
-
* @private
|
1356
|
-
*/
|
1357
|
-
soy.$$bidiRtlWordRatio_ = function(str) {
|
1358
|
-
var rtlCount = 0;
|
1359
|
-
var totalCount = 0;
|
1360
|
-
var tokens = str.split(' ');
|
1361
|
-
for (var i = 0; i < tokens.length; i++) {
|
1362
|
-
if (soy.$$bidiIsRtlText_(tokens[i])) {
|
1363
|
-
rtlCount++;
|
1364
|
-
totalCount++;
|
1365
|
-
} else if (!soy.$$bidiIsNeutralText_(tokens[i])) {
|
1366
|
-
totalCount++;
|
1367
|
-
}
|
1368
|
-
}
|
1369
|
-
|
1370
|
-
return totalCount == 0 ? 0 : rtlCount / totalCount;
|
1371
|
-
};
|
1372
|
-
|
1373
|
-
|
1374
|
-
/**
|
1375
|
-
* Check the directionality of a piece of text, return true if the piece of
|
1376
|
-
* text should be laid out in RTL direction.
|
1377
|
-
* @param {string} str The piece of text that need to be detected.
|
1378
|
-
* @return {boolean} true if this piece of text should be laid out in RTL.
|
1379
|
-
* @private
|
1380
|
-
*/
|
1381
|
-
soy.$$bidiDetectRtlDirectionality_ = function(str) {
|
1382
|
-
return soy.$$bidiRtlWordRatio_(str) >
|
1383
|
-
soy.$$bidiRtlDetectionThreshold_;
|
1384
|
-
};
|
1385
|
-
|
1386
|
-
|
1387
|
-
/**
|
1388
|
-
* Regular expressions to check if the last strongly-directional character in a
|
1389
|
-
* piece of text is LTR.
|
1390
|
-
* @type {RegExp}
|
1391
|
-
* @private
|
1392
|
-
*/
|
1393
|
-
soy.$$bidiLtrExitDirCheckRe_ = new RegExp(
|
1394
|
-
'[' + soy.$$bidiLtrChars_ + '][^' + soy.$$bidiRtlChars_ + ']*$');
|
1395
|
-
|
1396
|
-
|
1397
|
-
/**
|
1398
|
-
* Regular expressions to check if the last strongly-directional character in a
|
1399
|
-
* piece of text is RTL.
|
1400
|
-
* @type {RegExp}
|
1401
|
-
* @private
|
1402
|
-
*/
|
1403
|
-
soy.$$bidiRtlExitDirCheckRe_ = new RegExp(
|
1404
|
-
'[' + soy.$$bidiRtlChars_ + '][^' + soy.$$bidiLtrChars_ + ']*$');
|
1405
|
-
|
1406
|
-
|
1407
|
-
/**
|
1408
|
-
* Check if the exit directionality a piece of text is LTR, i.e. if the last
|
1409
|
-
* strongly-directional character in the string is LTR.
|
1410
|
-
* @param {string} str string being checked.
|
1411
|
-
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
1412
|
-
* Default: false.
|
1413
|
-
* @return {boolean} Whether LTR exit directionality was detected.
|
1414
|
-
* @private
|
1415
|
-
*/
|
1416
|
-
soy.$$bidiIsLtrExitText_ = function(str, opt_isHtml) {
|
1417
|
-
str = soy.$$bidiStripHtmlIfNecessary_(str, opt_isHtml);
|
1418
|
-
return soy.$$bidiLtrExitDirCheckRe_.test(str);
|
1419
|
-
};
|
1420
|
-
|
1421
|
-
|
1422
|
-
/**
|
1423
|
-
* Check if the exit directionality a piece of text is RTL, i.e. if the last
|
1424
|
-
* strongly-directional character in the string is RTL.
|
1425
|
-
* @param {string} str string being checked.
|
1426
|
-
* @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
|
1427
|
-
* Default: false.
|
1428
|
-
* @return {boolean} Whether RTL exit directionality was detected.
|
1429
|
-
* @private
|
1430
|
-
*/
|
1431
|
-
soy.$$bidiIsRtlExitText_ = function(str, opt_isHtml) {
|
1432
|
-
str = soy.$$bidiStripHtmlIfNecessary_(str, opt_isHtml);
|
1433
|
-
return soy.$$bidiRtlExitDirCheckRe_.test(str);
|
1694
|
+
var formatter = soy.$$getBidiFormatterInstance_(bidiGlobalDir);
|
1695
|
+
return formatter.unicodeWrap(str + '', true);
|
1434
1696
|
};
|
1435
1697
|
|
1436
1698
|
|