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