// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #library("utf16"); #import("unicode_core.dart"); #import("unicode.dart"); /** * Decodes the UTF-16 bytes as an iterable. Thus, the consumer can only convert * as much of the input as needed. Determines the byte order from the BOM, * or uses big-endian as a default. This method always strips a leading BOM. * Set the [replacementCodepoint] to null to throw an IllegalArgumentException * rather than replace the bad value. The default value for * [replacementCodepoint] is U+FFFD. */ IterableUtf16Decoder decodeUtf16AsIterable(List bytes, [int offset = 0, int length, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) { return new IterableUtf16Decoder._( () => new Utf16BytesToCodeUnitsDecoder(bytes, offset, length, replacementCodepoint), replacementCodepoint); } /** * Decodes the UTF-16BE bytes as an iterable. Thus, the consumer can only * convert as much of the input as needed. This method strips a leading BOM by * default, but can be overridden by setting the optional parameter [stripBom] * to false. Set the [replacementCodepoint] to null to throw an * IllegalArgumentException rather than replace the bad value. The default * value for the [replacementCodepoint] is U+FFFD. */ IterableUtf16Decoder decodeUtf16beAsIterable(List bytes, [int offset = 0, int length, bool stripBom = true, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) { return new IterableUtf16Decoder._( () => new Utf16beBytesToCodeUnitsDecoder(bytes, offset, length, stripBom, replacementCodepoint), replacementCodepoint); } /** * Decodes the UTF-16LE bytes as an iterable. Thus, the consumer can only * convert as much of the input as needed. This method strips a leading BOM by * default, but can be overridden by setting the optional parameter [stripBom] * to false. Set the [replacementCodepoint] to null to throw an * IllegalArgumentException rather than replace the bad value. The default * value for the [replacementCodepoint] is U+FFFD. */ IterableUtf16Decoder decodeUtf16leAsIterable(List bytes, [int offset = 0, int length, bool stripBom = true, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) { return new IterableUtf16Decoder._( () => new Utf16leBytesToCodeUnitsDecoder(bytes, offset, length, stripBom, replacementCodepoint), replacementCodepoint); } /** * Produce a String from a sequence of UTF-16 encoded bytes. This method always * strips a leading BOM. Set the [replacementCodepoint] to null to throw an * IllegalArgumentException rather than replace the bad value. The default * value for the [replacementCodepoint] is U+FFFD. */ String decodeUtf16(List bytes, [int offset = 0, int length, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) { Utf16BytesToCodeUnitsDecoder decoder = new Utf16BytesToCodeUnitsDecoder(bytes, offset, length, replacementCodepoint); List codeunits = decoder.decodeRest(); // TODO is16BitCodeUnit() is used to work around a bug with frog/dartc // (http://code.google.com/p/dart/issues/detail?id=1357). Consider // removing after this issue is resolved. if (is16BitCodeUnit()) { return new String.fromCharCodes(codeunits); } else { return new String.fromCharCodes( utf16CodeUnitsToCodepoints(codeunits, 0, null, replacementCodepoint)); } } /** * Produce a String from a sequence of UTF-16BE encoded bytes. This method * strips a leading BOM by default, but can be overridden by setting the * optional parameter [stripBom] to false. Set the [replacementCodepoint] to * null to throw an IllegalArgumentException rather than replace the bad value. * The default value for the [replacementCodepoint] is U+FFFD. */ String decodeUtf16be(List bytes, [int offset = 0, int length, bool stripBom = true, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) { List codeunits = (new Utf16beBytesToCodeUnitsDecoder(bytes, offset, length, stripBom, replacementCodepoint)).decodeRest(); // TODO is16BitCodeUnit() is used to work around a bug with frog/dartc // (http://code.google.com/p/dart/issues/detail?id=1357). Consider // removing after this issue is resolved. if (is16BitCodeUnit()) { return new String.fromCharCodes(codeunits); } else { return new String.fromCharCodes( utf16CodeUnitsToCodepoints(codeunits, 0, null, replacementCodepoint)); } } /** * Produce a String from a sequence of UTF-16LE encoded bytes. This method * strips a leading BOM by default, but can be overridden by setting the * optional parameter [stripBom] to false. Set the [replacementCodepoint] to * null to throw an IllegalArgumentException rather than replace the bad value. * The default value for the [replacementCodepoint] is U+FFFD. */ String decodeUtf16le(List bytes, [int offset = 0, int length, bool stripBom = true, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) { List codeunits = (new Utf16leBytesToCodeUnitsDecoder(bytes, offset, length, stripBom, replacementCodepoint)).decodeRest(); // TODO is16BitCodeUnit() is used to work around a bug with frog/dartc // (http://code.google.com/p/dart/issues/detail?id=1357). Consider // removing after this issue is resolved. if (is16BitCodeUnit()) { return new String.fromCharCodes(codeunits); } else { return new String.fromCharCodes( utf16CodeUnitsToCodepoints(codeunits, 0, null, replacementCodepoint)); } } /** * Produce a list of UTF-16 encoded bytes. This method prefixes the resulting * bytes with a big-endian byte-order-marker. */ List encodeUtf16(String str) => encodeUtf16be(str, true); /** * Produce a list of UTF-16BE encoded bytes. By default, this method produces * UTF-16BE bytes with no BOM. */ List encodeUtf16be(String str, [bool writeBOM = false]) { List utf16CodeUnits = _stringToUtf16CodeUnits(str); List encoding = new List(2 * utf16CodeUnits.length + (writeBOM ? 2 : 0)); int i = 0; if (writeBOM) { encoding[i++] = UNICODE_UTF_BOM_HI; encoding[i++] = UNICODE_UTF_BOM_LO; } for (int unit in utf16CodeUnits) { encoding[i++] = (unit & UNICODE_BYTE_ONE_MASK) >> 8; encoding[i++] = unit & UNICODE_BYTE_ZERO_MASK; } return encoding; } /** * Produce a list of UTF-16LE encoded bytes. By default, this method produces * UTF-16LE bytes with no BOM. */ List encodeUtf16le(String str, [bool writeBOM = false]) { List utf16CodeUnits = _stringToUtf16CodeUnits(str); List encoding = new List(2 * utf16CodeUnits.length + (writeBOM ? 2 : 0)); int i = 0; if (writeBOM) { encoding[i++] = UNICODE_UTF_BOM_LO; encoding[i++] = UNICODE_UTF_BOM_HI; } for (int unit in utf16CodeUnits) { encoding[i++] = unit & UNICODE_BYTE_ZERO_MASK; encoding[i++] = (unit & UNICODE_BYTE_ONE_MASK) >> 8; } return encoding; } /** * Identifies whether a List of bytes starts (based on offset) with a * byte-order marker (BOM). */ bool hasUtf16Bom(List utf32EncodedBytes, [int offset = 0, int length]) { return hasUtf16beBom(utf32EncodedBytes, offset, length) || hasUtf16leBom(utf32EncodedBytes, offset, length); } /** * Identifies whether a List of bytes starts (based on offset) with a * big-endian byte-order marker (BOM). */ bool hasUtf16beBom(List utf16EncodedBytes, [int offset = 0, int length]) { int end = length != null ? offset + length : utf16EncodedBytes.length; return (offset + 2) <= end && utf16EncodedBytes[offset] == UNICODE_UTF_BOM_HI && utf16EncodedBytes[offset + 1] == UNICODE_UTF_BOM_LO; } /** * Identifies whether a List of bytes starts (based on offset) with a * little-endian byte-order marker (BOM). */ bool hasUtf16leBom(List utf16EncodedBytes, [int offset = 0, int length]) { int end = length != null ? offset + length : utf16EncodedBytes.length; return (offset + 2) <= end && utf16EncodedBytes[offset] == UNICODE_UTF_BOM_LO && utf16EncodedBytes[offset + 1] == UNICODE_UTF_BOM_HI; } List _stringToUtf16CodeUnits(String str) { // TODO is16BitCodeUnit() is used to work around a bug with frog/dartc // (http://code.google.com/p/dart/issues/detail?id=1357). Consider // removing after this issue is resolved. if (is16BitCodeUnit()) { return str.charCodes(); } else { return codepointsToUtf16CodeUnits(str.charCodes()); } } /** * Return type of [decodeUtf16AsIterable] and variants. The Iterable type * provides an iterator on demand and the iterator will only translate bytes * as requested by the user of the iterator. (Note: results are not cached.) */ class IterableUtf16Decoder implements Iterable { final Function codeunitsProvider; final int replacementCodepoint; IterableUtf16Decoder._(ListRangeIterator this.codeunitsProvider(), int this.replacementCodepoint); Utf16CodeUnitDecoder iterator() => new Utf16CodeUnitDecoder.fromListRangeIterator(codeunitsProvider(), replacementCodepoint); } /** * Convert UTF-16 encoded bytes to UTF-16 code units by grouping 1-2 bytes * to produce the code unit (0-(2^16)-1). Relies on BOM to determine * endian-ness, and defaults to BE. */ class Utf16BytesToCodeUnitsDecoder implements ListRangeIterator { final ListRangeIterator utf16EncodedBytesIterator; final int replacementCodepoint; Utf16BytesToCodeUnitsDecoder._fromListRangeIterator( ListRangeIterator this.utf16EncodedBytesIterator, int this.replacementCodepoint); factory Utf16BytesToCodeUnitsDecoder(List utf16EncodedBytes, [ int offset = 0, int length, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) { if (length == null) { length = utf16EncodedBytes.length - offset; } if (hasUtf16beBom(utf16EncodedBytes, offset, length)) { return new Utf16beBytesToCodeUnitsDecoder(utf16EncodedBytes, offset + 2, length - 2, false, replacementCodepoint); } else if (hasUtf16leBom(utf16EncodedBytes, offset, length)) { return new Utf16leBytesToCodeUnitsDecoder(utf16EncodedBytes, offset + 2, length - 2, false, replacementCodepoint); } else { return new Utf16beBytesToCodeUnitsDecoder(utf16EncodedBytes, offset, length, false, replacementCodepoint); } } /** * Provides a fast way to decode the rest of the source bytes in a single * call. This method trades memory for improved speed in that it potentially * over-allocates the List containing results. */ List decodeRest() { List codeunits = new List(remaining); int i = 0; while (hasNext()) { codeunits[i++] = next(); } if (i == codeunits.length) { return codeunits; } else { List truncCodeunits = new List(i); truncCodeunits.setRange(0, i, codeunits); return truncCodeunits; } } bool hasNext() => utf16EncodedBytesIterator.hasNext(); int next() { if (utf16EncodedBytesIterator.remaining < 2) { utf16EncodedBytesIterator.next(); if (replacementCodepoint != null) { return replacementCodepoint; } else { throw new IllegalArgumentException( "Invalid UTF16 at ${utf16EncodedBytesIterator.position}"); } } else { return decode(); } } int get position() => utf16EncodedBytesIterator.position ~/ 2; void backup([int by = 1]) { utf16EncodedBytesIterator.backup(2 * by); } int get remaining() => (utf16EncodedBytesIterator.remaining + 1) ~/ 2; void skip([int count = 1]) { utf16EncodedBytesIterator.skip(2 * count); } abstract int decode(); } /** * Convert UTF-16BE encoded bytes to utf16 code units by grouping 1-2 bytes * to produce the code unit (0-(2^16)-1). */ class Utf16beBytesToCodeUnitsDecoder extends Utf16BytesToCodeUnitsDecoder { Utf16beBytesToCodeUnitsDecoder(List utf16EncodedBytes, [ int offset = 0, int length, bool stripBom = true, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) : super._fromListRangeIterator((new ListRange(utf16EncodedBytes, offset, length)).iterator(), replacementCodepoint) { if (stripBom && hasUtf16beBom(utf16EncodedBytes, offset, length)) { skip(); } } int decode() { int hi = utf16EncodedBytesIterator.next(); int lo = utf16EncodedBytesIterator.next(); return (hi << 8) + lo; } } /** * Convert UTF-16LE encoded bytes to utf16 code units by grouping 1-2 bytes * to produce the code unit (0-(2^16)-1). */ class Utf16leBytesToCodeUnitsDecoder extends Utf16BytesToCodeUnitsDecoder { Utf16leBytesToCodeUnitsDecoder(List utf16EncodedBytes, [ int offset = 0, int length, bool stripBom = true, int replacementCodepoint = UNICODE_REPLACEMENT_CHARACTER_CODEPOINT]) : super._fromListRangeIterator((new ListRange(utf16EncodedBytes, offset, length)).iterator(), replacementCodepoint) { if (stripBom && hasUtf16leBom(utf16EncodedBytes, offset, length)) { skip(); } } int decode() { int lo = utf16EncodedBytesIterator.next(); int hi = utf16EncodedBytesIterator.next(); return (hi << 8) + lo; } }