Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(187)

Side by Side Diff: pkg/intl/lib/bidi_utils.dart

Issue 11032035: Move Bidi functions into methods to make the docs look better (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « pkg/intl/lib/bidi_formatter.dart ('k') | pkg/intl/lib/intl.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 /** 5 /**
6 * Bidi stands for Bi-directional text. 6 * Bidi stands for Bi-directional text.
7 * According to http://en.wikipedia.org/wiki/Bi-directional_text: 7 * According to http://en.wikipedia.org/wiki/Bi-directional_text:
8 * Bi-directional text is text containing text in both text directionalities, 8 * Bi-directional text is text containing text in both text directionalities,
9 * both right-to-left (RTL) and left-to-right (LTR). It generally involves text 9 * both right-to-left (RTL) and left-to-right (LTR). It generally involves text
10 * containing different types of alphabets, but may also refer to boustrophedon, 10 * containing different types of alphabets, but may also refer to boustrophedon,
(...skipping 10 matching lines...) Expand all
21 * bidi_utils.dart directly. 21 * bidi_utils.dart directly.
22 */ 22 */
23 class TextDirection { 23 class TextDirection {
24 static const LTR = const TextDirection._('LTR', 'ltr'); 24 static const LTR = const TextDirection._('LTR', 'ltr');
25 static const RTL = const TextDirection._('RTL', 'rtl'); 25 static const RTL = const TextDirection._('RTL', 'rtl');
26 // If the directionality of the text cannot be determined and we are not using 26 // If the directionality of the text cannot be determined and we are not using
27 // the context direction (or if the context direction is unknown), then the 27 // the context direction (or if the context direction is unknown), then the
28 // text falls back on the more common ltr direction. 28 // text falls back on the more common ltr direction.
29 static const UNKNOWN = const TextDirection._('UNKNOWN', 'ltr'); 29 static const UNKNOWN = const TextDirection._('UNKNOWN', 'ltr');
30 30
31 /** 31 /**
32 * Textual representation of the directionality constant. One of 32 * Textual representation of the directionality constant. One of
33 * 'LTR', 'RTL', or 'UNKNOWN'. 33 * 'LTR', 'RTL', or 'UNKNOWN'.
34 */ 34 */
35 final String value; 35 final String value;
36 36
37 /** Textual representation of the directionality when used in span tag. */ 37 /** Textual representation of the directionality when used in span tag. */
38 final String spanText; 38 final String spanText;
39 39
40 const TextDirection._(this.value, this.spanText); 40 const TextDirection._(this.value, this.spanText);
41 41
42 /** 42 /**
43 * Returns true if [otherDirection] is known to be different from this 43 * Returns true if [otherDirection] is known to be different from this
44 * direction. 44 * direction.
45 */ 45 */
46 bool isDirectionChange(TextDirection otherDirection) { 46 bool isDirectionChange(TextDirection otherDirection) {
47 return otherDirection != TextDirection.UNKNOWN && this != otherDirection; 47 return otherDirection != TextDirection.UNKNOWN && this != otherDirection;
48 } 48 }
49 } 49 }
50 50
51 /** Unicode "Left-To-Right Embedding" (LRE) character. */
52 const LRE = '\u202A';
53
54 /** Unicode "Right-To-Left Embedding" (RLE) character. */
55 const RLE = '\u202B';
56
57 /** Unicode "Pop Directional Formatting" (PDF) character. */
58 const PDF = '\u202C';
59
60 /** Unicode "Left-To-Right Mark" (LRM) character. */
61 const LRM = '\u200E';
62
63 /** Unicode "Right-To-Left Mark" (RLM) character. */
64 const RLM = '\u200F';
65
66 /** Constant to define the threshold of RTL directionality. */
67 num _RTL_DETECTION_THRESHOLD = 0.40;
68
69 /** 51 /**
70 * Practical patterns to identify strong LTR and RTL characters, respectively. 52 * This provides utility methods for working with bidirectional text. All
71 * These patterns are not completely correct according to the Unicode 53 * of the methods are static, and are organized into a class primarily to
72 * standard. They are simplified for performance and small code size. 54 * group them together for documentation and discoverability.
73 */ 55 */
74 const String _LTR_CHARS = 56 class Bidi {
75 r'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590' 57
76 r'\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF'; 58 /** Unicode "Left-To-Right Embedding" (LRE) character. */
77 const String _RTL_CHARS = r'\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC'; 59 static const LRE = '\u202A';
78 60
79 /** 61 /** Unicode "Right-To-Left Embedding" (RLE) character. */
80 * Returns the input [text] with spaces instead of HTML tags or HTML escapes, 62 static const RLE = '\u202B';
81 * which is helpful for text directionality estimation. 63
82 * Note: This function should not be used in other contexts. 64 /** Unicode "Pop Directional Formatting" (PDF) character. */
83 * It does not deal well with many things: comments, script, 65 static const PDF = '\u202C';
84 * elements, style elements, dir attribute,`>` in quoted attribute values, 66
85 * etc. But it does handle well enough the most common use cases. 67 /** Unicode "Left-To-Right Mark" (LRM) character. */
86 * Since the worst that can happen as a result of these shortcomings is that 68 static const LRM = '\u200E';
87 * the wrong directionality will be estimated, we have not invested in 69
88 * improving this. 70 /** Unicode "Right-To-Left Mark" (RLM) character. */
89 */ 71 static const RLM = '\u200F';
90 String stripHtmlIfNeeded(String text) { 72
91 // The regular expression is simplified for an HTML tag (opening or 73 /** Constant to define the threshold of RTL directionality. */
92 // closing) or an HTML escape. We might want to skip over such expressions 74 static num _RTL_DETECTION_THRESHOLD = 0.40;
93 // when estimating the text directionality. 75
94 return text.replaceAll(const RegExp(r'<[^>]*>|&[^;]+;'), ' '); 76 /**
95 } 77 * Practical patterns to identify strong LTR and RTL characters, respectively.
96 78 * These patterns are not completely correct according to the Unicode
97 /** 79 * standard. They are simplified for performance and small code size.
98 * Determines if the first character in [text] with strong directionality is 80 */
99 * LTR. If [isHtml] is true, the text is HTML or HTML-escaped. 81 static const String _LTR_CHARS =
100 */ 82 r'A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02B8\u0300-\u0590'
101 bool startsWithLtr(String text, [isHtml=false]) { 83 r'\u0800-\u1FFF\u2C00-\uFB1C\uFDFE-\uFE6F\uFEFD-\uFFFF';
102 return const RegExp('^[^$_RTL_CHARS]*[$_LTR_CHARS]').hasMatch( 84 static const String _RTL_CHARS = r'\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC';
103 isHtml? stripHtmlIfNeeded(text) : text); 85
104 } 86 /**
105 87 * Returns the input [text] with spaces instead of HTML tags or HTML escapes,
106 /** 88 * which is helpful for text directionality estimation.
107 * Determines if the first character in [text] with strong directionality is 89 * Note: This function should not be used in other contexts.
108 * RTL. If [isHtml] is true, the text is HTML or HTML-escaped. 90 * It does not deal well with many things: comments, script,
109 */ 91 * elements, style elements, dir attribute,`>` in quoted attribute values,
110 bool startsWithRtl(String text, [isHtml=false]) { 92 * etc. But it does handle well enough the most common use cases.
111 return const RegExp('^[^$_LTR_CHARS]*[$_RTL_CHARS]').hasMatch( 93 * Since the worst that can happen as a result of these shortcomings is that
112 isHtml? stripHtmlIfNeeded(text) : text); 94 * the wrong directionality will be estimated, we have not invested in
113 } 95 * improving this.
114 96 */
115 /** 97 static String stripHtmlIfNeeded(String text) {
116 * Determines if the exit directionality (ie, the last strongly-directional 98 // The regular expression is simplified for an HTML tag (opening or
117 * character in [text] is LTR. If [isHtml] is true, the text is HTML or 99 // closing) or an HTML escape. We might want to skip over such expressions
118 * HTML-escaped. 100 // when estimating the text directionality.
119 */ 101 return text.replaceAll(const RegExp(r'<[^>]*>|&[^;]+;'), ' ');
120 bool endsWithLtr(String text, [isHtml=false]) { 102 }
121 return const RegExp('[$_LTR_CHARS][^$_RTL_CHARS]*\$').hasMatch( 103
122 isHtml? stripHtmlIfNeeded(text) : text); 104 /**
123 } 105 * Determines if the first character in [text] with strong directionality is
124 106 * LTR. If [isHtml] is true, the text is HTML or HTML-escaped.
125 /** 107 */
126 * Determines if the exit directionality (ie, the last strongly-directional 108 static bool startsWithLtr(String text, [isHtml=false]) {
127 * character in [text] is RTL. If [isHtml] is true, the text is HTML or 109 return const RegExp('^[^$_RTL_CHARS]*[$_LTR_CHARS]').hasMatch(
128 * HTML-escaped. 110 isHtml? stripHtmlIfNeeded(text) : text);
129 */ 111 }
130 bool endsWithRtl(String text, [isHtml=false]) { 112
131 return const RegExp('[$_RTL_CHARS][^$_LTR_CHARS]*\$').hasMatch( 113 /**
132 isHtml? stripHtmlIfNeeded(text) : text); 114 * Determines if the first character in [text] with strong directionality is
133 } 115 * RTL. If [isHtml] is true, the text is HTML or HTML-escaped.
134 116 */
135 /** 117 static bool startsWithRtl(String text, [isHtml=false]) {
136 * Determines if the given [text] has any LTR characters in it. 118 return const RegExp('^[^$_LTR_CHARS]*[$_RTL_CHARS]').hasMatch(
137 * If [isHtml] is true, the text is HTML or HTML-escaped. 119 isHtml? stripHtmlIfNeeded(text) : text);
138 */ 120 }
139 bool hasAnyLtr(String text, [isHtml=false]) { 121
140 return const RegExp(r'[' '$_LTR_CHARS' r']').hasMatch( 122 /**
141 isHtml? stripHtmlIfNeeded(text) : text); 123 * Determines if the exit directionality (ie, the last strongly-directional
142 } 124 * character in [text] is LTR. If [isHtml] is true, the text is HTML or
143 125 * HTML-escaped.
144 /** 126 */
145 * Determines if the given [text] has any RTL characters in it. 127 static bool endsWithLtr(String text, [isHtml=false]) {
146 * If [isHtml] is true, the text is HTML or HTML-escaped. 128 return const RegExp('[$_LTR_CHARS][^$_RTL_CHARS]*\$').hasMatch(
147 */ 129 isHtml? stripHtmlIfNeeded(text) : text);
148 bool hasAnyRtl(String text, [isHtml=false]) { 130 }
149 return const RegExp(r'[' '$_RTL_CHARS' r']').hasMatch( 131
150 isHtml? stripHtmlIfNeeded(text) : text); 132 /**
151 } 133 * Determines if the exit directionality (ie, the last strongly-directional
152 134 * character in [text] is RTL. If [isHtml] is true, the text is HTML or
153 /** 135 * HTML-escaped.
154 * Check if a BCP 47 / III [languageString] indicates an RTL language. 136 */
155 * 137 static bool endsWithRtl(String text, [isHtml=false]) {
156 * i.e. either: 138 return const RegExp('[$_RTL_CHARS][^$_LTR_CHARS]*\$').hasMatch(
157 * - a language code explicitly specifying one of the right-to-left scripts, 139 isHtml? stripHtmlIfNeeded(text) : text);
158 * e.g. "az-Arab", or 140 }
159 * - a language code specifying one of the languages normally written in a 141
160 * right-to-left script, e.g. "fa" (Farsi), except ones explicitly 142 /**
161 * specifying Latin or Cyrillic script (which are the usual LTR 143 * Determines if the given [text] has any LTR characters in it.
162 * alternatives). 144 * If [isHtml] is true, the text is HTML or HTML-escaped.
163 * 145 */
164 * The list of right-to-left scripts appears in the 100-199 range in 146 static bool hasAnyLtr(String text, [isHtml=false]) {
165 * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and 147 return const RegExp(r'[' '$_LTR_CHARS' r']').hasMatch(
166 * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and 148 isHtml? stripHtmlIfNeeded(text) : text);
167 * Tifinagh, which also have significant modern usage. The rest (Syriac, 149 }
168 * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage 150
169 * and are not recognized. 151 /**
170 * The languages usually written in a right-to-left script are taken as those 152 * Determines if the given [text] has any RTL characters in it.
171 * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in 153 * If [isHtml] is true, the text is HTML or HTML-escaped.
172 * http://www.iana.org/assignments/language-subtag-registry, 154 */
173 * as well as Sindhi (sd) and Uyghur (ug). 155 static bool hasAnyRtl(String text, [isHtml=false]) {
174 * The presence of other subtags of the language code, e.g. regions like EG 156 return const RegExp(r'[' '$_RTL_CHARS' r']').hasMatch(
175 * (Egypt), is ignored. 157 isHtml? stripHtmlIfNeeded(text) : text);
176 */ 158 }
177 bool isRtlLanguage(String languageString) { 159
178 return const RegExp(r'^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_]' 160 /**
179 r'(Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))' 161 * Check if a BCP 47 / III [languageString] indicates an RTL language.
180 r'($|-|_)', ignoreCase : true).hasMatch(languageString); 162 *
181 } 163 * i.e. either:
182 164 * - a language code explicitly specifying one of the right-to-left scripts,
183 /** 165 * e.g. "az-Arab", or
184 * Enforce the [html] snippet in RTL directionality regardless of overall 166 * - a language code specifying one of the languages normally written in a
185 * context. If the html piece was enclosed by a tag, the direction will be 167 * right-to-left script, e.g. "fa" (Farsi), except ones explicitly
186 * applied to existing tag, otherwise a span tag will be added as wrapper. 168 * specifying Latin or Cyrillic script (which are the usual LTR
187 * For this reason, if html snippet start with with tag, this tag must enclose 169 * alternatives).
188 * the whole piece. If the tag already has a direction specified, this new one 170 *
189 * will override existing one in behavior (should work on Chrome, FF, and IE 171 * The list of right-to-left scripts appears in the 100-199 range in
190 * since this was ported directly from the Closure version). 172 * http://www.unicode.org/iso15924/iso15924-num.html, of which Arabic and
191 */ 173 * Hebrew are by far the most widely used. We also recognize Thaana, N'Ko, and
192 String enforceRtlInHtml(String html) { 174 * Tifinagh, which also have significant modern usage. The rest (Syriac,
193 return _enforceInHtmlHelper(html, 'rtl'); 175 * Samaritan, Mandaic, etc.) seem to have extremely limited or no modern usage
194 } 176 * and are not recognized.
195 177 * The languages usually written in a right-to-left script are taken as those
196 /** 178 * with Suppress-Script: Hebr|Arab|Thaa|Nkoo|Tfng in
197 * Enforce RTL on both end of the given [text] using unicode BiDi formatting 179 * http://www.iana.org/assignments/language-subtag-registry,
198 * characters RLE and PDF. 180 * as well as Sindhi (sd) and Uyghur (ug).
199 */ 181 * The presence of other subtags of the language code, e.g. regions like EG
200 String enforceRtlInText(String text) { 182 * (Egypt), is ignored.
201 return '$RLE$text$PDF'; 183 */
202 } 184 static bool isRtlLanguage(String languageString) {
203 185 return const RegExp(r'^(ar|dv|he|iw|fa|nqo|ps|sd|ug|ur|yi|.*[-_]'
204 /** 186 r'(Arab|Hebr|Thaa|Nkoo|Tfng))(?!.*[-_](Latn|Cyrl)($|-|_))'
205 * Enforce the [html] snippet in LTR directionality regardless of overall 187 r'($|-|_)', ignoreCase : true).hasMatch(languageString);
206 * context. If the html piece was enclosed by a tag, the direction will be 188 }
207 * applied to existing tag, otherwise a span tag will be added as wrapper. 189
208 * For this reason, if html snippet start with with tag, this tag must enclose 190 /**
209 * the whole piece. If the tag already has a direction specified, this new one 191 * Enforce the [html] snippet in RTL directionality regardless of overall
210 * will override existing one in behavior (tested on FF and IE). 192 * context. If the html piece was enclosed by a tag, the direction will be
211 */ 193 * applied to existing tag, otherwise a span tag will be added as wrapper.
212 String enforceLtrInHtml(String html) { 194 * For this reason, if html snippet start with with tag, this tag must enclose
213 return _enforceInHtmlHelper(html, 'ltr'); 195 * the whole piece. If the tag already has a direction specified, this new one
214 } 196 * will override existing one in behavior (should work on Chrome, FF, and IE
215 197 * since this was ported directly from the Closure version).
216 /** 198 */
217 * Enforce LTR on both end of the given [text] using unicode BiDi formatting 199 static String enforceRtlInHtml(String html) {
218 * characters LRE and PDF. 200 return _enforceInHtmlHelper(html, 'rtl');
219 */ 201 }
220 String enforceLtrInText(String text) { 202
221 return '$LRE$text$PDF'; 203 /**
222 } 204 * Enforce RTL on both end of the given [text] using unicode BiDi formatting
223 205 * characters RLE and PDF.
224 /** 206 */
225 * Enforce the [html] snippet in the desired [direction] regardless of overall 207 static String enforceRtlInText(String text) {
226 * context. If the html piece was enclosed by a tag, the direction will be 208 return '$RLE$text$PDF';
227 * applied to existing tag, otherwise a span tag will be added as wrapper. 209 }
228 * For this reason, if html snippet start with with tag, this tag must enclose 210
229 * the whole piece. If the tag already has a direction specified, this new one 211 /**
230 * will override existing one in behavior (tested on FF and IE). 212 * Enforce the [html] snippet in LTR directionality regardless of overall
231 */ 213 * context. If the html piece was enclosed by a tag, the direction will be
232 String _enforceInHtmlHelper(String html, String direction) { 214 * applied to existing tag, otherwise a span tag will be added as wrapper.
233 if (html.startsWith('<')) { 215 * For this reason, if html snippet start with with tag, this tag must enclose
216 * the whole piece. If the tag already has a direction specified, this new one
217 * will override existing one in behavior (tested on FF and IE).
218 */
219 static String enforceLtrInHtml(String html) {
220 return _enforceInHtmlHelper(html, 'ltr');
221 }
222
223 /**
224 * Enforce LTR on both end of the given [text] using unicode BiDi formatting
225 * characters LRE and PDF.
226 */
227 static String enforceLtrInText(String text) {
228 return '$LRE$text$PDF';
229 }
230
231 /**
232 * Enforce the [html] snippet in the desired [direction] regardless of overall
233 * context. If the html piece was enclosed by a tag, the direction will be
234 * applied to existing tag, otherwise a span tag will be added as wrapper.
235 * For this reason, if html snippet start with with tag, this tag must enclose
236 * the whole piece. If the tag already has a direction specified, this new one
237 * will override existing one in behavior (tested on FF and IE).
238 */
239 static String _enforceInHtmlHelper(String html, String direction) {
240 if (html.startsWith('<')) {
241 StringBuffer buffer = new StringBuffer();
242 var startIndex = 0;
243 Match match = const RegExp('<\\w+').firstMatch(html);
244 if (match != null) {
245 buffer.add(html.substring(
246 startIndex, match.end())).add(' dir=$direction');
247 startIndex = match.end();
248 }
249 return buffer.add(html.substring(startIndex)).toString();
250 }
251 // '\n' is important for FF so that it won't incorrectly merge span groups.
252 return '\n<span dir=$direction>$html</span>';
253 }
254
255 /**
256 * Apply bracket guard to [str] using html span tag. This is to address the
257 * problem of messy bracket display that frequently happens in RTL layout.
258 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL
259 * directionality, regardless of the estimated directionality.
260 */
261 static String guardBracketInHtml(String str, [bool isRtlContext]) {
262 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext;
263 RegExp matchingBrackets =
264 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(&lt;.*?(&gt;)+)');
265 return _guardBracketHelper(str, matchingBrackets,
266 '<span dir=${useRtl? "rtl" : "ltr"}>', '</span>');
267 }
268
269 /**
270 * Apply bracket guard to [str] using LRM and RLM. This is to address the
271 * problem of messy bracket display that frequently happens in RTL layout.
272 * This version works for both plain text and html, but in some cases is not
273 * as good as guardBracketInHtml.
274 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL
275 * directionality, regardless of the estimated directionality.
276 */
277 static String guardBracketInText(String str, [bool isRtlContext]) {
278 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext;
279 var mark = useRtl ? RLM : LRM;
280 return _guardBracketHelper(str,
281 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)'), mark, mark);
282 }
283
284 /**
285 * (Mostly) reimplements the $& functionality of "replace" in JavaScript.
286 * Given a [str] and the [regexp] to match with, optionally supply a string to
287 * be inserted [before] the match and/or [after]. For example,
288 * `_guardBracketHelper('firetruck', const RegExp('truck'), 'hydrant', '!')`
289 * would return 'firehydrant!'.
290 */
291 // TODO(efortuna): Get rid of this once this is implemented in Dart.
292 // See Issue 2979.
293 static String _guardBracketHelper(String str, RegExp regexp, [String before,
294 String after]) {
234 StringBuffer buffer = new StringBuffer(); 295 StringBuffer buffer = new StringBuffer();
235 var startIndex = 0; 296 var startIndex = 0;
236 Match match = const RegExp('<\\w+').firstMatch(html); 297 Iterable matches = regexp.allMatches(str);
237 if (match != null) { 298 for (Match match in matches) {
238 buffer.add(html.substring( 299 buffer.add(str.substring(startIndex, match.start())).add(before);
239 startIndex, match.end())).add(' dir=$direction'); 300 buffer.add(str.substring(match.start(), match.end())).add(after);
240 startIndex = match.end(); 301 startIndex = match.end();
241 } 302 }
242 return buffer.add(html.substring(startIndex)).toString(); 303 return buffer.add(str.substring(startIndex)).toString();
243 } 304 }
244 // '\n' is important for FF so that it won't incorrectly merge span groups. 305
245 return '\n<span dir=$direction>$html</span>'; 306 /**
246 } 307 * Estimates the directionality of [text] using the best known
247 308 * general-purpose method (using relative word counts). A
248 /** 309 * TextDirection.UNKNOWN return value indicates completely neutral input.
249 * Apply bracket guard to [str] using html span tag. This is to address the 310 * [isHtml] is true if [text] HTML or HTML-escaped.
250 * problem of messy bracket display that frequently happens in RTL layout. 311 *
251 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL 312 * If the number of RTL words is above a certain percentage of the total
252 * directionality, regardless of the estimated directionality. 313 * number of strongly directional words, returns RTL.
253 */ 314 * Otherwise, if any words are strongly or weakly LTR, returns LTR.
254 String guardBracketInHtml(String str, [bool isRtlContext]) { 315 * Otherwise, returns UNKNOWN, which is used to mean `neutral`.
255 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; 316 * Numbers and URLs are counted as weakly LTR.
256 RegExp matchingBrackets = 317 */
257 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(&lt;.*?(&gt;)+)'); 318 static TextDirection estimateDirectionOfText(String text, [bool isHtml=false]) {
258 return _guardBracketHelper(str, matchingBrackets, 319 text = isHtml? stripHtmlIfNeeded(text) : text;
259 '<span dir=${useRtl? "rtl" : "ltr"}>', '</span>'); 320 var rtlCount = 0;
260 } 321 var total = 0;
261 322 var hasWeaklyLtr = false;
262 /** 323 // Split a string into 'words' for directionality estimation based on
263 * Apply bracket guard to [str] using LRM and RLM. This is to address the 324 // relative word counts.
264 * problem of messy bracket display that frequently happens in RTL layout. 325 for (String token in text.split(const RegExp(r'\s+'))) {
265 * This version works for both plain text and html, but in some cases is not 326 if (startsWithRtl(token)) {
266 * as good as guardBracketInHtml. 327 rtlCount++;
267 * If [isRtlContext] is true, then we explicitly want to wrap in a span of RTL 328 total++;
268 * directionality, regardless of the estimated directionality. 329 } else if (const RegExp(r'^http://').hasMatch(token)) {
269 */ 330 // Checked if token looks like something that must always be LTR even in
270 String guardBracketInText(String str, [bool isRtlContext]) { 331 // RTL text, such as a URL.
271 var useRtl = isRtlContext == null ? hasAnyRtl(str) : isRtlContext; 332 hasWeaklyLtr = true;
272 var mark = useRtl ? RLM : LRM; 333 } else if (hasAnyLtr(token)) {
273 return _guardBracketHelper(str, 334 total++;
274 const RegExp(r'(\(.*?\)+)|(\[.*?\]+)|(\{.*?\}+)|(<.*?>+)'), mark, mark); 335 } else if (const RegExp(r'\d').hasMatch(token)) {
275 } 336 // Checked if token contains any numerals.
276 337 hasWeaklyLtr = true;
277 /** 338 }
278 * (Mostly) reimplements the $& functionality of "replace" in JavaScript. 339 }
279 * Given a [str] and the [regexp] to match with, optionally supply a string to 340
280 * be inserted [before] the match and/or [after]. For example, 341 if (total == 0) {
281 * `_guardBracketHelper('firetruck', const RegExp('truck'), 'hydrant', '!')` 342 return hasWeaklyLtr ? TextDirection.LTR : TextDirection.UNKNOWN;
282 * would return 'firehydrant!'. 343 } else if (rtlCount > _RTL_DETECTION_THRESHOLD * total) {
283 */ 344 return TextDirection.RTL;
284 // TODO(efortuna): Get rid of this once this is implemented in Dart.
285 // See Issue 2979.
286 String _guardBracketHelper(String str, RegExp regexp, [String before,
287 String after]) {
288 StringBuffer buffer = new StringBuffer();
289 var startIndex = 0;
290 Iterable matches = regexp.allMatches(str);
291 for (Match match in matches) {
292 buffer.add(str.substring(startIndex, match.start())).add(before);
293 buffer.add(str.substring(match.start(), match.end())).add(after);
294 startIndex = match.end();
295 }
296 return buffer.add(str.substring(startIndex)).toString();
297 }
298
299 /**
300 * Estimates the directionality of [text] using the best known
301 * general-purpose method (using relative word counts). A
302 * TextDirection.UNKNOWN return value indicates completely neutral input.
303 * [isHtml] is true if [text] HTML or HTML-escaped.
304 *
305 * If the number of RTL words is above a certain percentage of the total
306 * number of strongly directional words, returns RTL.
307 * Otherwise, if any words are strongly or weakly LTR, returns LTR.
308 * Otherwise, returns UNKNOWN, which is used to mean `neutral`.
309 * Numbers and URLs are counted as weakly LTR.
310 */
311 TextDirection estimateDirectionOfText(String text, [bool isHtml=false]) {
312 text = isHtml? stripHtmlIfNeeded(text) : text;
313 var rtlCount = 0;
314 var total = 0;
315 var hasWeaklyLtr = false;
316 // Split a string into 'words' for directionality estimation based on
317 // relative word counts.
318 for (String token in text.split(const RegExp(r'\s+'))) {
319 if (startsWithRtl(token)) {
320 rtlCount++;
321 total++;
322 } else if (const RegExp(r'^http://').hasMatch(token)) {
323 // Checked if token looks like something that must always be LTR even in
324 // RTL text, such as a URL.
325 hasWeaklyLtr = true;
326 } else if (hasAnyLtr(token)) {
327 total++;
328 } else if (const RegExp(r'\d').hasMatch(token)) {
329 // Checked if token contains any numerals.
330 hasWeaklyLtr = true;
331 }
332 }
333
334 if (total == 0) {
335 return hasWeaklyLtr ? TextDirection.LTR : TextDirection.UNKNOWN;
336 } else if (rtlCount > _RTL_DETECTION_THRESHOLD * total) {
337 return TextDirection.RTL;
338 } else {
339 return TextDirection.LTR;
340 }
341 }
342
343 /**
344 * Find the first index in [str] of the first closing parenthesis that does
345 * not match an opening parenthesis.
346 */
347 int _unmatchedParenIndex(String str) {
348 int sum = 0;
349 int index = 0;
350 while (sum >= 0 || index > str.length) {
351 int char = str.charCodeAt(index);
352 if (char == '('.charCodeAt(0)) sum++;
353 else if (char == ')'.charCodeAt(0)) sum--;
354 index++;
355 }
356 return index;
357 }
358
359 /**
360 * Replace the double and single quote directly after a Hebrew character in
361 * [str] with GERESH and GERSHAYIM. This is most likely the user's intention.
362 */
363 String normalizeHebrewQuote(String str) {
364 StringBuffer buf = new StringBuffer();
365 if (str.length > 0) {
366 buf.add(str.substring(0, 1));
367 }
368 // Start at 1 because we're looking for the patterns [\u0591-\u05f2])" or
369 // [\u0591-\u05f2]'.
370 for (int i = 1; i < str.length; i++) {
371 if (str.substring(i, i+1) == '"'
372 && const RegExp('[\u0591-\u05f2]').hasMatch(str.substring(i-1, i))) {
373 buf.add('\u05f4');
374 } else if (str.substring(i, i+1) == "'"
375 && const RegExp('[\u0591-\u05f2]').hasMatch(str.substring(i-1, i))) {
376 buf.add('\u05f3');
377 } else { 345 } else {
378 buf.add(str.substring(i, i+1)); 346 return TextDirection.LTR;
379 } 347 }
380 } 348 }
381 return buf.toString(); 349
382 } 350 /**
383 351 * Find the first index in [str] of the first closing parenthesis that does
384 /** 352 * not match an opening parenthesis.
385 * Check the estimated directionality of [str], return true if the piece of 353 */
386 * text should be laid out in RTL direction. If [isHtml] is true, the string 354 static int _unmatchedParenIndex(String str) {
387 * is HTML or HTML-escaped. 355 int sum = 0;
388 */ 356 int index = 0;
389 bool detectRtlDirectionality(String str, [bool isHtml]) { 357 while (sum >= 0 || index > str.length) {
390 return estimateDirectionOfText(str, isHtml) == TextDirection.RTL; 358 int char = str.charCodeAt(index);
391 } 359 if (char == '('.charCodeAt(0)) sum++;
360 else if (char == ')'.charCodeAt(0)) sum--;
361 index++;
362 }
363 return index;
364 }
365
366 /**
367 * Replace the double and single quote directly after a Hebrew character in
368 * [str] with GERESH and GERSHAYIM. This is most likely the user's intention.
369 */
370 static String normalizeHebrewQuote(String str) {
371 StringBuffer buf = new StringBuffer();
372 if (str.length > 0) {
373 buf.add(str.substring(0, 1));
374 }
375 // Start at 1 because we're looking for the patterns [\u0591-\u05f2])" or
376 // [\u0591-\u05f2]'.
377 for (int i = 1; i < str.length; i++) {
378 if (str.substring(i, i+1) == '"'
379 && const RegExp('[\u0591-\u05f2]').hasMatch(str.substring(i-1, i))) {
380 buf.add('\u05f4');
381 } else if (str.substring(i, i+1) == "'"
382 && const RegExp('[\u0591-\u05f2]').hasMatch(str.substring(i-1, i))) {
383 buf.add('\u05f3');
384 } else {
385 buf.add(str.substring(i, i+1));
386 }
387 }
388 return buf.toString();
389 }
390
391 /**
392 * Check the estimated directionality of [str], return true if the piece of
393 * text should be laid out in RTL direction. If [isHtml] is true, the string
394 * is HTML or HTML-escaped.
395 */
396 static bool detectRtlDirectionality(String str, [bool isHtml]) {
397 return estimateDirectionOfText(str, isHtml) == TextDirection.RTL;
398 }
399 }
OLDNEW
« no previous file with comments | « pkg/intl/lib/bidi_formatter.dart ('k') | pkg/intl/lib/intl.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698