1 // Copyright 2013 Google Inc. 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 // Author: dsites@google.com (Dick Sites)
17 //
18
19
20 #include "getonescriptspan.h"
21 #include <string.h>
22
23 #include "fixunicodevalue.h"
24 #include "lang_script.h"
25 #include "port.h"
26 #include "utf8statetable.h"
27
28 #include "utf8prop_lettermarkscriptnum.h"
29 #include "utf8repl_lettermarklower.h"
30 #include "utf8scannot_lettermarkspecial.h"
31
32
33 namespace CLD2 {
34
35 // Alphabetical order for binary search, from
36 // generated_entities.cc
37 extern const int kNameToEntitySize;
38 extern const CharIntPair kNameToEntity[];
39
40 static const int kMaxUpToWordBoundary = 50; // span < this make longer,
41 // else make shorter
42 static const int kMaxAdvanceToWordBoundary = 10; // +/- this many bytes
43 // to round to word boundary,
44 // direction above
45
46 static const char kSpecialSymbol[256] = { // true for < > &
47 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
48 0,0,0,0,0,0,1,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,1,0,1,0,
49 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
50 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
51
52 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
53 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
54 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
55 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,
56 };
57
58
59
60 #define LT 0 // <
61 #define GT 1 // >
62 #define EX 2 // !
63 #define HY 3 // -
64 #define QU 4 // "
65 #define AP 5 // '
66 #define SL 6 // /
67 #define S_ 7
68 #define C_ 8
69 #define R_ 9
70 #define I_ 10
71 #define P_ 11
72 #define T_ 12
73 #define Y_ 13
74 #define L_ 14
75 #define E_ 15
76 #define CR 16 // <cr> or <lf>
77 #define NL 17 // non-letter: ASCII whitespace, digit, punctuation
78 #define PL 18 // possible letter, incl. &
79 #define xx 19 // <unused>
80
81 // Map byte to one of ~20 interesting categories for cheap tag parsing
82 static const uint8 kCharToSub[256] = {
83 NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,CR,NL, NL,CR,NL,NL,
84 NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
85 NL,EX,QU,NL, NL,NL,PL,AP, NL,NL,NL,NL, NL,HY,NL,SL,
86 NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, LT,NL,GT,NL,
87
88 PL,PL,PL,C_, PL,E_,PL,PL, PL,I_,PL,PL, L_,PL,PL,PL,
89 P_,PL,R_,S_, T_,PL,PL,PL, PL,Y_,PL,NL, NL,NL,NL,NL,
90 PL,PL,PL,C_, PL,E_,PL,PL, PL,I_,PL,PL, L_,PL,PL,PL,
91 P_,PL,R_,S_, T_,PL,PL,PL, PL,Y_,PL,NL, NL,NL,NL,NL,
92
93 NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
94 NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
95 NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
96 NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL, NL,NL,NL,NL,
97
98 PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL,
99 PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL,
100 PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL,
101 PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL, PL,PL,PL,PL,
102 };
103
104 #undef LT
105 #undef GT
106 #undef EX
107 #undef HY
108 #undef QU
109 #undef AP
110 #undef SL
111 #undef S_
112 #undef C_
113 #undef R_
114 #undef I_
115 #undef P_
116 #undef T_
117 #undef Y_
118 #undef L_
119 #undef E_
120 #undef CR
121 #undef NL
122 #undef PL
123 #undef xx
124
125
126 #define OK 0
127 #define X_ 1
128
129
130 static const int kMaxExitStateLettersMarksOnly = 1;
131 static const int kMaxExitStateAllText = 2;
132
133
134 // State machine to do cheap parse of non-letter strings incl. tags
135 // advances <tag>
136 // | |
137 // advances <tag> ... </tag> for <script> <style>
138 // | |
139 // advances <!-- ... <tag> ... -->
140 // | |
141 // advances <tag
142 // || (0)
143 // advances <tag <tag2>
144 // || (0)
145 //
146 // We start in state [0] at a non-letter and make at least one transition
147 // When scanning for just letters, arriving back at state [0] or [1] exits
148 // the state machine.
149 // When scanning for any non-tag text, arriving at state [2] also exits
150 static const uint8 kTagParseTbl_0[] = {
151 // < > ! - " ' / S C R I P T Y L E CR NL PL xx
152 3, 2, 2, 2, 2, 2, 2,OK, OK,OK,OK,OK, OK,OK,OK,OK, 2, 2,OK,X_, // [0] OK exit state
153 X_,X_,X_,X_, X_,X_,X_,X_, X_,X_,X_,X_, X_,X_,X_,X_, X_,X_,X_,X_, // [1] error exit state
154 3, 2, 2, 2, 2, 2, 2,OK, OK,OK,OK,OK, OK,OK,OK,OK, 2, 2,OK,X_, // [2] NL* [exit state]
155 X_, 2, 4, 9, 10,11, 9,13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [3] <
156 X_, 2, 9, 5, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [4] <!
157 X_, 2, 9, 6, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [5] <!-
158 6, 6, 6, 7, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,X_, // [6] <!--.*
159 6, 6, 6, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,X_, // [7] <!--.*-
160 6, 2, 6, 8, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,X_, // [8] <!--.*--
161 X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [9] <.*
162 10,10,10,10, 9,10,10,10, 10,10,10,10, 10,10,10,10, 12,10,10,X_, // [10] <.*"
163 11,11,11,11, 11, 9,11,11, 11,11,11,11, 11,11,11,11, 12,11,11,X_, // [11] <.*'
164 X_, 2,12,12, 12,12,12,12, 12,12,12,12, 12,12,12,12, 12,12,12,X_, // [12] <.* no " '
165
166 // < > ! - " ' / S C R I P T Y L E CR NL PL xx
167 X_, 2, 9, 9, 10,11, 9, 9, 14, 9, 9, 9, 28, 9, 9, 9, 9, 9, 9,X_, // [13] <S
168 X_, 2, 9, 9, 10,11, 9, 9, 9,15, 9, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [14] <SC
169 X_, 2, 9, 9, 10,11, 9, 9, 9, 9,16, 9, 9, 9, 9, 9, 9, 9, 9,X_, // [15] <SCR
170 X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9,17, 9, 9, 9, 9, 9, 9, 9,X_, // [16] <SCRI
171 X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 18, 9, 9, 9, 9, 9, 9,X_, // [17] <SCRIP
172 X_,19, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 19,19, 9,X_, // [18] <SCRIPT
173 20,19,19,19, 19,19,19,19, 19,19,19,19, 19,19,19,19, 19,19,19,X_, // [19] <SCRIPT .*
174 19,19,19,19, 19,19,21,19, 19,19,19,19, 19,19,19,19, 19,19,19,X_, // [20] <SCRIPT .*<
175 19,19,19,19, 19,19,19,22, 19,19,19,19, 19,19,19,19, 21,21,19,X_, // [21] <SCRIPT .*</ allow SP CR LF
176 19,19,19,19, 19,19,19,19, 23,19,19,19, 19,19,19,19, 19,19,19,X_, // [22] <SCRIPT .*</S
177 19,19,19,19, 19,19,19,19, 19,24,19,19, 19,19,19,19, 19,19,19,X_, // [23] <SCRIPT .*</SC
178 19,19,19,19, 19,19,19,19, 19,19,25,19, 19,19,19,19, 19,19,19,X_, // [24] <SCRIPT .*</SCR
179 19,19,19,19, 19,19,19,19, 19,19,19,26, 19,19,19,19, 19,19,19,X_, // [25] <SCRIPT .*</SCRI
180 19,19,19,19, 19,19,19,19, 19,19,19,19, 27,19,19,19, 19,19,19,X_, // [26] <SCRIPT .*</SCRIP
181 19, 2,19,19, 19,19,19,19, 19,19,19,19, 19,19,19,19, 19,19,19,X_, // [27] <SCRIPT .*</SCRIPT
182
183 // < > ! - " ' / S C R I P T Y L E CR NL PL xx
184 X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9,29, 9, 9, 9, 9, 9,X_, // [28] <ST
185 X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9,30, 9, 9, 9, 9,X_, // [29] <STY
186 X_, 2, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9,31, 9, 9, 9,X_, // [30] <STYL
187 X_,32, 9, 9, 10,11, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 32,32, 9,X_, // [31] <STYLE
188 33,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,X_, // [32] <STYLE .*
189 32,32,32,32, 32,32,34,32, 32,32,32,32, 32,32,32,32, 32,32,32,X_, // [33] <STYLE .*<
190 32,32,32,32, 32,32,32,35, 32,32,32,32, 32,32,32,32, 34,34,32,X_, // [34] <STYLE .*</ allow SP CR LF
191 32,32,32,32, 32,32,32,32, 32,32,32,32, 36,32,32,32, 32,32,32,X_, // [35] <STYLE .*</S
192 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,37,32,32, 32,32,32,X_, // [36] <STYLE .*</ST
193 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,38,32, 32,32,32,X_, // [37] <STYLE .*</STY
194 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,39, 32,32,32,X_, // [38] <STYLE .*</STYL
195 32, 2,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,32, 32,32,32,X_, // [39] <STYLE .*</STYLE
196 };
197
198 #undef OK
199 #undef X_
200
201 enum
202 {
203 UTFmax = 4, // maximum bytes per rune
204 Runesync = 0x80, // cannot represent part of a UTF sequence (<)
205 Runeself = 0x80, // rune and UTF sequences are the same (<)
206 Runeerror = 0xFFFD, // decoding error in UTF
207 Runemax = 0x10FFFF, // maximum rune value
208 };
209
210 // Debugging. Not thread safe.
211 static char gDisplayPiece[32];
212 const uint8 gCharlen[16] = {1,1,1,1, 1,1,1,1, 1,1,1,1, 2,2,3,4};
DisplayPiece(const char * next_byte_,int byte_length_)213 char* DisplayPiece(const char* next_byte_, int byte_length_) {
214 // Copy up to 8 UTF-8 chars to buffer
215 int k = 0; // byte count
216 int n = 0; // character count
217 for (int i = 0; i < byte_length_; ++i) {
218 char c = next_byte_[i];
219 if ((c & 0xc0) != 0x80) {
220 // Beginning of a UTF-8 character
221 int charlen = gCharlen[static_cast<uint8>(c) >> 4];
222 if (i + charlen > byte_length_) {break;} // Not enough room for full char
223 if (k >= (32 - 7)) {break;} // Not necessarily enough room
224 if (n >= 8) {break;} // Enough characters already
225 ++n;
226 }
227 if (c == '<') {
228 memcpy(&gDisplayPiece[k], "<", 4); k += 4;
229 } else if (c == '>') {
230 memcpy(&gDisplayPiece[k], ">", 4); k += 4;
231 } else if (c == '&') {
232 memcpy(&gDisplayPiece[k], "&", 5); k += 5;
233 } else if (c == '\'') {
234 memcpy(&gDisplayPiece[k], "'", 6); k += 6;
235 } else if (c == '"') {
236 memcpy(&gDisplayPiece[k], """, 6); k += 6;
237 } else {
238 gDisplayPiece[k++] = c;
239 }
240 }
241 gDisplayPiece[k++] = '\0';
242 return gDisplayPiece;
243 }
244
245
246
247 // runetochar copies (encodes) one rune, pointed to by r, to at most
248 // UTFmax bytes starting at s and returns the number of bytes generated.
runetochar(char * str,const char32 * rune)249 int runetochar(char *str, const char32 *rune) {
250 // Convert to unsigned for range check.
251 unsigned long c;
252
253 // 1 char 00-7F
254 c = *rune;
255 if(c <= 0x7F) {
256 str[0] = c;
257 return 1;
258 }
259
260 // 2 char 0080-07FF
261 if(c <= 0x07FF) {
262 str[0] = 0xC0 | (c >> 1*6);
263 str[1] = 0x80 | (c & 0x3F);
264 return 2;
265 }
266
267 // Range check
268 if (c > Runemax) {
269 c = Runeerror;
270 }
271
272 // 3 char 0800-FFFF
273 if (c <= 0xFFFF) {
274 str[0] = 0xE0 | (c >> 2*6);
275 str[1] = 0x80 | ((c >> 1*6) & 0x3F);
276 str[2] = 0x80 | (c & 0x3F);
277 return 3;
278 }
279
280 // 4 char 10000-1FFFFF
281 str[0] = 0xF0 | (c >> 3*6);
282 str[1] = 0x80 | ((c >> 2*6) & 0x3F);
283 str[2] = 0x80 | ((c >> 1*6) & 0x3F);
284 str[3] = 0x80 | (c & 0x3F);
285 return 4;
286 }
287
288
289
290 // Useful for converting an entity to an ascii value.
291 // RETURNS unicode value, or -1 if entity isn't valid. Don't include & or ;
LookupEntity(const char * entity_name,int entity_len)292 int LookupEntity(const char* entity_name, int entity_len) {
293 // Make a C string
294 if (entity_len >= 16) {return -1;} // All real entities are shorter
295 char temp[16];
296 memcpy(temp, entity_name, entity_len);
297 temp[entity_len] = '\0';
298 int match = BinarySearch(temp, 0, kNameToEntitySize, kNameToEntity);
299 if (match >= 0) {return kNameToEntity[match].i;}
300 return -1;
301 }
302
ascii_isdigit(char c)303 bool ascii_isdigit(char c) {
304 return ('0' <= c) && (c <= '9');
305 }
ascii_isxdigit(char c)306 bool ascii_isxdigit(char c) {
307 if (('0' <= c) && (c <= '9')) {return true;}
308 if (('a' <= c) && (c <= 'f')) {return true;}
309 if (('A' <= c) && (c <= 'F')) {return true;}
310 return false;
311 }
ascii_isalnum(char c)312 bool ascii_isalnum(char c) {
313 if (('0' <= c) && (c <= '9')) {return true;}
314 if (('a' <= c) && (c <= 'z')) {return true;}
315 if (('A' <= c) && (c <= 'Z')) {return true;}
316 return false;
317 }
hex_digit_to_int(char c)318 int hex_digit_to_int(char c) {
319 if (('0' <= c) && (c <= '9')) {return c - '0';}
320 if (('a' <= c) && (c <= 'f')) {return c - 'a' + 10;}
321 if (('A' <= c) && (c <= 'F')) {return c - 'A' + 10;}
322 return 0;
323 }
324
strto32_base10(const char * nptr,const char * limit,const char ** endptr)325 static int32 strto32_base10(const char* nptr, const char* limit,
326 const char **endptr) {
327 *endptr = nptr;
328 while (nptr < limit && *nptr == '0') {
329 ++nptr;
330 }
331 if (nptr == limit || !ascii_isdigit(*nptr))
332 return -1;
333 const char* end_digits_run = nptr;
334 while (end_digits_run < limit && ascii_isdigit(*end_digits_run)) {
335 ++end_digits_run;
336 }
337 *endptr = end_digits_run;
338 const int num_digits = end_digits_run - nptr;
339 // kint32max == 2147483647.
340 if (num_digits < 9 ||
341 (num_digits == 10 && memcmp(nptr, "2147483647", 10) <= 0)) {
342 int value = 0;
343 for (; nptr < end_digits_run; ++nptr) {
344 value *= 10;
345 value += *nptr - '0';
346 }
347 // Overflow past the last valid unicode codepoint
348 // (0x10ffff) is converted to U+FFFD by FixUnicodeValue().
349 return FixUnicodeValue(value);
350 } else {
351 // Overflow: can't fit in an int32;
352 // returns the replacement character 0xFFFD.
353 return 0xFFFD;
354 }
355 }
356
strto32_base16(const char * nptr,const char * limit,const char ** endptr)357 static int32 strto32_base16(const char* nptr, const char* limit,
358 const char **endptr) {
359 *endptr = nptr;
360 while (nptr < limit && *nptr == '0') {
361 ++nptr;
362 }
363 if (nptr == limit || !ascii_isxdigit(*nptr)) {
364 return -1;
365 }
366 const char* end_xdigits_run = nptr;
367 while (end_xdigits_run < limit && ascii_isxdigit(*end_xdigits_run)) {
368 ++end_xdigits_run;
369 }
370 *endptr = end_xdigits_run;
371 const int num_xdigits = end_xdigits_run - nptr;
372 // kint32max == 0x7FFFFFFF.
373 if (num_xdigits < 8 || (num_xdigits == 8 && nptr[0] < '8')) {
374 int value = 0;
375 for (; nptr < end_xdigits_run; ++nptr) {
376 value <<= 4;
377 value += hex_digit_to_int(*nptr);
378 }
379 // Overflow past the last valid unicode codepoint
380 // (0x10ffff) is converted to U+FFFD by FixUnicodeValue().
381 return FixUnicodeValue(value);
382 } else {
383 // Overflow: can't fit in an int32;
384 // returns the replacement character 0xFFFD.
385 return 0xFFFD;
386 }
387 }
388
389 // Unescape the current character pointed to by src. SETS the number
390 // of chars read for the conversion (in UTF8). If src isn't a valid entity,
391 // just consume the & and RETURN -1. If src doesn't point to & -- which it
392 // should -- set src_consumed to 0 and RETURN -1.
ReadEntity(const char * src,int srcn,int * src_consumed)393 int ReadEntity(const char* src, int srcn, int* src_consumed) {
394 const char* const srcend = src + srcn;
395
396 if (srcn == 0 || *src != '&') { // input should start with an ampersand
397 *src_consumed = 0;
398 return -1;
399 }
400 *src_consumed = 1; // we'll get the & at least
401
402 // The standards are a bit unclear on when an entity ends. Certainly a ";"
403 // ends one, but spaces probably do too. We follow the lead of both IE and
404 // Netscape, which as far as we can tell end numeric entities (1st case below)
405 // at any non-digit, and end character entities (2nd case) at any non-alnum.
406 const char* entstart, *entend; // where the entity starts and ends
407 entstart = src + 1; // read past the &
408 int entval; // UCS2 value of the entity
409 if ( *entstart == '#' ) { // -- 1st case: numeric entity
410 if ( entstart + 2 >= srcend ) {
411 return -1; // no way a legitimate number could fit
412 } else if ( entstart[1] == 'x' || entstart[1] == 'X' ) { // hex numeric
413 entval = strto32_base16(entstart + 2, srcend, &entend);
414 } else { // decimal numeric entity
415 entval = strto32_base10(entstart+1, srcend, &entend);
416 }
417 if (entval == -1 || entend > srcend) {
418 return -1; // not entirely correct, but close enough
419 }
420 } else { // -- 2nd case: character entity
421 for (entend = entstart;
422 entend < srcend && ascii_isalnum(*entend);
423 ++entend ) {
424 // entity consists of alphanumeric chars
425 }
426 entval = LookupEntity(entstart, entend - entstart);
427 if (entval < 0) {
428 return -1; // not a legal entity name
429 }
430 // Now we do a strange-seeming IE6-compatibility check: if entval is
431 // >= 256, it *must* be followed by a semicolon or it's not considered
432 // an entity. The problem is lots of the newfangled entity names, like
433 // "lang", also occur in URL CGI arguments: "/search?q=test&lang=en".
434 // When these links are written in HTML, it would be really bad if the
435 // "&lang" were treated as an entity, which is what the spec says
436 // *should* happen (even when the HTML is inside an "A HREF" tag!)
437 // IE ignores the spec for these new, high-value entities, so we do too.
438 if ( entval >= 256 && !(entend < srcend && *entend == ';') ) {
439 return -1; // make non-;-terminated entity illegal
440 }
441 }
442
443 // Finally, figure out how much src was consumed
444 if ( entend < srcend && *entend == ';' ) {
445 entend++; // standard says ; terminator is special
446 }
447 *src_consumed = entend - src;
448 return entval;
449 }
450
451
452 // Src points to '&'
453 // Writes entity value to dst. Returns take(src), put(dst) byte counts
EntityToBuffer(const char * src,int len,char * dst,int * tlen,int * plen)454 void EntityToBuffer(const char* src, int len, char* dst,
455 int* tlen, int* plen) {
456 char32 entval = ReadEntity(src, len, tlen);
457
458 // ReadEntity does this already: entval = FixUnicodeValue(entval);
459
460 // Convert UTF-32 to UTF-8
461 if (entval > 0) {
462 *plen = runetochar(dst, &entval);
463 } else {
464 // Illegal entity; ignore the '&'
465 *tlen = 1;
466 *plen = 0;
467 }
468 }
469
470 // Returns true if character is < > or &, none of which are letters
IsSpecial(char c)471 bool inline IsSpecial(char c) {
472 if ((c & 0xe0) == 0x20) {
473 return kSpecialSymbol[static_cast<uint8>(c)];
474 }
475 return false;
476 }
477
478 // Quick Skip to next letter or < > & or to end of string (eos)
479 // Always return is_letter for eos
ScanToLetterOrSpecial(const char * src,int len)480 int ScanToLetterOrSpecial(const char* src, int len) {
481 int bytes_consumed;
482 StringPiece str(src, len);
483 UTF8GenericScan(&utf8scannot_lettermarkspecial_obj, str, &bytes_consumed);
484 return bytes_consumed;
485 }
486
487
488
489
490 // src points to non-letter, such as tag-opening '<'
491 // Return length from here to next possible letter
492 // On another < before >, return 1
493 // advances <tag>
494 // | |
495 // advances <tag> ... </tag> for <script> <style>
496 // | |
497 // advances <!-- ... <tag> ... -->
498 // | |
499 // advances <tag
500 // | | end of string
501 // advances <tag <tag2>
502 // ||
ScanToPossibleLetter(const char * isrc,int len,int max_exit_state)503 int ScanToPossibleLetter(const char* isrc, int len, int max_exit_state) {
504 const uint8* src = reinterpret_cast<const uint8*>(isrc);
505 const uint8* srclimit = src + len;
506 const uint8* tagParseTbl = kTagParseTbl_0;
507 int e = 0;
508 while (src < srclimit) {
509 e = tagParseTbl[kCharToSub[*src++]];
510 if (e <= max_exit_state) {
511 // We overshot by one byte
512 --src;
513 break;
514 }
515 tagParseTbl = &kTagParseTbl_0[e * 20];
516 }
517
518 if (src >= srclimit) {
519 // We fell off the end of the text.
520 // It looks like the most common case for this is a truncated file, not
521 // mismatched angle brackets. So we pretend that the last char was '>'
522 return len;
523 }
524
525 // OK to be in state 0 or state 2 at exit
526 if ((e != 0) && (e != 2)) {
527 // Error, '<' followed by '<'
528 // We want to back up to first <, then advance by one byte past it
529 int offset = src - reinterpret_cast<const uint8*>(isrc);
530
531 // Backscan to first '<' and return enough length to just get past it
532 --offset; // back up over the second '<', which caused us to stop
533 while ((0 < offset) && (isrc[offset] != '<')) {
534 // Find the first '<', which is unmatched
535 --offset;
536 }
537 // skip to just beyond first '<'
538 return offset + 1;
539 }
540
541 return src - reinterpret_cast<const uint8*>(isrc);
542 }
543
544
ScriptScanner(const char * buffer,int buffer_length,bool is_plain_text)545 ScriptScanner::ScriptScanner(const char* buffer,
546 int buffer_length,
547 bool is_plain_text)
548 : start_byte_(buffer),
549 next_byte_(buffer),
550 next_byte_limit_(buffer + buffer_length),
551 byte_length_(buffer_length),
552 is_plain_text_(is_plain_text),
553 letters_marks_only_(true),
554 one_script_only_(true),
555 exit_state_(kMaxExitStateLettersMarksOnly) {
556 script_buffer_ = new char[kMaxScriptBuffer];
557 script_buffer_lower_ = new char[kMaxScriptLowerBuffer];
558 map2original_.Clear(); // map from script_buffer_ to buffer
559 map2uplow_.Clear(); // map from script_buffer_lower_ to script_buffer_
560 }
561
562 // Extended version to allow spans of any non-tag text and spans of mixed script
ScriptScanner(const char * buffer,int buffer_length,bool is_plain_text,bool any_text,bool any_script)563 ScriptScanner::ScriptScanner(const char* buffer,
564 int buffer_length,
565 bool is_plain_text,
566 bool any_text,
567 bool any_script)
568 : start_byte_(buffer),
569 next_byte_(buffer),
570 next_byte_limit_(buffer + buffer_length),
571 byte_length_(buffer_length),
572 is_plain_text_(is_plain_text),
573 letters_marks_only_(!any_text),
574 one_script_only_(!any_script),
575 exit_state_(any_text ? kMaxExitStateAllText : kMaxExitStateLettersMarksOnly) {
576 script_buffer_ = new char[kMaxScriptBuffer];
577 script_buffer_lower_ = new char[kMaxScriptLowerBuffer];
578 map2original_.Clear(); // map from script_buffer_ to buffer
579 map2uplow_.Clear(); // map from script_buffer_lower_ to script_buffer_
580 }
581
582
~ScriptScanner()583 ScriptScanner::~ScriptScanner() {
584 delete[] script_buffer_;
585 delete[] script_buffer_lower_;
586 }
587
588
589
590
591 // Get to the first real non-tag letter or entity that is a letter
592 // Sets script of that letter
593 // Return len if no more letters
SkipToFrontOfSpan(const char * src,int len,int * script)594 int ScriptScanner::SkipToFrontOfSpan(const char* src, int len, int* script) {
595 int sc = UNKNOWN_ULSCRIPT;
596 int skip = 0;
597 int tlen, plen;
598
599 // Do run of non-letters (tag | &NL | NL)*
600 tlen = 0;
601 while (skip < len) {
602 // Do fast scan to next interesting byte
603 // int oldskip = skip;
604 skip += ScanToLetterOrSpecial(src + skip, len - skip);
605
606 // Check for no more letters/specials
607 if (skip >= len) {
608 // All done
609 *script = sc;
610 return len;
611 }
612
613 // We are at a letter, nonletter, tag, or entity
614 if (IsSpecial(src[skip]) && !is_plain_text_) {
615 if (src[skip] == '<') {
616 // Begining of tag; skip to end and go around again
617 tlen = ScanToPossibleLetter(src + skip, len - skip,
618 exit_state_);
619 sc = 0;
620 } else if (src[skip] == '>') {
621 // Unexpected end of tag; skip it and go around again
622 tlen = 1; // Over the >
623 sc = 0;
624 } else if (src[skip] == '&') {
625 // Expand entity, no advance
626 char temp[4];
627 EntityToBuffer(src + skip, len - skip,
628 temp, &tlen, &plen);
629 sc = GetUTF8LetterScriptNum(temp);
630 }
631 } else {
632 // Update 1..4 bytes
633 tlen = UTF8OneCharLen(src + skip);
634 sc = GetUTF8LetterScriptNum(src + skip);
635 }
636 if (sc != 0) {break;} // Letter found
637 skip += tlen; // Else advance
638 }
639
640 *script = sc;
641 return skip;
642 }
643
644
645 // These are for ASCII-only tag names
646 // Compare one letter uplow to c, ignoring case of uplowp
EqCase(char uplow,char c)647 inline bool EqCase(char uplow, char c) {
648 return (uplow | 0x20) == c;
649 }
650
651 // These are for ASCII-only tag names
652 // Return true for space / < > etc. all less than 0x40
NeqLetter(char c)653 inline bool NeqLetter(char c) {
654 return c < 0x40;
655 }
656
657 // These are for ASCII-only tag names
658 // Return true for space \n false for \r
WS(char c)659 inline bool WS(char c) {
660 return (c == ' ') || (c == '\n');
661 }
662
663 // Canonical CR or LF
664 static const char LF = '\n';
665
666
667 // The naive loop scans from next_byte_ to script_buffer_ until full.
668 // But this can leave an awkward hard-to-identify short fragment at the
669 // end of the input. We would prefer to make the next-to-last fragment
670 // shorter and the last fragment longer.
671
672 // Copy next run of non-tag characters to buffer [NUL terminated]
673 // This just replaces tags with space or \n and removes entities.
674 // Tags <br> <p> and <tr> are replaced with \n. Non-letter sequences
675 // including \r or \n are replaced by \n. All other tags and skipped text
676 // are replaced with ASCII space.
677 //
678 // Buffer ALWAYS has leading space and trailing space space space NUL
GetOneTextSpan(LangSpan * span)679 bool ScriptScanner::GetOneTextSpan(LangSpan* span) {
680 span->text = script_buffer_;
681 span->text_bytes = 0;
682 span->offset = next_byte_ - start_byte_;
683 span->ulscript = UNKNOWN_ULSCRIPT;
684 span->lang = UNKNOWN_LANGUAGE;
685 span->truncated = false;
686
687 int put_soft_limit = kMaxScriptBytes - kWithinScriptTail;
688 if ((kMaxScriptBytes <= byte_length_) &&
689 (byte_length_ < (2 * kMaxScriptBytes))) {
690 // Try to split the last two fragments in half
691 put_soft_limit = byte_length_ / 2;
692 }
693
694 script_buffer_[0] = ' '; // Always a space at front of output
695 script_buffer_[1] = '\0';
696 int take = 0;
697 int put = 1; // Start after the initial space
698 int tlen, plen;
699
700 if (byte_length_ <= 0) {
701 return false; // No more text to be found
702 }
703
704 // Go over alternating spans of text and tags,
705 // copying letters to buffer with single spaces for each run of non-letters
706 bool last_byte_was_space = false;
707 while (take < byte_length_) {
708 char c = next_byte_[take];
709 if (c == '\r') {c = LF;} // Canonical CR or LF
710 if (c == '\n') {c = LF;} // Canonical CR or LF
711
712 if (IsSpecial(c) && !is_plain_text_) {
713 if (c == '<') {
714 // Replace tag with space
715 c = ' '; // for almost-full test below
716 // or if <p> <br> <tr>, replace with \n
717 if (take < (byte_length_ - 3)) {
718 if (EqCase(next_byte_[take + 1], 'p') &&
719 NeqLetter(next_byte_[take + 2])) {
720 c = LF;
721 }
722 if (EqCase(next_byte_[take + 1], 'b') &&
723 EqCase(next_byte_[take + 2], 'r') &&
724 NeqLetter(next_byte_[take + 3])) {
725 c = LF;
726 }
727 if (EqCase(next_byte_[take + 1], 't') &&
728 EqCase(next_byte_[take + 2], 'r') &&
729 NeqLetter(next_byte_[take + 3])) {
730 c = LF;
731 }
732 }
733 // Begining of tag; skip to end and go around again
734 tlen = 1 + ScanToPossibleLetter(next_byte_ + take, byte_length_ - take,
735 exit_state_);
736 // Copy one byte, compressing spaces
737 if (!last_byte_was_space || !WS(c)) {
738 script_buffer_[put++] = c; // Advance dest
739 last_byte_was_space = WS(c);
740 }
741 } else if (c == '>') {
742 // Unexpected end of tag; copy it and go around again
743 tlen = 1; // Over the >
744 script_buffer_[put++] = c; // Advance dest
745 } else if (c == '&') {
746 // Expand entity, no advance
747 EntityToBuffer(next_byte_ + take, byte_length_ - take,
748 script_buffer_ + put, &tlen, &plen);
749 put += plen; // Advance dest
750 }
751 take += tlen; // Advance source
752 } else {
753 // Copy one byte, compressing spaces
754 if (!last_byte_was_space || !WS(c)) {
755 script_buffer_[put++] = c; // Advance dest
756 last_byte_was_space = WS(c);
757 }
758 ++take; // Advance source
759 }
760
761 if (WS(c) &&
762 (put >= put_soft_limit)) {
763 // Buffer is almost full
764 span->truncated = true;
765 break;
766 }
767 if (put >= kMaxScriptBytes) {
768 // Buffer is completely full
769 span->truncated = true;
770 break;
771 }
772 }
773
774 // Almost done. Back up to a character boundary if needed
775 while ((0 < take) && ((next_byte_[take] & 0xc0) == 0x80)) {
776 // Back up over continuation byte
777 --take;
778 --put;
779 }
780
781 // Update input position
782 next_byte_ += take;
783 byte_length_ -= take;
784
785 // Put four more spaces/NUL. Worst case is abcd _ _ _ \0
786 // kMaxScriptBytes | | put
787 script_buffer_[put + 0] = ' ';
788 script_buffer_[put + 1] = ' ';
789 script_buffer_[put + 2] = ' ';
790 script_buffer_[put + 3] = '\0';
791
792 span->text_bytes = put; // Does not include the last four chars above
793 return true;
794 }
795
796
797 // Copy next run of same-script non-tag letters to buffer [NUL terminated]
798 // Buffer ALWAYS has leading space and trailing space space space NUL
GetOneScriptSpan(LangSpan * span)799 bool ScriptScanner::GetOneScriptSpan(LangSpan* span) {
800 if (!letters_marks_only_) {
801 // Return non-tag text, including punctuation and digits
802 return GetOneTextSpan(span);
803 }
804
805 span->text = script_buffer_;
806 span->text_bytes = 0;
807 span->offset = next_byte_ - start_byte_;
808 span->ulscript = UNKNOWN_ULSCRIPT;
809 span->lang = UNKNOWN_LANGUAGE;
810 span->truncated = false;
811
812 // struct timeval script_start, script_mid, script_end;
813
814 int put_soft_limit = kMaxScriptBytes - kWithinScriptTail;
815 if ((kMaxScriptBytes <= byte_length_) &&
816 (byte_length_ < (2 * kMaxScriptBytes))) {
817 // Try to split the last two fragments in half
818 put_soft_limit = byte_length_ / 2;
819 }
820
821
822 int spanscript; // The script of this span
823 int sc = UNKNOWN_ULSCRIPT; // The script of next character
824 int tlen = 0;
825 int plen = 0;
826
827 script_buffer_[0] = ' '; // Always a space at front of output
828 script_buffer_[1] = '\0';
829 int take = 0;
830 int put = 1; // Start after the initial space
831
832 // Build offsets from span->text back to start_byte_ + span->offset
833 // This mapping reflects deletion of non-letters, expansion of
834 // entities, etc.
835 map2original_.Clear();
836 map2original_.Delete(span->offset); // So that MapBack(0) gives offset
837
838 // Get to the first real non-tag letter or entity that is a letter
839 int skip = SkipToFrontOfSpan(next_byte_, byte_length_, &spanscript);
840 next_byte_ += skip;
841 byte_length_ -= skip;
842
843 if (skip != 1) {
844 map2original_.Delete(skip);
845 map2original_.Insert(1);
846 } else {
847 map2original_.Copy(1);
848 }
849 if (byte_length_ <= 0) {
850 map2original_.Reset();
851 return false; // No more letters to be found
852 }
853
854 // There is at least one letter, so we know the script for this span
855 span->ulscript = (ULScript)spanscript;
856
857
858 // Go over alternating spans of same-script letters and non-letters,
859 // copying letters to buffer with single spaces for each run of non-letters
860 while (take < byte_length_) {
861 // Copy run of letters in same script (&LS | LS)*
862 int letter_count = 0; // Keep track of word length
863 bool need_break = false;
864
865 while (take < byte_length_) {
866 // We are at a letter, nonletter, tag, or entity
867 if (IsSpecial(next_byte_[take]) && !is_plain_text_) {
868 if (next_byte_[take] == '<') {
869 // Begining of tag
870 sc = 0;
871 break;
872 } else if (next_byte_[take] == '>') {
873 // Unexpected end of tag
874 sc = 0;
875 break;
876 } else if (next_byte_[take] == '&') {
877 // Copy entity, no advance
878 EntityToBuffer(next_byte_ + take, byte_length_ - take,
879 script_buffer_ + put, &tlen, &plen);
880 sc = GetUTF8LetterScriptNum(script_buffer_ + put);
881 }
882 } else {
883 // Real letter, safely copy up to 4 bytes, increment by 1..4
884 // Will update by 1..4 bytes at Advance, below
885 tlen = plen = UTF8OneCharLen(next_byte_ + take);
886 if (take < (byte_length_ - 3)) {
887 // X86 fast case, does unaligned load/store
888 UNALIGNED_STORE32(script_buffer_ + put,
889 UNALIGNED_LOAD32(next_byte_ + take));
890
891 } else {
892 // Slow case, happens 1-3 times per input document
893 memcpy(script_buffer_ + put, next_byte_ + take, plen);
894 }
895 sc = GetUTF8LetterScriptNum(next_byte_ + take);
896 }
897
898 // Allow continue across a single letter in a different script:
899 // A B D = three scripts, c = common script, i = inherited script,
900 // - = don't care, ( = take position before the += below
901 // AAA(A- continue
902 //
903 // AAA(BA continue
904 // AAA(BB break
905 // AAA(Bc continue (breaks after B)
906 // AAA(BD break
907 // AAA(Bi break
908 //
909 // AAA(c- break
910 //
911 // AAA(i- continue
912 //
913
914 if ((sc != spanscript) && (sc != ULScript_Inherited)) {
915 // Might need to break this script span
916 if (sc == ULScript_Common) {
917 need_break = true;
918 } else {
919 // Look at next following character, ignoring entity as Common
920 int sc2 = GetUTF8LetterScriptNum(next_byte_ + take + tlen);
921 if ((sc2 != ULScript_Common) && (sc2 != spanscript)) {
922 // We found a non-trivial change of script
923 if (one_script_only_) {
924 need_break = true;
925 }
926 }
927 }
928 }
929 if (need_break) {break;} // Non-letter or letter in wrong script
930
931 take += tlen; // Advance
932 put += plen; // Advance
933
934 // Update the offset map to reflect take/put lengths
935 if (tlen == plen) {
936 map2original_.Copy(tlen);
937 } else if (tlen < plen) {
938 map2original_.Copy(tlen);
939 map2original_.Insert(plen - tlen);
940 } else { // plen < tlen
941 map2original_.Copy(plen);
942 map2original_.Delete(tlen - plen);
943 }
944
945 ++letter_count;
946 if (put >= kMaxScriptBytes) {
947 // Buffer is full
948 span->truncated = true;
949 break;
950 }
951 } // End while letters
952
953 // Do run of non-letters (tag | &NL | NL)*
954 while (take < byte_length_) {
955 // Do fast scan to next interesting byte
956 tlen = ScanToLetterOrSpecial(next_byte_ + take, byte_length_ - take);
957 take += tlen;
958 map2original_.Delete(tlen);
959 if (take >= byte_length_) {break;} // Might have scanned to end
960
961 // We are at a letter, nonletter, tag, or entity
962 if (IsSpecial(next_byte_[take]) && !is_plain_text_) {
963 if (next_byte_[take] == '<') {
964 // Begining of tag; skip to end and go around again
965 tlen = ScanToPossibleLetter(next_byte_ + take, byte_length_ - take,
966 exit_state_);
967 sc = 0;
968 } else if (next_byte_[take] == '>') {
969 // Unexpected end of tag; skip it and go around again
970 tlen = 1; // Over the >
971 sc = 0;
972 } else if (next_byte_[take] == '&') {
973 // Expand entity, no advance
974 EntityToBuffer(next_byte_ + take, byte_length_ - take,
975 script_buffer_ + put, &tlen, &plen);
976 sc = GetUTF8LetterScriptNum(script_buffer_ + put);
977 }
978 } else {
979 // Update 1..4
980 tlen = UTF8OneCharLen(next_byte_ + take);
981 sc = GetUTF8LetterScriptNum(next_byte_ + take);
982 }
983 if (sc != 0) {break;} // Letter found
984 take += tlen; // Else advance
985 map2original_.Delete(tlen);
986 } // End while not-letters
987
988 script_buffer_[put++] = ' ';
989 map2original_.Insert(1);
990
991 // Letter in wrong script ?
992 if ((sc != spanscript) && (sc != ULScript_Inherited)) {break;}
993 if (put >= put_soft_limit) {
994 // Buffer is almost full
995 span->truncated = true;
996 break;
997 }
998 }
999
1000 // Almost done. Back up to a character boundary if needed
1001 while ((0 < take) && (take < byte_length_) &&
1002 ((next_byte_[take] & 0xc0) == 0x80)) {
1003 // Back up over continuation byte
1004 --take;
1005 --put;
1006 }
1007
1008 // Update input position
1009 next_byte_ += take;
1010 byte_length_ -= take;
1011
1012 // Put four more spaces/NUL. Worst case is abcd _ _ _ \0
1013 // kMaxScriptBytes | | put
1014 script_buffer_[put + 0] = ' ';
1015 script_buffer_[put + 1] = ' ';
1016 script_buffer_[put + 2] = ' ';
1017 script_buffer_[put + 3] = '\0';
1018 map2original_.Insert(4);
1019 map2original_.Reset();
1020
1021 span->text_bytes = put; // Does not include the last four chars above
1022 return true;
1023 }
1024
1025 // Force Latin, Cyrillic, Armenian, Greek scripts to be lowercase
1026 // List changes with each version of Unicode, so just always lowercase
1027 // Unicode 6.2.0:
1028 // ARMENIAN COPTIC CYRILLIC DESERET GEORGIAN GLAGOLITIC GREEK LATIN
LowerScriptSpan(LangSpan * span)1029 void ScriptScanner::LowerScriptSpan(LangSpan* span) {
1030 // If needed, lowercase all the text. If we do it sooner, might miss
1031 // lowercasing an entity such as Á
1032 // We only need to do this for Latn and Cyrl scripts
1033 map2uplow_.Clear();
1034 // Full Unicode lowercase of the entire buffer, including
1035 // four pad bytes off the end.
1036 // Ahhh. But the last byte 0x00 is not interchange-valid, so we do 3 pad
1037 // bytes and put the 0x00 in explicitly.
1038 // Build an offset map from script_buffer_lower_ back to script_buffer_
1039 int consumed, filled, changed;
1040 StringPiece istr(span->text, span->text_bytes + 3);
1041 StringPiece ostr(script_buffer_lower_, kMaxScriptLowerBuffer);
1042
1043 UTF8GenericReplace(&utf8repl_lettermarklower_obj,
1044 istr, ostr, is_plain_text_,
1045 &consumed, &filled, &changed, &map2uplow_);
1046 script_buffer_lower_[filled] = '\0';
1047 span->text = script_buffer_lower_;
1048 span->text_bytes = filled - 3;
1049 map2uplow_.Reset();
1050 }
1051
1052 // Copy next run of same-script non-tag letters to buffer [NUL terminated]
1053 // Force Latin, Cyrillic, Greek scripts to be lowercase
1054 // Buffer ALWAYS has leading space and trailing space space space NUL
GetOneScriptSpanLower(LangSpan * span)1055 bool ScriptScanner::GetOneScriptSpanLower(LangSpan* span) {
1056 bool ok = GetOneScriptSpan(span);
1057 LowerScriptSpan(span);
1058 return ok;
1059 }
1060
1061
1062 // Maps byte offset in most recent GetOneScriptSpan/Lower
1063 // span->text [0..text_bytes] into an additional byte offset from
1064 // span->offset, to get back to corresponding text in the original
1065 // input buffer.
1066 // text_offset must be the first byte
1067 // of a UTF-8 character, or just beyond the last character. Normally this
1068 // routine is called with the first byte of an interesting range and
1069 // again with the first byte of the following range.
MapBack(int text_offset)1070 int ScriptScanner::MapBack(int text_offset) {
1071 return map2original_.MapBack(map2uplow_.MapBack(text_offset));
1072 }
1073
1074
1075 // Gets lscript number for letters; always returns
1076 // 0 (common script) for non-letters
GetUTF8LetterScriptNum(const char * src)1077 int GetUTF8LetterScriptNum(const char* src) {
1078 int srclen = UTF8OneCharLen(src);
1079 const uint8* usrc = reinterpret_cast<const uint8*>(src);
1080 return UTF8GenericPropertyTwoByte(&utf8prop_lettermarkscriptnum_obj,
1081 &usrc, &srclen);
1082 }
1083
1084 } // namespace CLD2
1085
1086
1087