diff options
Diffstat (limited to 'browser/components/translation/cld2/internal/utf8statetable.cc')
-rw-r--r-- | browser/components/translation/cld2/internal/utf8statetable.cc | 1369 |
1 files changed, 1369 insertions, 0 deletions
diff --git a/browser/components/translation/cld2/internal/utf8statetable.cc b/browser/components/translation/cld2/internal/utf8statetable.cc new file mode 100644 index 000000000..8c97123d8 --- /dev/null +++ b/browser/components/translation/cld2/internal/utf8statetable.cc @@ -0,0 +1,1369 @@ +// Copyright 2013 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +// State Table follower for scanning UTF-8 strings without converting to +// 32- or 16-bit Unicode values. +// + +#ifdef COMPILER_MSVC +// MSVC warns: warning C4309: 'initializing' : truncation of constant value +// But the value is in fact not truncated. 0xFF still comes out 0xFF at +// runtime. +#pragma warning ( disable : 4309 ) +#endif + +#include "utf8statetable.h" + +#include <stdint.h> // for uintptr_t +#include <string.h> // for NULL, memcpy, memmove + +#include "integral_types.h" // for uint8, uint32, int8 +#include "stringpiece.h" +#include "offsetmap.h" + + +namespace CLD2 { + +static const int kReplaceAndResumeFlag = 0x80; // Bit in del byte to distinguish + // optional next-state field + // after replacement text +static const int kHtmlPlaintextFlag = 0x80; // Bit in add byte to distinguish + // HTML replacement vs. plaintext + + +/** + * This code implements a little interpreter for UTF8 state + * tables. There are three kinds of quite-similar state tables, + * property, scanning, and replacement. Each state in one of + * these tables consists of an array of 256 or 64 one-byte + * entries. The state is subscripted by an incoming source byte, + * and the entry either specifies the next state or specifies an + * action. Space-optimized tables have full 256-entry states for + * the first byte of a UTF-8 character, but only 64-entry states + * for continuation bytes. Space-optimized tables may only be + * used with source input that has been checked to be + * structurally- (or stronger interchange-) valid. + * + * A property state table has an unsigned one-byte property for + * each possible UTF-8 character. One-byte character properties + * are in the state[0] array, while for other lengths the + * state[0] array gives the next state, which contains the + * property value for two-byte characters or yet another state + * for longer ones. The code simply loads the right number of + * next-state values, then returns the final byte as property + * value. There are no actions specified in property tables. + * States are typically shared for multi-byte UTF-8 characters + * that all have the same property value. + * + * A scanning state table has entries that are either a + * next-state specifier for bytes that are accepted by the + * scanner, or an exit action for the last byte of each + * character that is rejected by the scanner. + * + * Scanning long strings involves a tight loop that picks up one + * byte at a time and follows next-state value back to state[0] + * for each accepted UTF-8 character. Scanning stops at the end + * of the string or at the first character encountered that has + * an exit action such as "reject". Timing information is given + * below. + * + * Since so much of Google's text is 7-bit-ASCII values + * (approximately 94% of the bytes of web documents), the + * scanning interpreter has two speed optimizations. One checks + * 8 bytes at a time to see if they are all in the range lo..hi, + * as specified in constants in the overall statetable object. + * The check involves ORing together four 4-byte values that + * overflow into the high bit of some byte when a byte is out of + * range. For seven-bit-ASCII, lo is 0x20 and hi is 0x7E. This + * loop is about 8x faster than the one-byte-at-a-time loop. + * + * If checking for exit bytes in the 0x00-0x1F and 7F range is + * unneeded, an even faster loop just looks at the high bits of + * 8 bytes at once, and is about 1.33x faster than the lo..hi + * loop. + * + * Exit from the scanning routines backs up to the first byte of + * the rejected character, so the text spanned is always a + * complete number of UTF-8 characters. The normal scanning exit + * is at the first rejected character, or at the end of the + * input text. Scanning also exits on any detected ill-formed + * character or at a special do-again action built into some + * exit-optimized tables. The do-again action gets back to the + * top of the scanning loop to retry eight-byte ASCII scans. It + * is typically put into state tables after four seven-bit-ASCII + * characters in a row are seen, to allow restarting the fast + * scan after some slower processing of multi-byte characters. + * + * A replacement state table is similar to a scanning state + * table but has more extensive actions. The default + * byte-at-a-time loop copies one byte from source to + * destination and goes to the next state. The replacement + * actions overwrite 1-3 bytes of the destination with different + * bytes, possibly shortening the output by 1 or 2 bytes. The + * replacement bytes come from within the state table, from + * dummy states inserted just after any state that contains a + * replacement action. This gives a quick address calculation for + * the replacement byte(s) and gives some cache locality. + * + * Additional replacement actions use one or two bytes from + * within dummy states to index a side table of more-extensive + * replacements. The side table specifies a length of 0..15 + * destination bytes to overwrite and a length of 0..127 bytes + * to overwrite them with, plus the actual replacement bytes. + * + * This side table uses one extra bit to specify a pair of + * replacements, the first to be used in an HTML context and the + * second to be used in a plaintext context. This allows + * replacements that are spelled with "<" in the former + * context and "<" in the latter. + * + * The side table also uses an extra bit to specify a non-zero + * next state after a replacement. This allows a combination + * replacement and state change, used to implement a limited + * version of the Boyer-Moore algorithm for multi-character + * replacement without backtracking. This is useful when there + * are overlapping replacements, such as ch => x and also c => + * y, the latter to be used only if the character after c is not + * h. in this case, the state[0] table's entry for c would + * change c to y and also have a next-state of say n, and the + * state[n] entry for h would specify a replacement of the two + * bytes yh by x. No backtracking is needed. + * + * A replacement table may also include the exit actions of a + * scanning state table, so some character sequences can + * terminate early. + * + * During replacement, an optional data structure called an + * offset map can be updated to reflect each change in length + * between source and destination. This offset map can later be + * used to map destination-string offsets to corresponding + * source-string offsets or vice versa. + * + * The routines below also have variants in which state-table + * entries are all two bytes instead of one byte. This allows + * tables with more than 240 total states, but takes up twice as + * much space per state. + * +**/ + +// Return true if current Tbl pointer is within state0 range +// Note that unsigned compare checks both ends of range simultaneously +static inline bool InStateZero(const UTF8ScanObj* st, const uint8* Tbl) { + const uint8* Tbl0 = &st->state_table[st->state0]; + return (static_cast<uint32>(Tbl - Tbl0) < st->state0_size); +} + +static inline bool InStateZero_2(const UTF8ReplaceObj_2* st, + const unsigned short int* Tbl) { + const unsigned short int* Tbl0 = &st->state_table[st->state0]; + // Word difference, not byte difference + return (static_cast<uint32>(Tbl - Tbl0) < st->state0_size); +} + +// UTF8PropObj, UTF8ScanObj, UTF8ReplaceObj are all typedefs of +// UTF8MachineObj. + +static bool IsPropObj(const UTF8StateMachineObj& obj) { + return obj.fast_state == NULL + && obj.max_expand == 0; +} + +static bool IsPropObj_2(const UTF8StateMachineObj_2& obj) { + return obj.fast_state == NULL + && obj.max_expand == 0; +} + +static bool IsScanObj(const UTF8StateMachineObj& obj) { + return obj.fast_state != NULL + && obj.max_expand == 0; +} + +static bool IsReplaceObj(const UTF8StateMachineObj& obj) { + // Normally, obj.fast_state != NULL, but the handwritten tables + // in utf8statetable_unittest don't handle fast_states. + return obj.max_expand > 0; +} + +static bool IsReplaceObj_2(const UTF8StateMachineObj_2& obj) { + return obj.max_expand > 0; +} + +// Look up property of one UTF-8 character and advance over it +// Return 0 if input length is zero +// Return 0 and advance one byte if input is ill-formed +uint8 UTF8GenericProperty(const UTF8PropObj* st, + const uint8** src, + int* srclen) { + if (*srclen <= 0) { + return 0; + } + + const uint8* lsrc = *src; + const uint8* Tbl_0 = &st->state_table[st->state0]; + const uint8* Tbl = Tbl_0; + int e; + int eshift = st->entry_shift; + + // Short series of tests faster than switch, optimizes 7-bit ASCII + unsigned char c = lsrc[0]; + if (static_cast<signed char>(c) >= 0) { // one byte + e = Tbl[c]; + *src += 1; + *srclen -= 1; + } else if (((c & 0xe0) == 0xc0) && (*srclen >= 2)) { // two bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + *src += 2; + *srclen -= 2; + } else if (((c & 0xf0) == 0xe0) && (*srclen >= 3)) { // three bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[2]]; + *src += 3; + *srclen -= 3; + }else if (((c & 0xf8) == 0xf0) && (*srclen >= 4)) { // four bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[2]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[3]]; + *src += 4; + *srclen -= 4; + } else { // Ill-formed + e = 0; + *src += 1; + *srclen -= 1; + } + return e; +} + +bool UTF8HasGenericProperty(const UTF8PropObj& st, const char* src) { + const uint8* lsrc = reinterpret_cast<const uint8*>(src); + const uint8* Tbl_0 = &st.state_table[st.state0]; + const uint8* Tbl = Tbl_0; + int e; + int eshift = st.entry_shift; + + // Short series of tests faster than switch, optimizes 7-bit ASCII + unsigned char c = lsrc[0]; + if (static_cast<signed char>(c) >= 0) { // one byte + e = Tbl[c]; + } else if ((c & 0xe0) == 0xc0) { // two bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + } else if ((c & 0xf0) == 0xe0) { // three bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[2]]; + } else { // four bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[2]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[3]]; + } + return e; +} + + +// BigOneByte versions are needed for tables > 240 states, but most +// won't need the TwoByte versions. +// Internally, to next-to-last offset is multiplied by 16 and the last +// offset is relative instead of absolute. +// Look up property of one UTF-8 character and advance over it +// Return 0 if input length is zero +// Return 0 and advance one byte if input is ill-formed +uint8 UTF8GenericPropertyBigOneByte(const UTF8PropObj* st, + const uint8** src, + int* srclen) { + if (*srclen <= 0) { + return 0; + } + + const uint8* lsrc = *src; + const uint8* Tbl_0 = &st->state_table[st->state0]; + const uint8* Tbl = Tbl_0; + int e; + int eshift = st->entry_shift; + + // Short series of tests faster than switch, optimizes 7-bit ASCII + unsigned char c = lsrc[0]; + if (static_cast<signed char>(c) >= 0) { // one byte + e = Tbl[c]; + *src += 1; + *srclen -= 1; + } else if (((c & 0xe0) == 0xc0) && (*srclen >= 2)) { // two bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + *src += 2; + *srclen -= 2; + } else if (((c & 0xf0) == 0xe0) && (*srclen >= 3)) { // three bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << (eshift + 4)]; // 16x the range + e = (reinterpret_cast<const int8*>(Tbl))[lsrc[1]]; + Tbl = &Tbl[e << eshift]; // Relative +/- + e = Tbl[lsrc[2]]; + *src += 3; + *srclen -= 3; + }else if (((c & 0xf8) == 0xf0) && (*srclen >= 4)) { // four bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << (eshift + 4)]; // 16x the range + e = (reinterpret_cast<const int8*>(Tbl))[lsrc[2]]; + Tbl = &Tbl[e << eshift]; // Relative +/- + e = Tbl[lsrc[3]]; + *src += 4; + *srclen -= 4; + } else { // Ill-formed + e = 0; + *src += 1; + *srclen -= 1; + } + return e; +} + +// BigOneByte versions are needed for tables > 240 states, but most +// won't need the TwoByte versions. +bool UTF8HasGenericPropertyBigOneByte(const UTF8PropObj& st, const char* src) { + const uint8* lsrc = reinterpret_cast<const uint8*>(src); + const uint8* Tbl_0 = &st.state_table[st.state0]; + const uint8* Tbl = Tbl_0; + int e; + int eshift = st.entry_shift; + + // Short series of tests faster than switch, optimizes 7-bit ASCII + unsigned char c = lsrc[0]; + if (static_cast<signed char>(c) >= 0) { // one byte + e = Tbl[c]; + } else if ((c & 0xe0) == 0xc0) { // two bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + } else if ((c & 0xf0) == 0xe0) { // three bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << (eshift + 4)]; // 16x the range + e = (reinterpret_cast<const int8*>(Tbl))[lsrc[1]]; + Tbl = &Tbl[e << eshift]; // Relative +/- + e = Tbl[lsrc[2]]; + } else { // four bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << (eshift + 4)]; // 16x the range + e = (reinterpret_cast<const int8*>(Tbl))[lsrc[2]]; + Tbl = &Tbl[e << eshift]; // Relative +/- + e = Tbl[lsrc[3]]; + } + return e; +} + + +// TwoByte versions are needed for tables > 240 states +// Look up property of one UTF-8 character and advance over it +// Return 0 if input length is zero +// Return 0 and advance one byte if input is ill-formed +uint8 UTF8GenericPropertyTwoByte(const UTF8PropObj_2* st, + const uint8** src, + int* srclen) { + if (*srclen <= 0) { + return 0; + } + + const uint8* lsrc = *src; + const unsigned short* Tbl_0 = &st->state_table[st->state0]; + const unsigned short* Tbl = Tbl_0; + int e; + int eshift = st->entry_shift; + + // Short series of tests faster than switch, optimizes 7-bit ASCII + unsigned char c = lsrc[0]; + if (static_cast<signed char>(c) >= 0) { // one byte + e = Tbl[c]; + *src += 1; + *srclen -= 1; + } else if (((c & 0xe0) == 0xc0) && (*srclen >= 2)) { // two bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + *src += 2; + *srclen -= 2; + } else if (((c & 0xf0) == 0xe0) && (*srclen >= 3)) { // three bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[2]]; + *src += 3; + *srclen -= 3; + }else if (((c & 0xf8) == 0xf0) && (*srclen >= 4)) { // four bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[2]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[3]]; + *src += 4; + *srclen -= 4; + } else { // Ill-formed + e = 0; + *src += 1; + *srclen -= 1; + } + return e; +} + +// TwoByte versions are needed for tables > 240 states +bool UTF8HasGenericPropertyTwoByte(const UTF8PropObj_2& st, const char* src) { + const uint8* lsrc = reinterpret_cast<const uint8*>(src); + const unsigned short* Tbl_0 = &st.state_table[st.state0]; + const unsigned short* Tbl = Tbl_0; + int e; + int eshift = st.entry_shift; + + // Short series of tests faster than switch, optimizes 7-bit ASCII + unsigned char c = lsrc[0]; + if (static_cast<signed char>(c) >= 0) { // one byte + e = Tbl[c]; + } else if ((c & 0xe0) == 0xc0) { // two bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + } else if ((c & 0xf0) == 0xe0) { // three bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[2]]; + } else { // four bytes + e = Tbl[c]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[1]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[2]]; + Tbl = &Tbl_0[e << eshift]; + e = Tbl[lsrc[3]]; + } + return e; +} + + +// Approximate speeds on 2.8 GHz Pentium 4: +// GenericScan 1-byte loop 300 MB/sec * +// GenericScan 4-byte loop 1200 MB/sec +// GenericScan 8-byte loop 2400 MB/sec * +// GenericScanFastAscii 4-byte loop 3000 MB/sec +// GenericScanFastAscii 8-byte loop 3200 MB/sec * +// +// * Implemented below. FastAscii loop is memory-bandwidth constrained. + +// Scan a UTF-8 stringpiece based on state table. +// Always scan complete UTF-8 characters +// Set number of bytes scanned. Return reason for exiting +int UTF8GenericScan(const UTF8ScanObj* st, + const StringPiece& str, + int* bytes_consumed) { + int eshift = st->entry_shift; // 6 (space optimized) or 8 + // int nEntries = (1 << eshift); // 64 or 256 entries per state + + const uint8* isrc = + reinterpret_cast<const uint8*>(str.data()); + const uint8* src = isrc; + const int len = str.length(); + const uint8* srclimit = isrc + len; + const uint8* srclimit8 = srclimit - 7; + *bytes_consumed = 0; + if (len == 0) return kExitOK; + + const uint8* Tbl_0 = &st->state_table[st->state0]; + +DoAgain: + // Do state-table scan + int e = 0; + uint8 c; + + // Do fast for groups of 8 identity bytes. + // This covers a lot of 7-bit ASCII ~8x faster than the 1-byte loop, + // including slowing slightly on cr/lf/ht + //---------------------------- + const uint8* Tbl2 = &st->fast_state[0]; + uint32 losub = st->losub; + uint32 hiadd = st->hiadd; + while (src < srclimit8) { + uint32 s0123 = (reinterpret_cast<const uint32 *>(src))[0]; + uint32 s4567 = (reinterpret_cast<const uint32 *>(src))[1]; + src += 8; + // This is a fast range check for all bytes in [lowsub..0x80-hiadd) + uint32 temp = (s0123 - losub) | (s0123 + hiadd) | + (s4567 - losub) | (s4567 + hiadd); + if ((temp & 0x80808080) != 0) { + // We typically end up here on cr/lf/ht; src was incremented + int e0123 = (Tbl2[src[-8]] | Tbl2[src[-7]]) | + (Tbl2[src[-6]] | Tbl2[src[-5]]); + if (e0123 != 0) {src -= 8; break;} // Exit on Non-interchange + e0123 = (Tbl2[src[-4]] | Tbl2[src[-3]]) | + (Tbl2[src[-2]] | Tbl2[src[-1]]); + if (e0123 != 0) {src -= 4; break;} // Exit on Non-interchange + // Else OK, go around again + } + } + //---------------------------- + + // Byte-at-a-time scan + //---------------------------- + const uint8* Tbl = Tbl_0; + while (src < srclimit) { + c = *src; + e = Tbl[c]; + src++; + if (e >= kExitIllegalStructure) {break;} + Tbl = &Tbl_0[e << eshift]; + } + //---------------------------- + + + // Exit possibilities: + // Some exit code, !state0, back up over last char + // Some exit code, state0, back up one byte exactly + // source consumed, !state0, back up over partial char + // source consumed, state0, exit OK + // For illegal byte in state0, avoid backup up over PREVIOUS char + // For truncated last char, back up to beginning of it + + if (e >= kExitIllegalStructure) { + // Back up over exactly one byte of rejected/illegal UTF-8 character + src--; + // Back up more if needed + if (!InStateZero(st, Tbl)) { + do {src--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80)); + } + } else if (!InStateZero(st, Tbl)) { + // Back up over truncated UTF-8 character + e = kExitIllegalStructure; + do {src--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80)); + } else { + // Normal termination, source fully consumed + e = kExitOK; + } + + if (e == kExitDoAgain) { + // Loop back up to the fast scan + goto DoAgain; + } + + *bytes_consumed = src - isrc; + return e; +} + +// Scan a UTF-8 stringpiece based on state table. +// Always scan complete UTF-8 characters +// Set number of bytes scanned. Return reason for exiting +// OPTIMIZED for case of 7-bit ASCII 0000..007f all valid +int UTF8GenericScanFastAscii(const UTF8ScanObj* st, + const StringPiece& str, + int* bytes_consumed) { + const uint8* isrc = + reinterpret_cast<const uint8*>(str.data()); + const uint8* src = isrc; + const int len = str.length(); + const uint8* srclimit = isrc + len; + const uint8* srclimit8 = srclimit - 7; + *bytes_consumed = 0; + if (len == 0) return kExitOK; + + int n; + int rest_consumed; + int exit_reason; + do { + // Skip 8 bytes of ASCII at a whack; no endianness issue + while ((src < srclimit8) && + (((reinterpret_cast<const uint32*>(src)[0] | + reinterpret_cast<const uint32*>(src)[1]) & 0x80808080) == 0)) { + src += 8; + } + // Run state table on the rest + n = src - isrc; + StringPiece str2(str.data() + n, str.length() - n); + exit_reason = UTF8GenericScan(st, str2, &rest_consumed); + src += rest_consumed; + } while ( exit_reason == kExitDoAgain ); + + *bytes_consumed = src - isrc; + return exit_reason; +} + +// Hack to change halfwidth katakana to match an old UTF8CharToLower() + +// Return number of src bytes skipped +static int DoSpecialFixup(const unsigned char c, + const unsigned char** srcp, const unsigned char* srclimit, + unsigned char** dstp, unsigned char* dstlimit) { + return 0; +} + + +// Scan a UTF-8 stringpiece based on state table, copying to output stringpiece +// and doing text replacements. +// DO NOT CALL DIRECTLY. Use UTF8GenericReplace() below +// Needs caller to loop on kExitDoAgain +static int UTF8GenericReplaceInternal(const UTF8ReplaceObj* st, + const StringPiece& istr, + StringPiece& ostr, + bool is_plain_text, + int* bytes_consumed, + int* bytes_filled, + int* chars_changed, + OffsetMap* offsetmap) { + int eshift = st->entry_shift; + int nEntries = (1 << eshift); // 64 or 256 entries per state + const uint8* isrc = reinterpret_cast<const uint8*>(istr.data()); + const int ilen = istr.length(); + const uint8* copystart = isrc; + const uint8* src = isrc; + const uint8* srclimit = src + ilen; + *bytes_consumed = 0; + *bytes_filled = 0; + *chars_changed = 0; + + const uint8* odst = reinterpret_cast<const uint8*>(ostr.data()); + const int olen = ostr.length(); + uint8* dst = const_cast<uint8*>(odst); + uint8* dstlimit = dst + olen; + + int total_changed = 0; + + // Invariant condition during replacements: + // remaining dst size >= remaining src size + if ((dstlimit - dst) < (srclimit - src)) { + if (offsetmap != NULL) { + offsetmap->Copy(src - copystart); + copystart = src; + } + return kExitDstSpaceFull; + } + const uint8* Tbl_0 = &st->state_table[st->state0]; + + Do_state_table: + // Do state-table scan, copying as we go + const uint8* Tbl = Tbl_0; + int e = 0; + uint8 c = 0; + + Do_state_table_newe: + + //---------------------------- + while (src < srclimit) { + c = *src; + e = Tbl[c]; + *dst = c; + src++; + dst++; + if (e >= kExitIllegalStructure) {break;} + Tbl = &Tbl_0[e << eshift]; + } + //---------------------------- + + // Exit possibilities: + // Replacement code, do the replacement and loop + // Some other exit code, state0, back up one byte exactly + // Some other exit code, !state0, back up over last char + // source consumed, state0, exit OK + // source consumed, !state0, back up over partial char + // For illegal byte in state0, avoid backup up over PREVIOUS char + // For truncated last char, back up to beginning of it + + if (e >= kExitIllegalStructure) { + // Switch on exit code; most loop back to top + int offset = 0; + switch (e) { + // These all make the output string the same size or shorter + // No checking needed + case kExitReplace31: // del 2, add 1 bytes to change + dst -= 2; + if (offsetmap != NULL) { + offsetmap->Copy(src - copystart - 2); + offsetmap->Delete(2); + copystart = src; + } + dst[-1] = (unsigned char)Tbl[c + (nEntries * 1)]; + total_changed++; + goto Do_state_table; + case kExitReplace32: // del 3, add 2 bytes to change + dst--; + if (offsetmap != NULL) { + offsetmap->Copy(src - copystart - 1); + offsetmap->Delete(1); + copystart = src; + } + dst[-2] = (unsigned char)Tbl[c + (nEntries * 2)]; + dst[-1] = (unsigned char)Tbl[c + (nEntries * 1)]; + total_changed++; + goto Do_state_table; + case kExitReplace21: // del 2, add 1 bytes to change + dst--; + if (offsetmap != NULL) { + offsetmap->Copy(src - copystart - 1); + offsetmap->Delete(1); + copystart = src; + } + dst[-1] = (unsigned char)Tbl[c + (nEntries * 1)]; + total_changed++; + goto Do_state_table; + case kExitReplace3: // update 3 bytes to change + dst[-3] = (unsigned char)Tbl[c + (nEntries * 3)]; + // Fall into next case + case kExitReplace2: // update 2 bytes to change + dst[-2] = (unsigned char)Tbl[c + (nEntries * 2)]; + // Fall into next case + case kExitReplace1: // update 1 byte to change + dst[-1] = (unsigned char)Tbl[c + (nEntries * 1)]; + total_changed++; + goto Do_state_table; + case kExitReplace1S0: // update 1 byte to change, 256-entry state + dst[-1] = (unsigned char)Tbl[c + (256 * 1)]; + total_changed++; + goto Do_state_table; + // These can make the output string longer than the input + case kExitReplaceOffset2: + if ((nEntries != 256) && InStateZero(st, Tbl)) { + // For space-optimized table, we need multiples of 256 bytes + // in state0 and multiples of nEntries in other states + offset += ((unsigned char)Tbl[c + (256 * 2)] << 8); + } else { + offset += ((unsigned char)Tbl[c + (nEntries * 2)] << 8); + } + // Fall into next case + case kExitSpecial: // Apply special fixups [read: hacks] + case kExitReplaceOffset1: + if ((nEntries != 256) && InStateZero(st, Tbl)) { + // For space-optimized table, we need multiples of 256 bytes + // in state0 and multiples of nEntries in other states + offset += (unsigned char)Tbl[c + (256 * 1)]; + } else { + offset += (unsigned char)Tbl[c + (nEntries * 1)]; + } + { + const RemapEntry* re = &st->remap_base[offset]; + int del_len = re->delete_bytes & ~kReplaceAndResumeFlag; + int add_len = re->add_bytes & ~kHtmlPlaintextFlag; + + // Special-case non-HTML replacement of five sensitive entities + // " & ' < > + // 0022 0026 0027 003c 003e + // A replacement creating one of these is expressed as a pair of + // entries, one for HTML output and one for plaintext output. + // The first of the pair has the high bit of add_bytes set. + if (re->add_bytes & kHtmlPlaintextFlag) { + // Use this entry for plain text + if (!is_plain_text) { + // Use very next entry for HTML text (same back/delete length) + re = &st->remap_base[offset + 1]; + add_len = re->add_bytes & ~kHtmlPlaintextFlag; + } + } + + int string_offset = re->bytes_offset; + // After the replacement, need (dstlimit - newdst) >= (srclimit - src) + uint8* newdst = dst - del_len + add_len; + if ((dstlimit - newdst) < (srclimit - src)) { + // Won't fit; don't do the replacement. Caller may realloc and retry + e = kExitDstSpaceFull; + break; // exit, backing up over this char for later retry + } + dst -= del_len; + memcpy(dst, &st->remap_string[string_offset], add_len); + dst += add_len; + total_changed++; + if (offsetmap != NULL) { + if (add_len > del_len) { + offsetmap->Copy(src - copystart); + offsetmap->Insert(add_len - del_len); + copystart = src; + } else if (add_len < del_len) { + offsetmap->Copy(src - copystart + add_len - del_len); + offsetmap->Delete(del_len - add_len); + copystart = src; + } + } + if (re->delete_bytes & kReplaceAndResumeFlag) { + // There is a non-zero target state at the end of the + // replacement string + e = st->remap_string[string_offset + add_len]; + Tbl = &Tbl_0[e << eshift]; + goto Do_state_table_newe; + } + } + if (e == kExitRejectAlt) {break;} + if (e != kExitSpecial) {goto Do_state_table;} + + // case kExitSpecial: // Apply special fixups [read: hacks] + // In this routine, do either UTF8CharToLower() + // fullwidth/halfwidth mapping or + // voiced mapping or + // semi-voiced mapping + + // First, do EXIT_REPLACE_OFFSET1 action (above) + // Second: do additional code fixup + { + int srcdel = DoSpecialFixup(c, &src, srclimit, &dst, dstlimit); + if (offsetmap != NULL) { + if (srcdel != 0) { + offsetmap->Copy(src - copystart - srcdel); + offsetmap->Delete(srcdel); + copystart = src; + } + } + } + goto Do_state_table; + + case kExitIllegalStructure: // structurally illegal byte; quit + case kExitReject: // NUL or illegal code encountered; quit + case kExitRejectAlt: // Apply replacement, then exit + default: // and all other exits + break; + } // End switch (e) + + // Exit possibilities: + // Some other exit code, state0, back up one byte exactly + // Some other exit code, !state0, back up over last char + + // Back up over exactly one byte of rejected/illegal UTF-8 character + src--; + dst--; + // Back up more if needed + if (!InStateZero(st, Tbl)) { + do {src--;dst--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80)); + } + } else if (!InStateZero(st, Tbl)) { + // src >= srclimit, !state0 + // Back up over truncated UTF-8 character + e = kExitIllegalStructure; + do {src--; dst--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80)); + } else { + // src >= srclimit, state0 + // Normal termination, source fully consumed + e = kExitOK; + } + + if (offsetmap != NULL) { + if (src > copystart) { + offsetmap->Copy(src - copystart); + copystart = src; + } + } + + // Possible return values here: + // kExitDstSpaceFull caller may realloc and retry from middle + // kExitIllegalStructure caller my overwrite/truncate + // kExitOK all done and happy + // kExitReject caller may overwrite/truncate + // kExitDoAgain LOOP NOT DONE; caller must retry from middle + // (may do fast ASCII loop first) + // kExitPlaceholder -unused- + // kExitNone -unused- + *bytes_consumed = src - isrc; + *bytes_filled = dst - odst; + *chars_changed = total_changed; + return e; +} + +// TwoByte versions are needed for tables > 240 states, such +// as the table for full Unicode 4.1 canonical + compatibility mapping + +// Scan a UTF-8 stringpiece based on state table with two-byte entries, +// copying to output stringpiece +// and doing text replacements. +// DO NOT CALL DIRECTLY. Use UTF8GenericReplace() below +// Needs caller to loop on kExitDoAgain +static int UTF8GenericReplaceInternalTwoByte(const UTF8ReplaceObj_2* st, + const StringPiece& istr, + StringPiece& ostr, + bool is_plain_text, + int* bytes_consumed, + int* bytes_filled, + int* chars_changed, + OffsetMap* offsetmap) { + int eshift = st->entry_shift; + int nEntries = (1 << eshift); // 64 or 256 entries per state + const uint8* isrc = reinterpret_cast<const uint8*>(istr.data()); + const int ilen = istr.length(); + const uint8* copystart = isrc; + const uint8* src = isrc; + const uint8* srclimit = src + ilen; + *bytes_consumed = 0; + *bytes_filled = 0; + *chars_changed = 0; + + const uint8* odst = reinterpret_cast<const uint8*>(ostr.data()); + const int olen = ostr.length(); + uint8* dst = const_cast<uint8*>(odst); + uint8* dstlimit = dst + olen; + + *chars_changed = 0; + + int total_changed = 0; + + int src_lll = srclimit - src; + int dst_lll = dstlimit - dst; + + + // Invariant condition during replacements: + // remaining dst size >= remaining src size + if ((dstlimit - dst) < (srclimit - src)) { + if (offsetmap != NULL) { + offsetmap->Copy(src - copystart); + copystart = src; + } + return kExitDstSpaceFull_2; + } + const unsigned short* Tbl_0 = &st->state_table[st->state0]; + + Do_state_table_2: + // Do state-table scan, copying as we go + const unsigned short* Tbl = Tbl_0; + int e = 0; + uint8 c = 0; + + Do_state_table_newe_2: + + //---------------------------- + while (src < srclimit) { + c = *src; + e = Tbl[c]; + *dst = c; + src++; + dst++; + if (e >= kExitIllegalStructure_2) {break;} + Tbl = &Tbl_0[e << eshift]; + } + //---------------------------- + src_lll = src - isrc; + dst_lll = dst - odst; + + // Exit possibilities: + // Replacement code, do the replacement and loop + // Some other exit code, state0, back up one byte exactly + // Some other exit code, !state0, back up over last char + // source consumed, state0, exit OK + // source consumed, !state0, back up over partial char + // For illegal byte in state0, avoid backup up over PREVIOUS char + // For truncated last char, back up to beginning of it + + if (e >= kExitIllegalStructure_2) { + // Switch on exit code; most loop back to top + int offset = 0; + switch (e) { + // These all make the output string the same size or shorter + // No checking needed + case kExitReplace31_2: // del 2, add 1 bytes to change + dst -= 2; + if (offsetmap != NULL) { + offsetmap->Copy(src - copystart - 2); + offsetmap->Delete(2); + copystart = src; + } + dst[-1] = (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff); + total_changed++; + goto Do_state_table_2; + case kExitReplace32_2: // del 3, add 2 bytes to change + dst--; + if (offsetmap != NULL) { + offsetmap->Copy(src - copystart - 1); + offsetmap->Delete(1); + copystart = src; + } + dst[-2] = (unsigned char)(Tbl[c + (nEntries * 1)] >> 8 & 0xff); + dst[-1] = (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff); + total_changed++; + goto Do_state_table_2; + case kExitReplace21_2: // del 2, add 1 bytes to change + dst--; + if (offsetmap != NULL) { + offsetmap->Copy(src - copystart - 1); + offsetmap->Delete(1); + copystart = src; + } + dst[-1] = (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff); + total_changed++; + goto Do_state_table_2; + case kExitReplace3_2: // update 3 bytes to change + dst[-3] = (unsigned char)(Tbl[c + (nEntries * 2)] & 0xff); + // Fall into next case + case kExitReplace2_2: // update 2 bytes to change + dst[-2] = (unsigned char)(Tbl[c + (nEntries * 1)] >> 8 & 0xff); + // Fall into next case + case kExitReplace1_2: // update 1 byte to change + dst[-1] = (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff); + total_changed++; + goto Do_state_table_2; + case kExitReplace1S0_2: // update 1 byte to change, 256-entry state + dst[-1] = (unsigned char)(Tbl[c + (256 * 1)] & 0xff); + total_changed++; + goto Do_state_table_2; + // These can make the output string longer than the input + case kExitReplaceOffset2_2: + if ((nEntries != 256) && InStateZero_2(st, Tbl)) { + // For space-optimized table, we need multiples of 256 bytes + // in state0 and multiples of nEntries in other states + offset += ((unsigned char)(Tbl[c + (256 * 1)] >> 8 & 0xff) << 8); + } else { + offset += ((unsigned char)(Tbl[c + (nEntries * 1)] >> 8 & 0xff) << 8); + } + // Fall into next case + case kExitReplaceOffset1_2: + if ((nEntries != 256) && InStateZero_2(st, Tbl)) { + // For space-optimized table, we need multiples of 256 bytes + // in state0 and multiples of nEntries in other states + offset += (unsigned char)(Tbl[c + (256 * 1)] & 0xff); + } else { + offset += (unsigned char)(Tbl[c + (nEntries * 1)] & 0xff); + } + { + const RemapEntry* re = &st->remap_base[offset]; + int del_len = re->delete_bytes & ~kReplaceAndResumeFlag; + int add_len = re->add_bytes & ~kHtmlPlaintextFlag; + // Special-case non-HTML replacement of five sensitive entities + // " & ' < > + // 0022 0026 0027 003c 003e + // A replacement creating one of these is expressed as a pair of + // entries, one for HTML output and one for plaintext output. + // The first of the pair has the high bit of add_bytes set. + if (re->add_bytes & kHtmlPlaintextFlag) { + // Use this entry for plain text + if (!is_plain_text) { + // Use very next entry for HTML text (same back/delete length) + re = &st->remap_base[offset + 1]; + add_len = re->add_bytes & ~kHtmlPlaintextFlag; + } + } + + // After the replacement, need (dstlimit - dst) >= (srclimit - src) + int string_offset = re->bytes_offset; + // After the replacement, need (dstlimit - newdst) >= (srclimit - src) + uint8* newdst = dst - del_len + add_len; + if ((dstlimit - newdst) < (srclimit - src)) { + // Won't fit; don't do the replacement. Caller may realloc and retry + e = kExitDstSpaceFull_2; + break; // exit, backing up over this char for later retry + } + dst -= del_len; + memcpy(dst, &st->remap_string[string_offset], add_len); + dst += add_len; + if (offsetmap != NULL) { + if (add_len > del_len) { + offsetmap->Copy(src - copystart); + offsetmap->Insert(add_len - del_len); + copystart = src; + } else if (add_len < del_len) { + offsetmap->Copy(src - copystart + add_len - del_len); + offsetmap->Delete(del_len - add_len); + copystart = src; + } + } + if (re->delete_bytes & kReplaceAndResumeFlag) { + // There is a two-byte non-zero target state at the end of the + // replacement string + uint8 c1 = st->remap_string[string_offset + add_len]; + uint8 c2 = st->remap_string[string_offset + add_len + 1]; + e = (c1 << 8) | c2; + Tbl = &Tbl_0[e << eshift]; + total_changed++; + goto Do_state_table_newe_2; + } + } + total_changed++; + if (e == kExitRejectAlt_2) {break;} + goto Do_state_table_2; + + case kExitSpecial_2: // NO special fixups [read: hacks] + case kExitIllegalStructure_2: // structurally illegal byte; quit + case kExitReject_2: // NUL or illegal code encountered; quit + // and all other exits + default: + break; + } // End switch (e) + + // Exit possibilities: + // Some other exit code, state0, back up one byte exactly + // Some other exit code, !state0, back up over last char + + // Back up over exactly one byte of rejected/illegal UTF-8 character + src--; + dst--; + // Back up more if needed + if (!InStateZero_2(st, Tbl)) { + do {src--;dst--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80)); + } + } else if (!InStateZero_2(st, Tbl)) { + // src >= srclimit, !state0 + // Back up over truncated UTF-8 character + e = kExitIllegalStructure_2; + + do {src--; dst--;} while ((src > isrc) && ((src[0] & 0xc0) == 0x80)); + } else { + // src >= srclimit, state0 + // Normal termination, source fully consumed + e = kExitOK_2; + } + + if (offsetmap != NULL) { + if (src > copystart) { + offsetmap->Copy(src - copystart); + copystart = src; + } + } + + + // Possible return values here: + // kExitDstSpaceFull_2 caller may realloc and retry from middle + // kExitIllegalStructure_2 caller my overwrite/truncate + // kExitOK_2 all done and happy + // kExitReject_2 caller may overwrite/truncate + // kExitDoAgain_2 LOOP NOT DONE; caller must retry from middle + // (may do fast ASCII loop first) + // kExitPlaceholder_2 -unused- + // kExitNone_2 -unused- + *bytes_consumed = src - isrc; + *bytes_filled = dst - odst; + *chars_changed = total_changed; + return e; +} + + +// Scan a UTF-8 stringpiece based on state table, copying to output stringpiece +// and doing text replacements. +// Also writes an optional OffsetMap. Pass NULL to skip writing one. +// Always scan complete UTF-8 characters +// Set number of bytes consumed from input, number filled to output. +// Return reason for exiting +int UTF8GenericReplace(const UTF8ReplaceObj* st, + const StringPiece& istr, + StringPiece& ostr, + bool is_plain_text, + int* bytes_consumed, + int* bytes_filled, + int* chars_changed, + OffsetMap* offsetmap) { + StringPiece local_istr(istr.data(), istr.length()); + StringPiece local_ostr(ostr.data(), ostr.length()); + int total_consumed = 0; + int total_filled = 0; + int total_changed = 0; + int local_bytes_consumed, local_bytes_filled, local_chars_changed; + int e; + do { + e = UTF8GenericReplaceInternal(st, + local_istr, local_ostr, is_plain_text, + &local_bytes_consumed, &local_bytes_filled, + &local_chars_changed, + offsetmap); + local_istr.remove_prefix(local_bytes_consumed); + local_ostr.remove_prefix(local_bytes_filled); + total_consumed += local_bytes_consumed; + total_filled += local_bytes_filled; + total_changed += local_chars_changed; + } while ( e == kExitDoAgain ); + *bytes_consumed = total_consumed; + *bytes_filled = total_filled; + *chars_changed = total_changed; + return e; +} + +// Older version without offsetmap +int UTF8GenericReplace(const UTF8ReplaceObj* st, + const StringPiece& istr, + StringPiece& ostr, + bool is_plain_text, + int* bytes_consumed, + int* bytes_filled, + int* chars_changed) { + return UTF8GenericReplace(st, + istr, + ostr, + is_plain_text, + bytes_consumed, + bytes_filled, + chars_changed, + NULL); +} + +// Older version without is_plain_text or offsetmap +int UTF8GenericReplace(const UTF8ReplaceObj* st, + const StringPiece& istr, + StringPiece& ostr, + int* bytes_consumed, + int* bytes_filled, + int* chars_changed) { + bool is_plain_text = false; + return UTF8GenericReplace(st, + istr, + ostr, + is_plain_text, + bytes_consumed, + bytes_filled, + chars_changed, + NULL); +} + +// Scan a UTF-8 stringpiece based on state table with two-byte entries, +// copying to output stringpiece +// and doing text replacements. +// Also writes an optional OffsetMap. Pass NULL to skip writing one. +// Always scan complete UTF-8 characters +// Set number of bytes consumed from input, number filled to output. +// Return reason for exiting +int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st, + const StringPiece& istr, + StringPiece& ostr, + bool is_plain_text, + int* bytes_consumed, + int* bytes_filled, + int* chars_changed, + OffsetMap* offsetmap) { + StringPiece local_istr(istr.data(), istr.length()); + StringPiece local_ostr(ostr.data(), ostr.length()); + int total_consumed = 0; + int total_filled = 0; + int total_changed = 0; + int local_bytes_consumed, local_bytes_filled, local_chars_changed; + int e; + do { + e = UTF8GenericReplaceInternalTwoByte(st, + local_istr, local_ostr, is_plain_text, + &local_bytes_consumed, + &local_bytes_filled, + &local_chars_changed, + offsetmap); + local_istr.remove_prefix(local_bytes_consumed); + local_ostr.remove_prefix(local_bytes_filled); + total_consumed += local_bytes_consumed; + total_filled += local_bytes_filled; + total_changed += local_chars_changed; + } while ( e == kExitDoAgain_2 ); + *bytes_consumed = total_consumed; + *bytes_filled = total_filled; + *chars_changed = total_changed; + + return e - kExitOK_2 + kExitOK; +} + +// Older version without offsetmap +int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st, + const StringPiece& istr, + StringPiece& ostr, + bool is_plain_text, + int* bytes_consumed, + int* bytes_filled, + int* chars_changed) { + return UTF8GenericReplaceTwoByte(st, + istr, + ostr, + is_plain_text, + bytes_consumed, + bytes_filled, + chars_changed, + NULL); +} + +// Older version without is_plain_text or offsetmap +int UTF8GenericReplaceTwoByte(const UTF8ReplaceObj_2* st, + const StringPiece& istr, + StringPiece& ostr, + int* bytes_consumed, + int* bytes_filled, + int* chars_changed) { + bool is_plain_text = false; + return UTF8GenericReplaceTwoByte(st, + istr, + ostr, + is_plain_text, + bytes_consumed, + bytes_filled, + chars_changed, + NULL); +} + + + +// Adjust a stringpiece to encompass complete UTF-8 characters. +// The data pointer will be increased by 0..3 bytes to get to a character +// boundary, and the length will then be decreased by 0..3 bytes +// to encompass the last complete character. +void UTF8TrimToChars(StringPiece* istr) { + const char* src = istr->data(); + int len = istr->length(); + // Exit if empty string + if (len == 0) { + return; + } + + // Exit on simple, common case + if ( ((src[0] & 0xc0) != 0x80) && + (static_cast<signed char>(src[len - 1]) >= 0) ) { + // First byte is not a continuation and last byte is 7-bit ASCII -- done + return; + } + + // Adjust the back end, len > 0 + const char* srclimit = src + len; + // Backscan over any ending continuation bytes to find last char start + const char* s = srclimit - 1; // Last byte of the string + while ((src <= s) && ((*s & 0xc0) == 0x80)) { + s--; + } + // Include entire last char if it fits + if (src <= s) { + int last_char_len = UTF8OneCharLen(s); + if (s + last_char_len <= srclimit) { + // Last char fits, so include it, else exclude it + s += last_char_len; + } + } + if (s != srclimit) { + // s is one byte beyond the last full character, if any + istr->remove_suffix(srclimit - s); + // Exit if now empty string + if (istr->length() == 0) { + return; + } + } + + // Adjust the front end, len > 0 + len = istr->length(); + srclimit = src + len; + s = src; // First byte of the string + // Scan over any beginning continuation bytes to find first char start + while ((s < srclimit) && ((*s & 0xc0) == 0x80)) { + s++; + } + if (s != src) { + // s is at the first full character, if any + istr->remove_prefix(s - src); + } +} + +} // End namespace CLD2 |