Index: packages/intl/lib/src/intl/bidi_utils.dart |
diff --git a/packages/intl/lib/src/intl/bidi_utils.dart b/packages/intl/lib/src/intl/bidi_utils.dart |
index 378e0cc9789fe5850ffa4299b06c5e6f1285d680..438ef29080890809b5c097b6cef8f5e687a704d8 100644 |
--- a/packages/intl/lib/src/intl/bidi_utils.dart |
+++ b/packages/intl/lib/src/intl/bidi_utils.dart |
@@ -4,24 +4,22 @@ |
part of intl; |
-/** |
- * Bidi stands for Bi-directional text. |
- * According to http://en.wikipedia.org/wiki/Bi-directional_text: |
- * Bi-directional text is text containing text in both text directionalities, |
- * both right-to-left (RTL) and left-to-right (LTR). It generally involves text |
- * containing different types of alphabets, but may also refer to boustrophedon, |
- * which is changing text directionality in each row. |
- * |
- * This file provides some utility classes for determining directionality of |
- * text, switching CSS layout from LTR to RTL, and other normalizing utilities |
- * needed when switching between RTL and LTR formatting. |
- * |
- * It defines the TextDirection class which is used to represent directionality |
- * of text, |
- * In most cases, it is preferable to use bidi_formatter.dart, which provides |
- * bidi functionality in the given directional context, instead of using |
- * bidi_utils.dart directly. |
- */ |
+/// Bidi stands for Bi-directional text. According to |
+/// http://en.wikipedia.org/wiki/Bi-directional_text: Bi-directional text is |
+/// text containing text in both text directionalities, both right-to-left (RTL) |
+/// and left-to-right (LTR). It generally involves text containing different |
+/// types of alphabets, but may also refer to boustrophedon, which is changing |
+/// text directionality in each row. |
+/// |
+/// This file provides some utility classes for determining directionality of |
+/// text, switching CSS layout from LTR to RTL, and other normalizing utilities |
+/// needed when switching between RTL and LTR formatting. |
+/// |
+/// It defines the TextDirection class which is used to represent directionality |
+/// of text, |
+/// In most cases, it is preferable to use bidi_formatter.dart, which provides |
+/// bidi functionality in the given directional context, instead of using |
+/// bidi_utils.dart directly. |
class TextDirection { |
static const LTR = const TextDirection._('LTR', 'ltr'); |
static const RTL = const TextDirection._('RTL', 'rtl'); |
@@ -30,71 +28,60 @@ class TextDirection { |
// text falls back on the more common ltr direction. |
static const UNKNOWN = const TextDirection._('UNKNOWN', 'ltr'); |
- /** |
- * Textual representation of the directionality constant. One of |
- * 'LTR', 'RTL', or 'UNKNOWN'. |
- */ |
+ /// Textual representation of the directionality constant. One of |
+ /// 'LTR', 'RTL', or 'UNKNOWN'. |
final String value; |
- /** Textual representation of the directionality when used in span tag. */ |
+ /// Textual representation of the directionality when used in span tag. |
final String spanText; |
const TextDirection._(this.value, this.spanText); |
- /** |
- * Returns true if [otherDirection] is known to be different from this |
- * direction. |
- */ |
+ /// Returns true if [otherDirection] is known to be different from this |
+ /// direction. |
bool isDirectionChange(TextDirection otherDirection) => |
otherDirection != TextDirection.UNKNOWN && this != otherDirection; |
} |
-/** |
- * This provides utility methods for working with bidirectional text. All |
- * of the methods are static, and are organized into a class primarily to |
- * group them together for documentation and discoverability. |
- */ |
+/// This provides utility methods for working with bidirectional text. All |
+/// of the methods are static, and are organized into a class primarily to |
+/// group them together for documentation and discoverability. |
class Bidi { |
- |
- /** Unicode "Left-To-Right Embedding" (LRE) character. */ |
+ /// Unicode "Left-To-Right Embedding" (LRE) character. |
static const LRE = '\u202A'; |
- /** Unicode "Right-To-Left Embedding" (RLE) character. */ |
+ /// Unicode "Right-To-Left Embedding" (RLE) character. |
static const RLE = '\u202B'; |
- /** Unicode "Pop Directional Formatting" (PDF) character. */ |
+ /// Unicode "Pop Directional Formatting" (PDF) character. |
static const PDF = '\u202C'; |
- /** Unicode "Left-To-Right Mark" (LRM) character. */ |
+ /// Unicode "Left-To-Right Mark" (LRM) character. |
static const LRM = '\u200E'; |
- /** Unicode "Right-To-Left Mark" (RLM) character. */ |
+ /// Unicode "Right-To-Left Mark" (RLM) character. |
static const RLM = '\u200F'; |
- /** Constant to define the threshold of RTL directionality. */ |
+ /// Constant to define the threshold of RTL directionality. |
static num _RTL_DETECTION_THRESHOLD = 0.40; |
- /** |
- * Practical patterns to identify strong LTR and RTL characters, respectively. |
- * These patterns are not completely correct according to the Unicode |
- * standard. They are simplified for performance and small code size. |
- */ |
+ /// Practical patterns to identify strong LTR and RTL characters, |
+ /// respectively. These patterns are not completely correct according to the |
+ /// Unicode standard. They are simplified for performance and small code size. |
static const String _LTR_CHARS = |
r'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590' |
r'\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'; |
static const String _RTL_CHARS = r'\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; |
- /** |
- * Returns the input [text] with spaces instead of HTML tags or HTML escapes, |
- * which is helpful for text directionality estimation. |
- * Note: This function should not be used in other contexts. |
- * It does not deal well with many things: comments, script, |
- * elements, style elements, dir attribute,`>` in quoted attribute values, |
- * etc. But it does handle well enough the most common use cases. |
- * Since the worst that can happen as a result of these shortcomings is that |
- * the wrong directionality will be estimated, we have not invested in |
- * improving this. |
- */ |
+ /// Returns the input [text] with spaces instead of HTML tags or HTML escapes, |
+ /// which is helpful for text directionality estimation. |
+ /// Note: This function should not be used in other contexts. |
+ /// It does not deal well with many things: comments, script, |
+ /// elements, style elements, dir attribute,`>` in quoted attribute values, |
+ /// etc. But it does handle well enough the most common use cases. |
+ /// Since the worst that can happen as a result of these shortcomings is that |
+ /// the wrong directionality will be estimated, we have not invested in |
+ /// improving this. |
static String stripHtmlIfNeeded(String text) { |
// The regular expression is simplified for an HTML tag (opening or |
// closing) or an HTML escape. We might want to skip over such expressions |
@@ -102,135 +89,124 @@ class Bidi { |
return text.replaceAll(new RegExp(r'<[^>]*>|&[^;]+;'), ' '); |
} |
- /** |
- * Determines if the first character in [text] with strong directionality is |
- * LTR. If [isHtml] is true, the text is HTML or HTML-escaped. |
- */ |
+ /// Determines if the first character in [text] with strong directionality is |
+ /// LTR. If [isHtml] is true, the text is HTML or HTML-escaped. |
static bool startsWithLtr(String text, [isHtml = false]) { |
return new RegExp('^[^$_RTL_CHARS]*[$_LTR_CHARS]') |
.hasMatch(isHtml ? stripHtmlIfNeeded(text) : text); |
} |
- /** |
- * Determines if the first character in [text] with strong directionality is |
- * RTL. If [isHtml] is true, the text is HTML or HTML-escaped. |
- */ |
+ /// Determines if the first character in [text] with strong directionality is |
+ /// RTL. If [isHtml] is true, the text is HTML or HTML-escaped. |
static bool startsWithRtl(String text, [isHtml = false]) { |
return new RegExp('^[^$_LTR_CHARS]*[$_RTL_CHARS]') |
.hasMatch(isHtml ? stripHtmlIfNeeded(text) : text); |
} |
- /** |
- * Determines if the exit directionality (ie, the last strongly-directional |
- * character in [text] is LTR. If [isHtml] is true, the text is HTML or |
- * HTML-escaped. |
- */ |
+ /// Determines if the exit directionality (ie, the last strongly-directional |
+ /// character in [text] is LTR. If [isHtml] is true, the text is HTML or |
+ /// HTML-escaped. |
static bool endsWithLtr(String text, [isHtml = false]) { |
return new RegExp('[$_LTR_CHARS][^$_RTL_CHARS]*\$') |
.hasMatch(isHtml ? stripHtmlIfNeeded(text) : text); |
} |
- /** |
- * Determines if the exit directionality (ie, the last strongly-directional |
- * character in [text] is RTL. If [isHtml] is true, the text is HTML or |
- * HTML-escaped. |
- */ |
+ /// Determines if the exit directionality (ie, the last strongly-directional |
+ /// character in [text] is RTL. If [isHtml] is true, the text is HTML or |
+ /// HTML-escaped. |
static bool endsWithRtl(String text, [isHtml = false]) { |
return new RegExp('[$_RTL_CHARS][^$_LTR_CHARS]*\$') |
.hasMatch(isHtml ? stripHtmlIfNeeded(text) : text); |
} |
- /** |
- * Determines if the given [text] has any LTR characters in it. |
- * If [isHtml] is true, the text is HTML or HTML-escaped. |
- */ |
+ /// Determines if the given [text] has any LTR characters in it. |
+ /// If [isHtml] is true, the text is HTML or HTML-escaped. |
static bool hasAnyLtr(String text, [isHtml = false]) { |
return new RegExp(r'[' '$_LTR_CHARS' r']') |
.hasMatch(isHtml ? stripHtmlIfNeeded(text) : text); |
} |
- /** |
- * Determines if the given [text] has any RTL characters in it. |
- * If [isHtml] is true, the text is HTML or HTML-escaped. |
- */ |
+ /// Determines if the given [text] has any RTL characters in it. |
+ /// If [isHtml] is true, the text is HTML or HTML-escaped. |
static bool hasAnyRtl(String text, [isHtml = false]) { |
return new RegExp(r'[' '$_RTL_CHARS' r']') |
.hasMatch(isHtml ? stripHtmlIfNeeded(text) : text); |
} |
- /** |
- * Check if a BCP 47 / III [languageString] indicates an RTL language. |
- * |
- * i.e. either: |
- * - a language code explicitly specifying one of the right-to-left scripts, |
- * e.g. "az-Arab", or |
- * - a language code specifying one of the languages normally written in a |
- * right-to-left script, e.g. "fa" (Farsi), except ones explicitly |
- * specifying Latin or Cyrillic script (which are the usual LTR |
- * alternatives). |
- * |
- * The list of right-to-left scripts appears in the 100-199 range in |
- * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and |
- * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and |
- * Tifinagh, which also have significant modern usage. The rest (Syriac, |
- * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage |
- * and are not recognized. |
- * The languages usually written in a right-to-left script are taken as those |
- * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in |
- * http://www.iana.org/assignments/language-subtag-registry, |
- * as well as Sindhi (sd) and Uyghur (ug). |
- * The presence of other subtags of the language code, e.g. regions like EG |
- * (Egypt), is ignored. |
- */ |
- static bool isRtlLanguage(String languageString) { |
- return new RegExp(r'^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_]' |
- r'(Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))' |
- r'($|-|_)', caseSensitive: false).hasMatch(languageString); |
+ static final _rtlLocaleRegex = new RegExp( |
+ r'^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_]' |
+ r'(Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))' |
+ r'($|-|_)', |
+ caseSensitive: false); |
+ |
+ static String _lastLocaleCheckedForRtl; |
+ static bool _lastRtlCheck; |
+ |
+ /// Check if a BCP 47 / III [languageString] indicates an RTL language. |
+ /// |
+ /// i.e. either: |
+ /// - a language code explicitly specifying one of the right-to-left scripts, |
+ /// e.g. "az-Arab", or |
+ /// - a language code specifying one of the languages normally written in a |
+ /// right-to-left script, e.g. "fa" (Farsi), except ones explicitly |
+ /// specifying Latin or Cyrillic script (which are the usual LTR |
+ /// alternatives). |
+ /// |
+ /// The list of right-to-left scripts appears in the 100-199 range in |
+ /// http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and |
+ /// Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, |
+ /// and Tifinagh, which also have significant modern usage. The rest (Syriac, |
+ /// Samaritan, Mandaic, etc.) seem to have extremely limited or no modern |
+ /// usage and are not recognized. The languages usually written in a |
+ /// right-to-left script are taken as those with Suppress-Script: |
+ /// Hebr|Arab|Thaa|Nkoo|Tfng in |
+ /// http://www.iana.org/assignments/language-subtag-registry, as well as |
+ /// Sindhi (sd) and Uyghur (ug). The presence of other subtags of the |
+ /// language code, e.g. regions like EG (Egypt), is ignored. |
+ static bool isRtlLanguage([String languageString]) { |
+ var language = languageString ?? Intl.getCurrentLocale(); |
+ if (_lastLocaleCheckedForRtl != language) { |
+ _lastLocaleCheckedForRtl = language; |
+ _lastRtlCheck = _rtlLocaleRegex.hasMatch(language); |
+ } |
+ return _lastRtlCheck; |
} |
- /** |
- * Enforce the [html] snippet in RTL directionality regardless of overall |
- * context. If the html piece was enclosed by a tag, the direction will be |
- * applied to existing tag, otherwise a span tag will be added as wrapper. |
- * For this reason, if html snippet start with with tag, this tag must enclose |
- * the whole piece. If the tag already has a direction specified, this new one |
- * will override existing one in behavior (should work on Chrome, FF, and IE |
- * since this was ported directly from the Closure version). |
- */ |
+ /// Enforce the [html] snippet in RTL directionality regardless of overall |
+ /// context. If the html piece was enclosed by a tag, the direction will be |
+ /// applied to existing tag, otherwise a span tag will be added as wrapper. |
+ /// For this reason, if html snippet start with with tag, this tag must |
+ /// enclose the whole piece. If the tag already has a direction specified, |
+ /// this new one will override existing one in behavior (should work on |
+ /// Chrome, FF, and IE since this was ported directly from the Closure |
+ /// version). |
static String enforceRtlInHtml(String html) => |
_enforceInHtmlHelper(html, 'rtl'); |
- /** |
- * Enforce RTL on both end of the given [text] using unicode BiDi formatting |
- * characters RLE and PDF. |
- */ |
+ /// Enforce RTL on both end of the given [text] using unicode BiDi formatting |
+ /// characters RLE and PDF. |
static String enforceRtlInText(String text) => '$RLE$text$PDF'; |
- /** |
- * Enforce the [html] snippet in LTR directionality regardless of overall |
- * context. If the html piece was enclosed by a tag, the direction will be |
- * applied to existing tag, otherwise a span tag will be added as wrapper. |
- * For this reason, if html snippet start with with tag, this tag must enclose |
- * the whole piece. If the tag already has a direction specified, this new one |
- * will override existing one in behavior (tested on FF and IE). |
- */ |
+ /// Enforce the [html] snippet in LTR directionality regardless of overall |
+ /// context. If the html piece was enclosed by a tag, the direction will be |
+ /// applied to existing tag, otherwise a span tag will be added as wrapper. |
+ /// For this reason, if html snippet start with with tag, this tag must |
+ /// enclose the whole piece. If the tag already has a direction specified, |
+ /// this new one will override existing one in behavior (tested on FF and IE). |
static String enforceLtrInHtml(String html) => |
_enforceInHtmlHelper(html, 'ltr'); |
- /** |
- * Enforce LTR on both end of the given [text] using unicode BiDi formatting |
- * characters LRE and PDF. |
- */ |
+ /// Enforce LTR on both end of the given [text] using unicode BiDi formatting |
+ /// characters LRE and PDF. |
static String enforceLtrInText(String text) => '$LRE$text$PDF'; |
- /** |
- * Enforce the [html] snippet in the desired [direction] regardless of overall |
- * context. If the html piece was enclosed by a tag, the direction will be |
- * applied to existing tag, otherwise a span tag will be added as wrapper. |
- * For this reason, if html snippet start with with tag, this tag must enclose |
- * the whole piece. If the tag already has a direction specified, this new one |
- * will override existing one in behavior (tested on FF and IE). |
- */ |
+ /// Enforce the [html] snippet in the desired [direction] regardless of |
+ /// overall context. If the html piece was enclosed by a tag, the direction |
+ /// will be applied to existing tag, otherwise a span tag will be added as |
+ /// wrapper. For this reason, if html snippet start with with tag, this tag |
+ /// must enclose the whole piece. If the tag already has a direction |
+ /// specified, this new one will override existing one in behavior (tested on |
+ /// FF and IE). |
static String _enforceInHtmlHelper(String html, String direction) { |
if (html.startsWith('<')) { |
StringBuffer buffer = new StringBuffer(); |
@@ -248,12 +224,10 @@ class Bidi { |
return '\n<span dir=$direction>$html</span>'; |
} |
- /** |
- * Apply bracket guard to [str] using html span tag. This is to address the |
- * problem of messy bracket display that frequently happens in RTL layout. |
- * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL |
- * directionality, regardless of the estimated directionality. |
- */ |
+ /// Apply bracket guard to [str] using html span tag. This is to address the |
+ /// problem of messy bracket display that frequently happens in RTL layout. |
+ /// If [isRtlContext] is true, then we explicitly want to wrap in a span of |
+ /// RTL directionality, regardless of the estimated directionality. |
static String guardBracketInHtml(String str, [bool isRtlContext]) { |
var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; |
RegExp matchingBrackets = |
@@ -262,14 +236,12 @@ class Bidi { |
'<span dir=${useRtl? "rtl" : "ltr"}>', '</span>'); |
} |
- /** |
- * Apply bracket guard to [str] using LRM and RLM. This is to address the |
- * problem of messy bracket display that frequently happens in RTL layout. |
- * This version works for both plain text and html, but in some cases is not |
- * as good as guardBracketInHtml. |
- * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL |
- * directionality, regardless of the estimated directionality. |
- */ |
+ /// Apply bracket guard to [str] using LRM and RLM. This is to address the |
+ /// problem of messy bracket display that frequently happens in RTL layout. |
+ /// This version works for both plain text and html, but in some cases is not |
+ /// as good as guardBracketInHtml. If [isRtlContext] is true, then we |
+ /// explicitly want to wrap in a span of RTL directionality, regardless of the |
+ /// estimated directionality. |
static String guardBracketInText(String str, [bool isRtlContext]) { |
var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; |
var mark = useRtl ? RLM : LRM; |
@@ -277,15 +249,12 @@ class Bidi { |
new RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)'), mark, mark); |
} |
- /** |
- * (Mostly) reimplements the $& functionality of "replace" in JavaScript. |
- * Given a [str] and the [regexp] to match with, optionally supply a string to |
- * be inserted [before] the match and/or [after]. For example, |
- * `_guardBracketHelper('firetruck', new RegExp('truck'), 'hydrant', '!')` |
- * would return 'firehydrant!'. |
- */ |
- // TODO(efortuna): Get rid of this once this is implemented in Dart. |
- // See Issue 2979. |
+ /// (Mostly) reimplements the $& functionality of "replace" in JavaScript. |
+ /// Given a [str] and the [regexp] to match with, optionally supply a string |
+ /// to be inserted [before] the match and/or [after]. For example, |
+ /// `_guardBracketHelper('firetruck', new RegExp('truck'), 'hydrant', '!')` |
+ /// would return 'firehydrant!'. // TODO(efortuna): Get rid of this once this |
+ /// is implemented in Dart. // See Issue 2979. |
static String _guardBracketHelper(String str, RegExp regexp, |
[String before, String after]) { |
var buffer = new StringBuffer(); |
@@ -301,18 +270,16 @@ class Bidi { |
return (buffer..write(str.substring(startIndex))).toString(); |
} |
- /** |
- * Estimates the directionality of [text] using the best known |
- * general-purpose method (using relative word counts). A |
- * TextDirection.UNKNOWN return value indicates completely neutral input. |
- * [isHtml] is true if [text] HTML or HTML-escaped. |
- * |
- * If the number of RTL words is above a certain percentage of the total |
- * number of strongly directional words, returns RTL. |
- * Otherwise, if any words are strongly or weakly LTR, returns LTR. |
- * Otherwise, returns UNKNOWN, which is used to mean `neutral`. |
- * Numbers and URLs are counted as weakly LTR. |
- */ |
+ /// Estimates the directionality of [text] using the best known |
+ /// general-purpose method (using relative word counts). A |
+ /// TextDirection.UNKNOWN return value indicates completely neutral input. |
+ /// [isHtml] is true if [text] HTML or HTML-escaped. |
+ /// |
+ /// If the number of RTL words is above a certain percentage of the total |
+ /// number of strongly directional words, returns RTL. |
+ /// Otherwise, if any words are strongly or weakly LTR, returns LTR. |
+ /// Otherwise, returns UNKNOWN, which is used to mean `neutral`. |
+ /// Numbers and URLs are counted as weakly LTR. |
static TextDirection estimateDirectionOfText(String text, |
{bool isHtml: false}) { |
text = isHtml ? stripHtmlIfNeeded(text) : text; |
@@ -346,10 +313,8 @@ class Bidi { |
} |
} |
- /** |
- * Replace the double and single quote directly after a Hebrew character in |
- * [str] with GERESH and GERSHAYIM. This is most likely the user's intention. |
- */ |
+ /// Replace the double and single quote directly after a Hebrew character in |
+ /// [str] with GERESH and GERSHAYIM. This is most likely the user's intention. |
static String normalizeHebrewQuote(String str) { |
StringBuffer buf = new StringBuffer(); |
if (str.length > 0) { |
@@ -371,11 +336,9 @@ class Bidi { |
return buf.toString(); |
} |
- /** |
- * Check the estimated directionality of [str], return true if the piece of |
- * text should be laid out in RTL direction. If [isHtml] is true, the string |
- * is HTML or HTML-escaped. |
- */ |
+ /// Check the estimated directionality of [str], return true if the piece of |
+ /// text should be laid out in RTL direction. If [isHtml] is true, the string |
+ /// is HTML or HTML-escaped. |
static bool detectRtlDirectionality(String str, {bool isHtml: false}) => |
estimateDirectionOfText(str, isHtml: isHtml) == TextDirection.RTL; |
} |