closure 1.4.2 → 1.4.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 ? '&shy;' : '<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
- * Base name for the soy utilities, when used outside of Closure Library.
40
- * Check to see soy is already defined in the current scope before asigning to
41
- * prevent clobbering if soyutils.js is loaded more than once.
42
- * @type {Object}
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
- var soy = soy || {};
45
- soy.esc = soy.esc || {};
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
- // Just enough browser detection for this file.
49
- (function() {
50
- var ua = navigator.userAgent;
51
- var isOpera = ua.indexOf('Opera') == 0;
52
- /**
53
- * @type {boolean}
54
- * @private
55
- */
56
- soy.$$IS_OPERA_ = isOpera;
57
- /**
58
- * @type {boolean}
59
- * @private
60
- */
61
- soy.$$IS_IE_ = !isOpera && ua.indexOf('MSIE') != -1;
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
- // StringBuilder (compatible with the 'stringbuilder' code style).
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
- * Utility class to facilitate much faster string concatenation in IE,
76
- * using Array.join() rather than the '+' operator. For other browsers
77
- * we simply use the '+' operator.
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 {Object|number|string|boolean=} opt_a1 Optional first initial item
80
- * to append.
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
- soy.StringBuilder = function(opt_a1, var_args) {
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
- * Internal buffer for the string to be concatenated.
89
- * @type {string|Array}
90
- * @private
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
- this.buffer_ = soy.$$IS_IE_ ? [] : '';
365
+ StringBuffer: function(opt_a1, var_args) {
93
366
 
94
- if (opt_a1 != null) {
95
- this.append.apply(this, arguments);
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
- soy.StringBuilder.prototype.bufferLength_ = 0;
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 {soy.StringBuilder} This same StringBuilder object.
422
+ * @return {goog.string.StringBuffer} This same StringBuilder object.
119
423
  */
120
- soy.StringBuilder.prototype.append = function(a1, opt_a2, var_args) {
424
+ goog.string.StringBuffer.prototype.append = function(a1, opt_a2, var_args) {
121
425
 
122
- if (soy.$$IS_IE_) {
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
- soy.StringBuilder.prototype.clear = function() {
455
+ goog.string.StringBuffer.prototype.clear = function() {
152
456
 
153
- if (soy.$$IS_IE_) {
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
- soy.StringBuilder.prototype.toString = function() {
472
+ goog.string.StringBuffer.prototype.toString = function() {
169
473
 
170
- if (soy.$$IS_IE_) {
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
- // Public utilities.
816
+ // StringBuilder (compatible with the 'stringbuilder' code style).
188
817
 
189
818
 
190
819
  /**
191
- * Immutable object that is passed into templates that are rendered
192
- * without any data.
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
- * @type {Object}
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
- soy.$$DEFAULT_TEMPLATE_DATA_ = {};
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 = function(
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.$$renderWithWrapper_(
236
- template, opt_templateData, opt_document, false /* asElement */,
237
- opt_injectedData);
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 /** @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);
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
- * Regular expression used within $$changeNewlineToBr().
865
- * @type {RegExp}
866
- * @private
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.$$CHANGE_NEWLINE_TO_BR_RE_ = /[\r\n]/;
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_ ? '&shy;' : '<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
- * 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.
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.$$bidiStartEdge = function(bidiGlobalDir) {
1071
- return bidiGlobalDir < 0 ? 'right' : 'left';
1072
- };
1586
+ soy.$$bidiFormatterCache_ = {};
1073
1587
 
1074
1588
 
1075
1589
  /**
1076
- * Returns the trailing horizontal edge, i.e. "right" or "left", depending on
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 {string} "left" for RTL context and "right" otherwise.
1593
+ * @return {goog.i18n.BidiFormatter} A formatter for bidiGlobalDir.
1594
+ * @private
1081
1595
  */
1082
- soy.$$bidiEndEdge = function(bidiGlobalDir) {
1083
- return bidiGlobalDir < 0 ? 'left' : 'right';
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 soy.$$bidiDetectRtlDirectionality_(text) ? -1 : 1;
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 {string} "dir=rtl" for RTL text in non-RTL context; "dir=ltr" for LTR
1117
- * text in non-LTR context; else, the empty string.
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
- 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
- '');
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 dir = soy.$$bidiTextDir(text, opt_isHtml);
1159
- return soy.$$bidiMarkAfterKnownDir_(bidiGlobalDir, dir, text, opt_isHtml);
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
- str = String(str);
1229
- var textDir = soy.$$bidiTextDir(str, true);
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
- str = String(str);
1255
- var textDir = soy.$$bidiTextDir(str, true);
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