Upstream version 11.40.271.0
[platform/framework/web/crosswalk.git] / src / third_party / google_input_tools / third_party / closure_library / closure / goog / i18n / bidi.js
1 // Copyright 2007 The Closure Library Authors. All Rights Reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //      http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS-IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 /**
16  * @fileoverview Utility functions for supporting Bidi issues.
17  */
18
19
20 /**
21  * Namespace for bidi supporting functions.
22  */
23 goog.provide('goog.i18n.bidi');
24 goog.provide('goog.i18n.bidi.Dir');
25 goog.provide('goog.i18n.bidi.DirectionalString');
26 goog.provide('goog.i18n.bidi.Format');
27
28
29 /**
30  * @define {boolean} FORCE_RTL forces the {@link goog.i18n.bidi.IS_RTL} constant
31  * to say that the current locale is a RTL locale.  This should only be used
32  * if you want to override the default behavior for deciding whether the
33  * current locale is RTL or not.
34  *
35  * {@see goog.i18n.bidi.IS_RTL}
36  */
37 goog.define('goog.i18n.bidi.FORCE_RTL', false);
38
39
40 /**
41  * Constant that defines whether or not the current locale is a RTL locale.
42  * If {@link goog.i18n.bidi.FORCE_RTL} is not true, this constant will default
43  * to check that {@link goog.LOCALE} is one of a few major RTL locales.
44  *
45  * <p>This is designed to be a maximally efficient compile-time constant. For
46  * example, for the default goog.LOCALE, compiling
47  * "if (goog.i18n.bidi.IS_RTL) alert('rtl') else {}" should produce no code. It
48  * is this design consideration that limits the implementation to only
49  * supporting a few major RTL locales, as opposed to the broader repertoire of
50  * something like goog.i18n.bidi.isRtlLanguage.
51  *
52  * <p>Since this constant refers to the directionality of the locale, it is up
53  * to the caller to determine if this constant should also be used for the
54  * direction of the UI.
55  *
56  * {@see goog.LOCALE}
57  *
58  * @type {boolean}
59  *
60  * TODO(user): write a test that checks that this is a compile-time constant.
61  */
62 goog.i18n.bidi.IS_RTL = goog.i18n.bidi.FORCE_RTL ||
63     (
64         (goog.LOCALE.substring(0, 2).toLowerCase() == 'ar' ||
65          goog.LOCALE.substring(0, 2).toLowerCase() == 'fa' ||
66          goog.LOCALE.substring(0, 2).toLowerCase() == 'he' ||
67          goog.LOCALE.substring(0, 2).toLowerCase() == 'iw' ||
68          goog.LOCALE.substring(0, 2).toLowerCase() == 'ps' ||
69          goog.LOCALE.substring(0, 2).toLowerCase() == 'sd' ||
70          goog.LOCALE.substring(0, 2).toLowerCase() == 'ug' ||
71          goog.LOCALE.substring(0, 2).toLowerCase() == 'ur' ||
72          goog.LOCALE.substring(0, 2).toLowerCase() == 'yi') &&
73         (goog.LOCALE.length == 2 ||
74          goog.LOCALE.substring(2, 3) == '-' ||
75          goog.LOCALE.substring(2, 3) == '_')
76     ) || (
77         goog.LOCALE.length >= 3 &&
78         goog.LOCALE.substring(0, 3).toLowerCase() == 'ckb' &&
79         (goog.LOCALE.length == 3 ||
80          goog.LOCALE.substring(3, 4) == '-' ||
81          goog.LOCALE.substring(3, 4) == '_')
82     );
83
84
85 /**
86  * Unicode formatting characters and directionality string constants.
87  * @enum {string}
88  */
89 goog.i18n.bidi.Format = {
90   /** Unicode "Left-To-Right Embedding" (LRE) character. */
91   LRE: '\u202A',
92   /** Unicode "Right-To-Left Embedding" (RLE) character. */
93   RLE: '\u202B',
94   /** Unicode "Pop Directional Formatting" (PDF) character. */
95   PDF: '\u202C',
96   /** Unicode "Left-To-Right Mark" (LRM) character. */
97   LRM: '\u200E',
98   /** Unicode "Right-To-Left Mark" (RLM) character. */
99   RLM: '\u200F'
100 };
101
102
103 /**
104  * Directionality enum.
105  * @enum {number}
106  */
107 goog.i18n.bidi.Dir = {
108   /**
109    * Left-to-right.
110    */
111   LTR: 1,
112
113   /**
114    * Right-to-left.
115    */
116   RTL: -1,
117
118   /**
119    * Neither left-to-right nor right-to-left.
120    */
121   NEUTRAL: 0,
122
123   /**
124    * A historical misnomer for NEUTRAL.
125    * @deprecated For "neutral", use NEUTRAL; for "unknown", use null.
126    */
127   UNKNOWN: 0
128 };
129
130
131 /**
132  * 'right' string constant.
133  * @type {string}
134  */
135 goog.i18n.bidi.RIGHT = 'right';
136
137
138 /**
139  * 'left' string constant.
140  * @type {string}
141  */
142 goog.i18n.bidi.LEFT = 'left';
143
144
145 /**
146  * 'left' if locale is RTL, 'right' if not.
147  * @type {string}
148  */
149 goog.i18n.bidi.I18N_RIGHT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.LEFT :
150     goog.i18n.bidi.RIGHT;
151
152
153 /**
154  * 'right' if locale is RTL, 'left' if not.
155  * @type {string}
156  */
157 goog.i18n.bidi.I18N_LEFT = goog.i18n.bidi.IS_RTL ? goog.i18n.bidi.RIGHT :
158     goog.i18n.bidi.LEFT;
159
160
161 /**
162  * Convert a directionality given in various formats to a goog.i18n.bidi.Dir
163  * constant. Useful for interaction with different standards of directionality
164  * representation.
165  *
166  * @param {goog.i18n.bidi.Dir|number|boolean|null} givenDir Directionality given
167  *     in one of the following formats:
168  *     1. A goog.i18n.bidi.Dir constant.
169  *     2. A number (positive = LTR, negative = RTL, 0 = neutral).
170  *     3. A boolean (true = RTL, false = LTR).
171  *     4. A null for unknown directionality.
172  * @param {boolean=} opt_noNeutral Whether a givenDir of zero or
173  *     goog.i18n.bidi.Dir.NEUTRAL should be treated as null, i.e. unknown, in
174  *     order to preserve legacy behavior.
175  * @return {?goog.i18n.bidi.Dir} A goog.i18n.bidi.Dir constant matching the
176  *     given directionality. If given null, returns null (i.e. unknown).
177  */
178 goog.i18n.bidi.toDir = function(givenDir, opt_noNeutral) {
179   if (typeof givenDir == 'number') {
180     // This includes the non-null goog.i18n.bidi.Dir case.
181     return givenDir > 0 ? goog.i18n.bidi.Dir.LTR :
182         givenDir < 0 ? goog.i18n.bidi.Dir.RTL :
183         opt_noNeutral ? null : goog.i18n.bidi.Dir.NEUTRAL;
184   } else if (givenDir == null) {
185     return null;
186   } else {
187     // Must be typeof givenDir == 'boolean'.
188     return givenDir ? goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR;
189   }
190 };
191
192
193 /**
194  * A practical pattern to identify strong LTR characters. This pattern is not
195  * theoretically correct according to the Unicode standard. It is simplified for
196  * performance and small code size.
197  * @type {string}
198  * @private
199  */
200 goog.i18n.bidi.ltrChars_ =
201     'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590\u0800-\u1FFF' +
202     '\u200E\u2C00-\uFB1C\uFE00-\uFE6F\uFEFD-\uFFFF';
203
204
205 /**
206  * A practical pattern to identify strong RTL character. This pattern is not
207  * theoretically correct according to the Unicode standard. It is simplified
208  * for performance and small code size.
209  * @type {string}
210  * @private
211  */
212 goog.i18n.bidi.rtlChars_ = '\u0591-\u07FF\u200F\uFB1D-\uFDFF\uFE70-\uFEFC';
213
214
215 /**
216  * Simplified regular expression for an HTML tag (opening or closing) or an HTML
217  * escape. We might want to skip over such expressions when estimating the text
218  * directionality.
219  * @type {RegExp}
220  * @private
221  */
222 goog.i18n.bidi.htmlSkipReg_ = /<[^>]*>|&[^;]+;/g;
223
224
225 /**
226  * Returns the input text with spaces instead of HTML tags or HTML escapes, if
227  * opt_isStripNeeded is true. Else returns the input as is.
228  * Useful for text directionality estimation.
229  * Note: the function should not be used in other contexts; it is not 100%
230  * correct, but rather a good-enough implementation for directionality
231  * estimation purposes.
232  * @param {string} str The given string.
233  * @param {boolean=} opt_isStripNeeded Whether to perform the stripping.
234  *     Default: false (to retain consistency with calling functions).
235  * @return {string} The given string cleaned of HTML tags / escapes.
236  * @private
237  */
238 goog.i18n.bidi.stripHtmlIfNeeded_ = function(str, opt_isStripNeeded) {
239   return opt_isStripNeeded ? str.replace(goog.i18n.bidi.htmlSkipReg_, '') :
240       str;
241 };
242
243
244 /**
245  * Regular expression to check for RTL characters.
246  * @type {RegExp}
247  * @private
248  */
249 goog.i18n.bidi.rtlCharReg_ = new RegExp('[' + goog.i18n.bidi.rtlChars_ + ']');
250
251
252 /**
253  * Regular expression to check for LTR characters.
254  * @type {RegExp}
255  * @private
256  */
257 goog.i18n.bidi.ltrCharReg_ = new RegExp('[' + goog.i18n.bidi.ltrChars_ + ']');
258
259
260 /**
261  * Test whether the given string has any RTL characters in it.
262  * @param {string} str The given string that need to be tested.
263  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
264  *     Default: false.
265  * @return {boolean} Whether the string contains RTL characters.
266  */
267 goog.i18n.bidi.hasAnyRtl = function(str, opt_isHtml) {
268   return goog.i18n.bidi.rtlCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
269       str, opt_isHtml));
270 };
271
272
273 /**
274  * Test whether the given string has any RTL characters in it.
275  * @param {string} str The given string that need to be tested.
276  * @return {boolean} Whether the string contains RTL characters.
277  * @deprecated Use hasAnyRtl.
278  */
279 goog.i18n.bidi.hasRtlChar = goog.i18n.bidi.hasAnyRtl;
280
281
282 /**
283  * Test whether the given string has any LTR characters in it.
284  * @param {string} str The given string that need to be tested.
285  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
286  *     Default: false.
287  * @return {boolean} Whether the string contains LTR characters.
288  */
289 goog.i18n.bidi.hasAnyLtr = function(str, opt_isHtml) {
290   return goog.i18n.bidi.ltrCharReg_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
291       str, opt_isHtml));
292 };
293
294
295 /**
296  * Regular expression pattern to check if the first character in the string
297  * is LTR.
298  * @type {RegExp}
299  * @private
300  */
301 goog.i18n.bidi.ltrRe_ = new RegExp('^[' + goog.i18n.bidi.ltrChars_ + ']');
302
303
304 /**
305  * Regular expression pattern to check if the first character in the string
306  * is RTL.
307  * @type {RegExp}
308  * @private
309  */
310 goog.i18n.bidi.rtlRe_ = new RegExp('^[' + goog.i18n.bidi.rtlChars_ + ']');
311
312
313 /**
314  * Check if the first character in the string is RTL or not.
315  * @param {string} str The given string that need to be tested.
316  * @return {boolean} Whether the first character in str is an RTL char.
317  */
318 goog.i18n.bidi.isRtlChar = function(str) {
319   return goog.i18n.bidi.rtlRe_.test(str);
320 };
321
322
323 /**
324  * Check if the first character in the string is LTR or not.
325  * @param {string} str The given string that need to be tested.
326  * @return {boolean} Whether the first character in str is an LTR char.
327  */
328 goog.i18n.bidi.isLtrChar = function(str) {
329   return goog.i18n.bidi.ltrRe_.test(str);
330 };
331
332
333 /**
334  * Check if the first character in the string is neutral or not.
335  * @param {string} str The given string that need to be tested.
336  * @return {boolean} Whether the first character in str is a neutral char.
337  */
338 goog.i18n.bidi.isNeutralChar = function(str) {
339   return !goog.i18n.bidi.isLtrChar(str) && !goog.i18n.bidi.isRtlChar(str);
340 };
341
342
343 /**
344  * Regular expressions to check if a piece of text is of LTR directionality
345  * on first character with strong directionality.
346  * @type {RegExp}
347  * @private
348  */
349 goog.i18n.bidi.ltrDirCheckRe_ = new RegExp(
350     '^[^' + goog.i18n.bidi.rtlChars_ + ']*[' + goog.i18n.bidi.ltrChars_ + ']');
351
352
353 /**
354  * Regular expressions to check if a piece of text is of RTL directionality
355  * on first character with strong directionality.
356  * @type {RegExp}
357  * @private
358  */
359 goog.i18n.bidi.rtlDirCheckRe_ = new RegExp(
360     '^[^' + goog.i18n.bidi.ltrChars_ + ']*[' + goog.i18n.bidi.rtlChars_ + ']');
361
362
363 /**
364  * Check whether the first strongly directional character (if any) is RTL.
365  * @param {string} str String being checked.
366  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
367  *     Default: false.
368  * @return {boolean} Whether RTL directionality is detected using the first
369  *     strongly-directional character method.
370  */
371 goog.i18n.bidi.startsWithRtl = function(str, opt_isHtml) {
372   return goog.i18n.bidi.rtlDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
373       str, opt_isHtml));
374 };
375
376
377 /**
378  * Check whether the first strongly directional character (if any) is RTL.
379  * @param {string} str String being checked.
380  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
381  *     Default: false.
382  * @return {boolean} Whether RTL directionality is detected using the first
383  *     strongly-directional character method.
384  * @deprecated Use startsWithRtl.
385  */
386 goog.i18n.bidi.isRtlText = goog.i18n.bidi.startsWithRtl;
387
388
389 /**
390  * Check whether the first strongly directional character (if any) is LTR.
391  * @param {string} str String being checked.
392  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
393  *     Default: false.
394  * @return {boolean} Whether LTR directionality is detected using the first
395  *     strongly-directional character method.
396  */
397 goog.i18n.bidi.startsWithLtr = function(str, opt_isHtml) {
398   return goog.i18n.bidi.ltrDirCheckRe_.test(goog.i18n.bidi.stripHtmlIfNeeded_(
399       str, opt_isHtml));
400 };
401
402
403 /**
404  * Check whether the first strongly directional character (if any) is LTR.
405  * @param {string} str String being checked.
406  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
407  *     Default: false.
408  * @return {boolean} Whether LTR directionality is detected using the first
409  *     strongly-directional character method.
410  * @deprecated Use startsWithLtr.
411  */
412 goog.i18n.bidi.isLtrText = goog.i18n.bidi.startsWithLtr;
413
414
415 /**
416  * Regular expression to check if a string looks like something that must
417  * always be LTR even in RTL text, e.g. a URL. When estimating the
418  * directionality of text containing these, we treat these as weakly LTR,
419  * like numbers.
420  * @type {RegExp}
421  * @private
422  */
423 goog.i18n.bidi.isRequiredLtrRe_ = /^http:\/\/.*/;
424
425
426 /**
427  * Check whether the input string either contains no strongly directional
428  * characters or looks like a url.
429  * @param {string} str String being checked.
430  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
431  *     Default: false.
432  * @return {boolean} Whether neutral directionality is detected.
433  */
434 goog.i18n.bidi.isNeutralText = function(str, opt_isHtml) {
435   str = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml);
436   return goog.i18n.bidi.isRequiredLtrRe_.test(str) ||
437       !goog.i18n.bidi.hasAnyLtr(str) && !goog.i18n.bidi.hasAnyRtl(str);
438 };
439
440
441 /**
442  * Regular expressions to check if the last strongly-directional character in a
443  * piece of text is LTR.
444  * @type {RegExp}
445  * @private
446  */
447 goog.i18n.bidi.ltrExitDirCheckRe_ = new RegExp(
448     '[' + goog.i18n.bidi.ltrChars_ + '][^' + goog.i18n.bidi.rtlChars_ + ']*$');
449
450
451 /**
452  * Regular expressions to check if the last strongly-directional character in a
453  * piece of text is RTL.
454  * @type {RegExp}
455  * @private
456  */
457 goog.i18n.bidi.rtlExitDirCheckRe_ = new RegExp(
458     '[' + goog.i18n.bidi.rtlChars_ + '][^' + goog.i18n.bidi.ltrChars_ + ']*$');
459
460
461 /**
462  * Check if the exit directionality a piece of text is LTR, i.e. if the last
463  * strongly-directional character in the string is LTR.
464  * @param {string} str String being checked.
465  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
466  *     Default: false.
467  * @return {boolean} Whether LTR exit directionality was detected.
468  */
469 goog.i18n.bidi.endsWithLtr = function(str, opt_isHtml) {
470   return goog.i18n.bidi.ltrExitDirCheckRe_.test(
471       goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
472 };
473
474
475 /**
476  * Check if the exit directionality a piece of text is LTR, i.e. if the last
477  * strongly-directional character in the string is LTR.
478  * @param {string} str String being checked.
479  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
480  *     Default: false.
481  * @return {boolean} Whether LTR exit directionality was detected.
482  * @deprecated Use endsWithLtr.
483  */
484 goog.i18n.bidi.isLtrExitText = goog.i18n.bidi.endsWithLtr;
485
486
487 /**
488  * Check if the exit directionality a piece of text is RTL, i.e. if the last
489  * strongly-directional character in the string is RTL.
490  * @param {string} str String being checked.
491  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
492  *     Default: false.
493  * @return {boolean} Whether RTL exit directionality was detected.
494  */
495 goog.i18n.bidi.endsWithRtl = function(str, opt_isHtml) {
496   return goog.i18n.bidi.rtlExitDirCheckRe_.test(
497       goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml));
498 };
499
500
501 /**
502  * Check if the exit directionality a piece of text is RTL, i.e. if the last
503  * strongly-directional character in the string is RTL.
504  * @param {string} str String being checked.
505  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
506  *     Default: false.
507  * @return {boolean} Whether RTL exit directionality was detected.
508  * @deprecated Use endsWithRtl.
509  */
510 goog.i18n.bidi.isRtlExitText = goog.i18n.bidi.endsWithRtl;
511
512
513 /**
514  * A regular expression for matching right-to-left language codes.
515  * See {@link #isRtlLanguage} for the design.
516  * @type {RegExp}
517  * @private
518  */
519 goog.i18n.bidi.rtlLocalesRe_ = new RegExp(
520     '^(ar|ckb|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|' +
521     '.*[-_](Arab|Hebr|Thaa|Nkoo|Tfng))' +
522     '(?!.*[-_](Latn|Cyrl)($|-|_))($|-|_)',
523     'i');
524
525
526 /**
527  * Check if a BCP 47 / III language code indicates an RTL language, i.e. either:
528  * - a language code explicitly specifying one of the right-to-left scripts,
529  *   e.g. "az-Arab", or<p>
530  * - a language code specifying one of the languages normally written in a
531  *   right-to-left script, e.g. "fa" (Farsi), except ones explicitly specifying
532  *   Latin or Cyrillic script (which are the usual LTR alternatives).<p>
533  * The list of right-to-left scripts appears in the 100-199 range in
534  * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and
535  * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and
536  * Tifinagh, which also have significant modern usage. The rest (Syriac,
537  * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage
538  * and are not recognized to save on code size.
539  * The languages usually written in a right-to-left script are taken as those
540  * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng  in
541  * http://www.iana.org/assignments/language-subtag-registry,
542  * as well as Central (or Sorani) Kurdish (ckb), Sindhi (sd) and Uyghur (ug).
543  * Other subtags of the language code, e.g. regions like EG (Egypt), are
544  * ignored.
545  * @param {string} lang BCP 47 (a.k.a III) language code.
546  * @return {boolean} Whether the language code is an RTL language.
547  */
548 goog.i18n.bidi.isRtlLanguage = function(lang) {
549   return goog.i18n.bidi.rtlLocalesRe_.test(lang);
550 };
551
552
553 /**
554  * Regular expression for bracket guard replacement in html.
555  * @type {RegExp}
556  * @private
557  */
558 goog.i18n.bidi.bracketGuardHtmlRe_ =
559     /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(&lt;.*?(&gt;)+)/g;
560
561
562 /**
563  * Regular expression for bracket guard replacement in text.
564  * @type {RegExp}
565  * @private
566  */
567 goog.i18n.bidi.bracketGuardTextRe_ =
568     /(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)/g;
569
570
571 /**
572  * Apply bracket guard using html span tag. This is to address the problem of
573  * messy bracket display frequently happens in RTL layout.
574  * @param {string} s The string that need to be processed.
575  * @param {boolean=} opt_isRtlContext specifies default direction (usually
576  *     direction of the UI).
577  * @return {string} The processed string, with all bracket guarded.
578  */
579 goog.i18n.bidi.guardBracketInHtml = function(s, opt_isRtlContext) {
580   var useRtl = opt_isRtlContext === undefined ?
581       goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext;
582   if (useRtl) {
583     return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_,
584         '<span dir=rtl>$&</span>');
585   }
586   return s.replace(goog.i18n.bidi.bracketGuardHtmlRe_,
587       '<span dir=ltr>$&</span>');
588 };
589
590
591 /**
592  * Apply bracket guard using LRM and RLM. This is to address the problem of
593  * messy bracket display frequently happens in RTL layout.
594  * This version works for both plain text and html. But it does not work as
595  * good as guardBracketInHtml in some cases.
596  * @param {string} s The string that need to be processed.
597  * @param {boolean=} opt_isRtlContext specifies default direction (usually
598  *     direction of the UI).
599  * @return {string} The processed string, with all bracket guarded.
600  */
601 goog.i18n.bidi.guardBracketInText = function(s, opt_isRtlContext) {
602   var useRtl = opt_isRtlContext === undefined ?
603       goog.i18n.bidi.hasAnyRtl(s) : opt_isRtlContext;
604   var mark = useRtl ? goog.i18n.bidi.Format.RLM : goog.i18n.bidi.Format.LRM;
605   return s.replace(goog.i18n.bidi.bracketGuardTextRe_, mark + '$&' + mark);
606 };
607
608
609 /**
610  * Enforce the html snippet in RTL directionality regardless overall context.
611  * If the html piece was enclosed by tag, dir will be applied to existing
612  * tag, otherwise a span tag will be added as wrapper. For this reason, if
613  * html snippet start with with tag, this tag must enclose the whole piece. If
614  * the tag already has a dir specified, this new one will override existing
615  * one in behavior (tested on FF and IE).
616  * @param {string} html The string that need to be processed.
617  * @return {string} The processed string, with directionality enforced to RTL.
618  */
619 goog.i18n.bidi.enforceRtlInHtml = function(html) {
620   if (html.charAt(0) == '<') {
621     return html.replace(/<\w+/, '$& dir=rtl');
622   }
623   // '\n' is important for FF so that it won't incorrectly merge span groups
624   return '\n<span dir=rtl>' + html + '</span>';
625 };
626
627
628 /**
629  * Enforce RTL on both end of the given text piece using unicode BiDi formatting
630  * characters RLE and PDF.
631  * @param {string} text The piece of text that need to be wrapped.
632  * @return {string} The wrapped string after process.
633  */
634 goog.i18n.bidi.enforceRtlInText = function(text) {
635   return goog.i18n.bidi.Format.RLE + text + goog.i18n.bidi.Format.PDF;
636 };
637
638
639 /**
640  * Enforce the html snippet in RTL directionality regardless overall context.
641  * If the html piece was enclosed by tag, dir will be applied to existing
642  * tag, otherwise a span tag will be added as wrapper. For this reason, if
643  * html snippet start with with tag, this tag must enclose the whole piece. If
644  * the tag already has a dir specified, this new one will override existing
645  * one in behavior (tested on FF and IE).
646  * @param {string} html The string that need to be processed.
647  * @return {string} The processed string, with directionality enforced to RTL.
648  */
649 goog.i18n.bidi.enforceLtrInHtml = function(html) {
650   if (html.charAt(0) == '<') {
651     return html.replace(/<\w+/, '$& dir=ltr');
652   }
653   // '\n' is important for FF so that it won't incorrectly merge span groups
654   return '\n<span dir=ltr>' + html + '</span>';
655 };
656
657
658 /**
659  * Enforce LTR on both end of the given text piece using unicode BiDi formatting
660  * characters LRE and PDF.
661  * @param {string} text The piece of text that need to be wrapped.
662  * @return {string} The wrapped string after process.
663  */
664 goog.i18n.bidi.enforceLtrInText = function(text) {
665   return goog.i18n.bidi.Format.LRE + text + goog.i18n.bidi.Format.PDF;
666 };
667
668
669 /**
670  * Regular expression to find dimensions such as "padding: .3 0.4ex 5px 6;"
671  * @type {RegExp}
672  * @private
673  */
674 goog.i18n.bidi.dimensionsRe_ =
675     /:\s*([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)\s+([.\d][.\w]*)/g;
676
677
678 /**
679  * Regular expression for left.
680  * @type {RegExp}
681  * @private
682  */
683 goog.i18n.bidi.leftRe_ = /left/gi;
684
685
686 /**
687  * Regular expression for right.
688  * @type {RegExp}
689  * @private
690  */
691 goog.i18n.bidi.rightRe_ = /right/gi;
692
693
694 /**
695  * Placeholder regular expression for swapping.
696  * @type {RegExp}
697  * @private
698  */
699 goog.i18n.bidi.tempRe_ = /%%%%/g;
700
701
702 /**
703  * Swap location parameters and 'left'/'right' in CSS specification. The
704  * processed string will be suited for RTL layout. Though this function can
705  * cover most cases, there are always exceptions. It is suggested to put
706  * those exceptions in separate group of CSS string.
707  * @param {string} cssStr CSS spefication string.
708  * @return {string} Processed CSS specification string.
709  */
710 goog.i18n.bidi.mirrorCSS = function(cssStr) {
711   return cssStr.
712       // reverse dimensions
713       replace(goog.i18n.bidi.dimensionsRe_, ':$1 $4 $3 $2').
714       replace(goog.i18n.bidi.leftRe_, '%%%%').          // swap left and right
715       replace(goog.i18n.bidi.rightRe_, goog.i18n.bidi.LEFT).
716       replace(goog.i18n.bidi.tempRe_, goog.i18n.bidi.RIGHT);
717 };
718
719
720 /**
721  * Regular expression for hebrew double quote substitution, finding quote
722  * directly after hebrew characters.
723  * @type {RegExp}
724  * @private
725  */
726 goog.i18n.bidi.doubleQuoteSubstituteRe_ = /([\u0591-\u05f2])"/g;
727
728
729 /**
730  * Regular expression for hebrew single quote substitution, finding quote
731  * directly after hebrew characters.
732  * @type {RegExp}
733  * @private
734  */
735 goog.i18n.bidi.singleQuoteSubstituteRe_ = /([\u0591-\u05f2])'/g;
736
737
738 /**
739  * Replace the double and single quote directly after a Hebrew character with
740  * GERESH and GERSHAYIM. In such case, most likely that's user intention.
741  * @param {string} str String that need to be processed.
742  * @return {string} Processed string with double/single quote replaced.
743  */
744 goog.i18n.bidi.normalizeHebrewQuote = function(str) {
745   return str.
746       replace(goog.i18n.bidi.doubleQuoteSubstituteRe_, '$1\u05f4').
747       replace(goog.i18n.bidi.singleQuoteSubstituteRe_, '$1\u05f3');
748 };
749
750
751 /**
752  * Regular expression to split a string into "words" for directionality
753  * estimation based on relative word counts.
754  * @type {RegExp}
755  * @private
756  */
757 goog.i18n.bidi.wordSeparatorRe_ = /\s+/;
758
759
760 /**
761  * Regular expression to check if a string contains any numerals. Used to
762  * differentiate between completely neutral strings and those containing
763  * numbers, which are weakly LTR.
764  * @type {RegExp}
765  * @private
766  */
767 goog.i18n.bidi.hasNumeralsRe_ = /\d/;
768
769
770 /**
771  * This constant controls threshold of RTL directionality.
772  * @type {number}
773  * @private
774  */
775 goog.i18n.bidi.rtlDetectionThreshold_ = 0.40;
776
777
778 /**
779  * Estimates the directionality of a string based on relative word counts.
780  * If the number of RTL words is above a certain percentage of the total number
781  * of strongly directional words, returns RTL.
782  * Otherwise, if any words are strongly or weakly LTR, returns LTR.
783  * Otherwise, returns UNKNOWN, which is used to mean "neutral".
784  * Numbers are counted as weakly LTR.
785  * @param {string} str The string to be checked.
786  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
787  *     Default: false.
788  * @return {goog.i18n.bidi.Dir} Estimated overall directionality of {@code str}.
789  */
790 goog.i18n.bidi.estimateDirection = function(str, opt_isHtml) {
791   var rtlCount = 0;
792   var totalCount = 0;
793   var hasWeaklyLtr = false;
794   var tokens = goog.i18n.bidi.stripHtmlIfNeeded_(str, opt_isHtml).
795       split(goog.i18n.bidi.wordSeparatorRe_);
796   for (var i = 0; i < tokens.length; i++) {
797     var token = tokens[i];
798     if (goog.i18n.bidi.startsWithRtl(token)) {
799       rtlCount++;
800       totalCount++;
801     } else if (goog.i18n.bidi.isRequiredLtrRe_.test(token)) {
802       hasWeaklyLtr = true;
803     } else if (goog.i18n.bidi.hasAnyLtr(token)) {
804       totalCount++;
805     } else if (goog.i18n.bidi.hasNumeralsRe_.test(token)) {
806       hasWeaklyLtr = true;
807     }
808   }
809
810   return totalCount == 0 ?
811       (hasWeaklyLtr ? goog.i18n.bidi.Dir.LTR : goog.i18n.bidi.Dir.NEUTRAL) :
812       (rtlCount / totalCount > goog.i18n.bidi.rtlDetectionThreshold_ ?
813           goog.i18n.bidi.Dir.RTL : goog.i18n.bidi.Dir.LTR);
814 };
815
816
817 /**
818  * Check the directionality of a piece of text, return true if the piece of
819  * text should be laid out in RTL direction.
820  * @param {string} str The piece of text that need to be detected.
821  * @param {boolean=} opt_isHtml Whether str is HTML / HTML-escaped.
822  *     Default: false.
823  * @return {boolean} Whether this piece of text should be laid out in RTL.
824  */
825 goog.i18n.bidi.detectRtlDirectionality = function(str, opt_isHtml) {
826   return goog.i18n.bidi.estimateDirection(str, opt_isHtml) ==
827       goog.i18n.bidi.Dir.RTL;
828 };
829
830
831 /**
832  * Sets text input element's directionality and text alignment based on a
833  * given directionality. Does nothing if the given directionality is unknown or
834  * neutral.
835  * @param {Element} element Input field element to set directionality to.
836  * @param {goog.i18n.bidi.Dir|number|boolean|null} dir Desired directionality,
837  *     given in one of the following formats:
838  *     1. A goog.i18n.bidi.Dir constant.
839  *     2. A number (positive = LRT, negative = RTL, 0 = neutral).
840  *     3. A boolean (true = RTL, false = LTR).
841  *     4. A null for unknown directionality.
842  */
843 goog.i18n.bidi.setElementDirAndAlign = function(element, dir) {
844   if (element) {
845     dir = goog.i18n.bidi.toDir(dir);
846     if (dir) {
847       element.style.textAlign =
848           dir == goog.i18n.bidi.Dir.RTL ?
849           goog.i18n.bidi.RIGHT : goog.i18n.bidi.LEFT;
850       element.dir = dir == goog.i18n.bidi.Dir.RTL ? 'rtl' : 'ltr';
851     }
852   }
853 };
854
855
856
857 /**
858  * Strings that have an (optional) known direction.
859  *
860  * Implementations of this interface are string-like objects that carry an
861  * attached direction, if known.
862  * @interface
863  */
864 goog.i18n.bidi.DirectionalString = function() {};
865
866
867 /**
868  * Interface marker of the DirectionalString interface.
869  *
870  * This property can be used to determine at runtime whether or not an object
871  * implements this interface.  All implementations of this interface set this
872  * property to {@code true}.
873  * @type {boolean}
874  */
875 goog.i18n.bidi.DirectionalString.prototype.
876     implementsGoogI18nBidiDirectionalString;
877
878
879 /**
880  * Retrieves this object's known direction (if any).
881  * @return {?goog.i18n.bidi.Dir} The known direction. Null if unknown.
882  */
883 goog.i18n.bidi.DirectionalString.prototype.getDirection;