{
pascalx.io.charset — поддержка кодировок текста.
Copyright © 2021, 2026 Малик Разработчик
Это свободная программа: вы можете перераспространять её и/или изменять
её на условиях Меньшей Стандартной общественной лицензии GNU в том виде,
в каком она была опубликована Фондом свободного программного обеспечения;
либо версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЁННЫХ ЦЕЛЕЙ. Подробнее см. в Меньшей Стандартной
общественной лицензии GNU.
Вы должны были получить копию Меньшей Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см.
<https://www.gnu.org/licenses/>.
}
unit pascalx.io.charset;
{$MODE OBJFPC}
interface
{%region} uses
{$IFDEF WINDOWS}
windows,
{$ENDIF}
pascalx.lang,
pascalx.io,
pascalx.io.extension,
pascalx.io.bytearray;
{%endregion}
{$WARN 3018 OFF} { позволить конструкторы с любой видимостью }
{$TYPEINFO ON}
{$CALLING REGISTER}
const UNIT_NAME = 'pascalx.io.charset';
{%region} type
CharacterDecodingErrorHandler = interface;
CharacterEncodingErrorHandler = interface;
CharactersBuffer = class;
CharactersStorage = class;
CharDecoder = class;
CharEncoder = class;
Charset = class;
CharsetException = class;
CharacterDecodingException = class;
CharacterEncodingException = class;
RegisteredCharsetNameException = class;
UnsupportedCharsetNameException = class;
ErrorAction = (
eaIgnore,
eaReport,
eaReplace,
eaException
);
Charset_Array1d = specialize DynamicArray<Charset>;
Charset_Collection1d = specialize Collection<Charset>;
CharacterDecodingErrorHandler = interface(RawInterface) ['{9BEC84B9-E33B-4EA9-8CB3-83AD3B102358}']
procedure decodingError(decoder: CharDecoder);
end;
CharacterEncodingErrorHandler = interface(RawInterface) ['{9BEC84B9-E33B-4EA9-8CB3-83AD3B102359}']
procedure encodingError(encoder: CharEncoder);
end;
CharactersBuffer = class sealed(&Object, UCharWriter, Extendable)
private
fldLength: int;
fldContent: uchar_Array1d;
procedure checkBounds(beginIndex, endIndex: int);
function charAt(index: int): uchar;
function madeLarger(curLength, newLength: int): uchar_Array1d;
public
procedure close();
procedure write(ucharData: int);
procedure write(const src: uchar_Array1d);
procedure write(const src: uchar_Array1d; offset, length: int);
procedure flush();
procedure getChars(beginIndex, endIndex: int; const dst: uchar_Array1d; offset: int);
procedure copyInto(const dst: uchar_Array1d; offset: int);
procedure discard();
procedure discard(charsQuantity: int);
function toString(): AnsiString; override;
function getExtensions(): Extension_Array1d;
function getExtension(const typ: ShortString): Extension;
function toUCharArray(): uchar_Array1d;
property chars[index: int]: uchar read charAt; default;
property length: int read fldLength;
end;
CharactersStorage = class sealed(&Object, UCharReader, Extension, LimitedSizeExtension, Extendable)
private
fldPosition: int;
fldLength: int;
fldContent: uchar_Array1d;
public
constructor create(const content: UnicodeString);
constructor create(const content: uchar_Array1d);
constructor create(const content: uchar_Array1d; length: int);
constructor create(const content: uchar_Array1d; length, position: int);
procedure close();
function read(): int;
function read(const dst: uchar_Array1d): int;
function read(const dst: uchar_Array1d; offset, length: int): int;
function skip(ucharsQuantity: long): long;
function available(): long;
function getExtensions(): Extension_Array1d;
function getExtension(const typ: ShortString): Extension;
property length: int read fldLength;
property position: int read fldPosition;
end;
CharDecoder = class abstract(&Object)
private
class function toStream(src: ByteReader; length: int): ByteArrayInputStream; static;
private
fldGivedChars: long;
fldAcceptedBytes: long;
fldErrorAction: ErrorAction;
fldErrorHandler: CharacterDecodingErrorHandler;
fldCountersMonitor: Mutex;
fldCharset: Charset;
procedure setErrorAction(newErrorAction: ErrorAction);
protected
procedure decode(src: ByteReader; length: int; dst: UCharWriter); virtual; abstract;
procedure report();
public
constructor create(charset: Charset);
destructor destroy; override;
procedure reset();
procedure decodeToWriter(dst: UCharWriter; const src: byte_Array1d);
procedure decodeToWriter(dst: UCharWriter; const src: byte_Array1d; offset, length: int);
procedure decodeToWriter(dst: UCharWriter; src: ByteReader; length: int);
function givedChars(): long;
function acceptedBytes(): long;
function decode(const src: byte_Array1d): uchar_Array1d;
function decode(const src: byte_Array1d; offset, length: int): uchar_Array1d;
function decode(src: ByteReader; length: int): uchar_Array1d;
published
property charset: Charset read fldCharset;
property errorAction: ErrorAction read fldErrorAction write setErrorAction;
property errorHandler: CharacterDecodingErrorHandler read fldErrorHandler write fldErrorHandler;
end;
CharEncoder = class abstract(&Object)
private
class function toStream(src: UCharReader; length: int): CharactersStorage; static;
private
fldGivedBytes: long;
fldAcceptedChars: long;
fldErrorAction: ErrorAction;
fldErrorHandler: CharacterEncodingErrorHandler;
fldCountersMonitor: Mutex;
fldCharset: Charset;
procedure setErrorAction(newErrorAction: ErrorAction);
protected
procedure encode(src: UCharReader; length: int; dst: ByteWriter); virtual; abstract;
procedure report();
public
constructor create(charset: Charset);
destructor destroy; override;
procedure reset();
procedure encodeToWriter(dst: ByteWriter; const src: UnicodeString);
procedure encodeToWriter(dst: ByteWriter; const src: uchar_Array1d);
procedure encodeToWriter(dst: ByteWriter; const src: uchar_Array1d; offset, length: int);
procedure encodeToWriter(dst: ByteWriter; src: UCharReader; length: int);
function canEncode(src: uchar): boolean; virtual;
function canEncode(const src: UnicodeString): boolean;
function canEncode(const src: uchar_Array1d): boolean;
function canEncode(const src: uchar_Array1d; offset, length: int): boolean;
function givedBytes(): long;
function acceptedChars(): long;
function encode(const src: UnicodeString): byte_Array1d;
function encode(const src: uchar_Array1d): byte_Array1d;
function encode(const src: uchar_Array1d; offset, length: int): byte_Array1d;
function encode(src: UCharReader; length: int): byte_Array1d;
published
property charset: Charset read fldCharset;
property errorAction: ErrorAction read fldErrorAction write setErrorAction;
property errorHandler: CharacterEncodingErrorHandler read fldErrorHandler write fldErrorHandler;
end;
Charset = class abstract(&Object)
public
class procedure register(charsetRef: Charset); static;
class function isSupported(const charsetName: AnsiString): boolean; static;
class function enumerate(): Charset_Collection1d; static;
class function getDefault(): Charset; static;
class function getConsoleInput(): Charset; static;
class function getConsoleOutput(): Charset; static;
class function get(const charsetName: AnsiString): Charset; static;
private
fldName: AnsiString;
fldAliases: AnsiString_Array1d;
protected
constructor create(const name: AnsiString; const aliases: AnsiString_Array1d);
public
function newDecoder(): CharDecoder; virtual; abstract;
function newEncoder(): CharEncoder; virtual; abstract;
function contains(charset: Charset): boolean; virtual;
function displayName(): UnicodeString; virtual;
function equals(anot: TObject): boolean; override; final;
function getHashCode(): long; override; final;
function toString(): AnsiString; override; final;
function isRegistered(): boolean;
function isName(const charsetName: AnsiString): boolean;
function decode(const src: byte_Array1d): uchar_Array1d;
function decode(const src: byte_Array1d; offset, length: int): uchar_Array1d;
function encode(const src: uchar_Array1d): byte_Array1d;
function encode(const src: uchar_Array1d; offset, length: int): byte_Array1d;
function aliases(): AnsiString_Array1d;
published
property name: AnsiString read fldName;
end;
CharsetException = class(InvalidDataFormatException);
CharacterDecodingException = class(CharsetException);
CharacterEncodingException = class(CharsetException);
RegisteredCharsetNameException = class(Exception);
UnsupportedCharsetNameException = class(IOException);
{%endregion}
implementation
{%region} uses
pascalx.io.charset.unicode,
pascalx.lang.table;
{%endregion}
{$R *.res}
{$TYPEINFO OFF}
{$CALLING REGISTER}
{%region} type
CharsetCollection = class;
HashtableOfAnsiStringToCharset = specialize HashtableOfAnsiString<Charset>;
CharsetCollection = class sealed(RefCountObject, Charset_Collection1d)
private
fldLength: int;
fldCharsets: Charset_Array1d;
public
constructor create(const charsets: Charset_Array1d; length: int);
procedure copyInto(const dstArray; dstOffset: int);
function getLength(): int;
function componentAt(index: int): Charset;
function toArray(): Charset_Array1d;
end;
{%endregion}
{%region} var
charsetRegisteredCount: int;
charsetRegisteredArray: Charset_Array1d;
charsetRegisteredTable: HashtableOfAnsiStringToCharset;
charsetMonitor: Mutex;
charsetDefault: Charset;
{$IFDEF WINDOWS}
charsetConsoleInput: Charset;
charsetConsoleOutput: Charset;
{$ENDIF}
{%endregion}
{%region routines — charset}
procedure charsetRegisteredArrayFinalize();
var
index: int;
begin
for index := charsetRegisteredCount - 1 downto 0 do begin
charsetRegisteredArray[index].free();
end;
charsetRegisteredCount := 0;
charsetRegisteredArray := nil;
charsetRegisteredTable := nil;
charsetMonitor := nil;
charsetDefault := nil;
{$IFDEF WINDOWS}
charsetConsoleInput := nil;
charsetConsoleOutput := nil;
{$ENDIF}
end;
{%endregion}
{%region CharactersBuffer }
procedure CharactersBuffer.checkBounds(beginIndex, endIndex: int);
var
locLength: int;
begin
locLength := fldLength;
if ((beginIndex or endIndex) < 0) or (beginIndex > locLength) or (endIndex > locLength) or (beginIndex > endIndex) then begin
raise IndexOutOfBoundsException.create(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'out-of-bounds.index'));
end;
end;
function CharactersBuffer.charAt(index: int): uchar;
begin
if (index < 0) or (index >= fldLength) then begin
raise IndexOutOfBoundsException.create(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'out-of-bounds.index'));
end;
result := fldContent[index];
end;
function CharactersBuffer.madeLarger(curLength, newLength: int): uchar_Array1d;
var
curCapacity: int;
newCapacity: int;
curContent: uchar_Array1d;
newContent: uchar_Array1d;
begin
if newLength < 0 then begin
raise BufferTooLargeError.create(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, '!error.buffer-too-large'));
end;
newContent := fldContent;
curContent := newContent;
curCapacity := system.length(curContent);
if curCapacity <= 0 then begin
if newLength >= CoInt.MAX_VALUE - $3f then begin
newCapacity := CoInt.MAX_VALUE;
end else begin
newCapacity := newLength + $3f;
end;
newContent := &Array.newUChar1d(newCapacity);
fldContent := newContent;
end else
if newLength > curCapacity then begin
newCapacity := (curCapacity shl 1) or 1;
if newCapacity < 0 then newCapacity := CoInt.MAX_VALUE;
if newCapacity < newLength then begin
if newLength >= CoInt.MAX_VALUE - $3f then begin
newCapacity := CoInt.MAX_VALUE;
end else begin
newCapacity := newLength + $3f;
end;
end;
newContent := &Array.newUChar1d(newCapacity);
&Array.copyPrimitives(curContent, 0, newContent, 0, curLength);
fldContent := newContent;
end;
fldLength := newLength;
result := newContent;
end;
procedure CharactersBuffer.close();
begin
destroy;
end;
procedure CharactersBuffer.write(ucharData: int);
var
curLength: int;
newLength: int;
locContent: uchar_Array1d;
begin
curLength := fldLength;
newLength := curLength + 1;
locContent := madeLarger(curLength, newLength);
locContent[curLength] := uchar(ucharData);
end;
procedure CharactersBuffer.write(const src: uchar_Array1d);
begin
write(src, 0, system.length(src));
end;
procedure CharactersBuffer.write(const src: uchar_Array1d; offset, length: int);
var
curLength: int;
newLength: int;
locContent: uchar_Array1d;
begin
&Array.checkBounds(src, offset, length);
if length > 0 then begin
curLength := fldLength;
newLength := curLength + length;
locContent := madeLarger(curLength, newLength);
&Array.copyPrimitives(src, offset, locContent, curLength, length);
end;
end;
procedure CharactersBuffer.flush();
begin
end;
procedure CharactersBuffer.getChars(beginIndex, endIndex: int; const dst: uchar_Array1d; offset: int);
var
locLength: int;
begin
checkBounds(beginIndex, endIndex);
locLength := endIndex - beginIndex;
&Array.checkBounds(dst, offset, locLength);
&Array.copyPrimitives(fldContent, beginIndex, dst, offset, locLength);
end;
procedure CharactersBuffer.copyInto(const dst: uchar_Array1d; offset: int);
var
locLength: int;
begin
locLength := fldLength;
&Array.checkBounds(dst, offset, locLength);
&Array.copyPrimitives(fldContent, 0, dst, offset, locLength);
end;
procedure CharactersBuffer.discard();
begin
fldLength := 0;
end;
procedure CharactersBuffer.discard(charsQuantity: int);
var
locLength: int;
locContent: uchar_Array1d;
begin
if charsQuantity > 0 then begin
locLength := fldLength;
if charsQuantity >= locLength then begin
fldLength := 0;
exit;
end;
locContent := fldContent;
dec(locLength, charsQuantity);
&Array.copyPrimitives(locContent, charsQuantity, locContent, 0, locLength);
fldLength := locLength;
end;
end;
function CharactersBuffer.toString(): AnsiString;
begin
result := (UnicodeString.create(fldContent, 0, fldLength)).toUTF8();
end;
function CharactersBuffer.getExtensions(): Extension_Array1d;
begin
result := nil;
end;
function CharactersBuffer.getExtension(const typ: ShortString): Extension;
begin
result := nil;
end;
function CharactersBuffer.toUCharArray(): uchar_Array1d;
var
locLength: int;
locContent: uchar_Array1d;
begin
locLength := fldLength;
locContent := &Array.newUChar1d(locLength);
&Array.copyPrimitives(fldContent, 0, locContent, 0, locLength);
result := locContent;
end;
{%endregion}
{%region CharactersStorage }
constructor CharactersStorage.create(const content: UnicodeString);
begin
create(content.toUCharArray());
end;
constructor CharactersStorage.create(const content: uchar_Array1d);
begin
inherited create();
fldLength := system.length(content);
fldContent := content;
end;
constructor CharactersStorage.create(const content: uchar_Array1d; length: int);
var
alength: int;
begin
inherited create();
if length > 0 then begin
alength := system.length(content);
if length < alength then begin
fldLength := length;
end else begin
fldLength := alength;
end;
fldContent := content;
end;
end;
constructor CharactersStorage.create(const content: uchar_Array1d; length, position: int);
var
alength: int;
slength: int;
begin
inherited create();
if length > 0 then begin
alength := system.length(content);
if length < alength then begin
slength := length;
end else begin
slength := alength;
end;
fldLength := slength;
if position < 0 then begin
fldPosition := 0;
end else
if position < slength then begin
fldPosition := position;
end else begin
fldPosition := slength;
end;
fldContent := content;
end;
end;
procedure CharactersStorage.close();
begin
destroy;
end;
function CharactersStorage.read(): int;
var
locPosition: int;
begin
locPosition := fldPosition;
if locPosition >= fldLength then begin
result := -1;
exit;
end;
fldPosition := locPosition + 1;
result := int(fldContent[locPosition]);
end;
function CharactersStorage.read(const dst: uchar_Array1d): int;
begin
result := read(dst, 0, system.length(dst));
end;
function CharactersStorage.read(const dst: uchar_Array1d; offset, length: int): int;
var
locPosition: int;
locRemainder: int;
begin
&Array.checkBounds(dst, offset, length);
if length <= 0 then begin
result := 0;
exit;
end;
locPosition := fldPosition;
locRemainder := fldLength - locPosition;
if locRemainder <= 0 then begin
result := -1;
exit;
end;
if length > locRemainder then length := locRemainder;
&Array.copyPrimitives(fldContent, locPosition, dst, offset, length);
fldPosition := locPosition + length;
result := length;
end;
function CharactersStorage.skip(ucharsQuantity: long): long;
var
locLength: int;
locPosition: int;
locRemainder: int;
begin
if ucharsQuantity <= 0 then begin
result := 0;
exit;
end;
if ucharsQuantity < CoInt.MAX_VALUE then begin
locLength := int(ucharsQuantity);
end else begin
locLength := CoInt.MAX_VALUE;
end;
locPosition := fldPosition;
locRemainder := fldLength - locPosition;
if locLength > locRemainder then locLength := locRemainder;
fldPosition := locPosition + locLength;
result := locLength;
end;
function CharactersStorage.available(): long;
begin
result := fldLength - fldPosition;
end;
function CharactersStorage.getExtensions(): Extension_Array1d;
begin
result := [ self as Extension ];
end;
function CharactersStorage.getExtension(const typ: ShortString): Extension;
var
ext: Extension;
begin
ext := self;
if Lang.isInstance(ext, typ) then begin
result := Extension(Lang.cast(ext, typ));
exit;
end;
result := nil;
end;
{%endregion}
{%region CharDecoder }
class function CharDecoder.toStream(src: ByteReader; length: int): ByteArrayInputStream;
var
aarray: byte_Array1d;
begin
aarray := &Array.newByte1d(length);
length := src.read(aarray, 0, length);
result := ByteArrayInputStream.create(aarray, length);
end;
procedure CharDecoder.setErrorAction(newErrorAction: ErrorAction);
begin
if (newErrorAction < eaIgnore) or (newErrorAction > eaException) then newErrorAction := eaReplace;
fldErrorAction := newErrorAction;
end;
procedure CharDecoder.report();
var
handler: CharacterDecodingErrorHandler;
begin
handler := fldErrorHandler;
if handler <> nil then handler.decodingError(self);
end;
constructor CharDecoder.create(charset: Charset);
begin
inherited create();
fldErrorAction := eaReplace;
fldCountersMonitor := Mutex.create();
fldCharset := charset;
end;
destructor CharDecoder.destroy;
begin
fldCountersMonitor.free();
inherited destroy;
end;
procedure CharDecoder.reset();
var
cmon: Mutex;
begin
cmon := fldCountersMonitor;
cmon.beginSynchronized();
try
fldGivedChars := 0;
fldAcceptedBytes := 0;
finally
cmon.endSynchronized();
end;
end;
procedure CharDecoder.decodeToWriter(dst: UCharWriter; const src: byte_Array1d);
begin
if dst = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('dst') ]));
end;
dst.write(decode(src, 0, length(src)));
end;
procedure CharDecoder.decodeToWriter(dst: UCharWriter; const src: byte_Array1d; offset, length: int);
begin
if dst = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('dst') ]));
end;
dst.write(decode(src, offset, length));
end;
procedure CharDecoder.decodeToWriter(dst: UCharWriter; src: ByteReader; length: int);
begin
if dst = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('dst') ]));
end;
dst.write(decode(src, length));
end;
function CharDecoder.givedChars(): long;
begin
result := fldGivedChars;
end;
function CharDecoder.acceptedBytes(): long;
begin
result := fldAcceptedBytes;
end;
function CharDecoder.decode(const src: byte_Array1d): uchar_Array1d;
begin
result := decode(src, 0, length(src));
end;
function CharDecoder.decode(const src: byte_Array1d; offset, length: int): uchar_Array1d;
var
deltaGivedChars: long;
deltaAcceptedBytes: long;
buffer: CharactersBuffer;
stream: ByteArrayInputStream;
cmon: Mutex;
begin
&Array.checkBounds(src, offset, length);
if length <= 0 then begin
result := &Array.newUChar1d(0);
exit;
end;
buffer := CharactersBuffer.create();
try
stream := ByteArrayInputStream.create(src, offset + length, offset);
try
decode(stream, length, buffer);
deltaAcceptedBytes := stream.position() - offset;
finally
stream.free();
end;
deltaGivedChars := buffer.length;
cmon := fldCountersMonitor;
cmon.beginSynchronized();
try
inc(fldGivedChars, deltaGivedChars);
inc(fldAcceptedBytes, deltaAcceptedBytes);
finally
cmon.endSynchronized();
end;
result := buffer.toUCharArray();
finally
buffer.free();
end;
end;
function CharDecoder.decode(src: ByteReader; length: int): uchar_Array1d;
var
offset: long;
deltaGivedChars: long;
deltaAcceptedBytes: long;
seekable: SeekExtension;
buffer: CharactersBuffer;
stream: ByteArrayInputStream;
cmon: Mutex;
begin
if src = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('src') ]));
end;
if length <= 0 then begin
result := &Array.newUChar1d(0);
exit;
end;
buffer := CharactersBuffer.create();
try
if Lang.isInstance(src, ByteArrayInputStream) then begin
stream := ByteArrayInputStream(Lang.cast(src, ByteArrayInputStream));
offset := stream.position();
decode(src, length, buffer);
deltaAcceptedBytes := stream.position() - offset;
end else begin
seekable := SeekExtension(src.getExtension(SeekExtension));
if seekable <> nil then begin
offset := seekable.position();
decode(src, length, buffer);
deltaAcceptedBytes := seekable.position() - offset;
end else begin
stream := toStream(src, length);
try
decode(stream, length, buffer);
deltaAcceptedBytes := stream.position();
finally
stream.free();
end;
end;
end;
deltaGivedChars := buffer.length;
cmon := fldCountersMonitor;
cmon.beginSynchronized();
try
inc(fldGivedChars, deltaGivedChars);
inc(fldAcceptedBytes, deltaAcceptedBytes);
finally
cmon.endSynchronized();
end;
result := buffer.toUCharArray();
finally
buffer.free();
end;
end;
{%endregion}
{%region CharEncoder }
class function CharEncoder.toStream(src: UCharReader; length: int): CharactersStorage;
var
aarray: uchar_Array1d;
begin
aarray := &Array.newUChar1d(length);
length := src.read(aarray, 0, length);
result := CharactersStorage.create(aarray, length);
end;
procedure CharEncoder.setErrorAction(newErrorAction: ErrorAction);
begin
if (newErrorAction < eaIgnore) or (newErrorAction > eaException) then newErrorAction := eaReplace;
fldErrorAction := newErrorAction;
end;
procedure CharEncoder.report();
var
handler: CharacterEncodingErrorHandler;
begin
handler := fldErrorHandler;
if handler <> nil then handler.encodingError(self);
end;
constructor CharEncoder.create(charset: Charset);
begin
inherited create();
fldErrorAction := eaReplace;
fldCountersMonitor := Mutex.create();
fldCharset := charset;
end;
destructor CharEncoder.destroy;
begin
fldCountersMonitor.free();
inherited destroy;
end;
procedure CharEncoder.reset();
var
cmon: Mutex;
begin
cmon := fldCountersMonitor;
cmon.beginSynchronized();
try
fldGivedBytes := 0;
fldAcceptedChars := 0;
finally
cmon.endSynchronized();
end;
end;
procedure CharEncoder.encodeToWriter(dst: ByteWriter; const src: UnicodeString);
begin
if dst = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('dst') ]));
end;
dst.write(encode(src));
end;
procedure CharEncoder.encodeToWriter(dst: ByteWriter; const src: uchar_Array1d);
begin
if dst = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('dst') ]));
end;
dst.write(encode(src, 0, length(src)));
end;
procedure CharEncoder.encodeToWriter(dst: ByteWriter; const src: uchar_Array1d; offset, length: int);
begin
if dst = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('dst') ]));
end;
dst.write(encode(src, offset, length));
end;
procedure CharEncoder.encodeToWriter(dst: ByteWriter; src: UCharReader; length: int);
begin
if dst = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('dst') ]));
end;
dst.write(encode(src, length));
end;
function CharEncoder.canEncode(src: uchar): boolean;
begin
result := true;
end;
function CharEncoder.canEncode(const src: UnicodeString): boolean;
var
idx: int;
begin
for idx := 0 to length(src) - 1 do if not canEncode(src[idx + 1]) then begin
result := false;
exit;
end;
result := true;
end;
function CharEncoder.canEncode(const src: uchar_Array1d): boolean;
begin
result := canEncode(src, 0, length(src));
end;
function CharEncoder.canEncode(const src: uchar_Array1d; offset, length: int): boolean;
var
idx: int;
begin
&Array.checkBounds(src, offset, length);
for idx := offset to offset + length - 1 do if not canEncode(src[idx]) then begin
result := false;
exit;
end;
result := true;
end;
function CharEncoder.givedBytes(): long;
begin
result := fldGivedBytes;
end;
function CharEncoder.acceptedChars(): long;
begin
result := fldAcceptedChars;
end;
function CharEncoder.encode(const src: UnicodeString): byte_Array1d;
var
length: int;
deltaGivedBytes: long;
deltaAcceptedChars: long;
buffer: ByteArrayOutputStream;
stream: CharactersStorage;
cmon: Mutex;
begin
length := system.length(src);
if length <= 0 then begin
result := &Array.newByte1d(0);
exit;
end;
buffer := ByteArrayOutputStream.create();
try
stream := CharactersStorage.create(src);
try
encode(stream, length, buffer);
deltaAcceptedChars := stream.position;
finally
stream.free();
end;
deltaGivedBytes := buffer.position();
cmon := fldCountersMonitor;
cmon.beginSynchronized();
try
inc(fldGivedBytes, deltaGivedBytes);
inc(fldAcceptedChars, deltaAcceptedChars);
finally
cmon.endSynchronized();
end;
result := buffer.toByteArray();
finally
buffer.free();
end;
end;
function CharEncoder.encode(const src: uchar_Array1d): byte_Array1d;
begin
result := encode(src, 0, length(src));
end;
function CharEncoder.encode(const src: uchar_Array1d; offset, length: int): byte_Array1d;
var
deltaGivedBytes: long;
deltaAcceptedChars: long;
buffer: ByteArrayOutputStream;
stream: CharactersStorage;
cmon: Mutex;
begin
&Array.checkBounds(src, offset, length);
if length <= 0 then begin
result := &Array.newByte1d(0);
exit;
end;
buffer := ByteArrayOutputStream.create();
try
stream := CharactersStorage.create(src, offset + length, offset);
try
encode(stream, length, buffer);
deltaAcceptedChars := stream.position - offset;
finally
stream.free();
end;
deltaGivedBytes := buffer.position();
cmon := fldCountersMonitor;
cmon.beginSynchronized();
try
inc(fldGivedBytes, deltaGivedBytes);
inc(fldAcceptedChars, deltaAcceptedChars);
finally
cmon.endSynchronized();
end;
result := buffer.toByteArray();
finally
buffer.free();
end;
end;
function CharEncoder.encode(src: UCharReader; length: int): byte_Array1d;
var
offset: long;
deltaGivedBytes: long;
deltaAcceptedChars: long;
seekable: SeekExtension;
buffer: ByteArrayOutputStream;
stream: CharactersStorage;
cmon: Mutex;
begin
if src = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('src') ]));
end;
if length <= 0 then begin
result := &Array.newByte1d(0);
exit;
end;
buffer := ByteArrayOutputStream.create();
try
if Lang.isInstance(src, CharactersStorage) then begin
stream := CharactersStorage(Lang.cast(src, CharactersStorage));
offset := stream.position;
encode(src, length, buffer);
deltaAcceptedChars := stream.position - offset;
end else begin
seekable := SeekExtension(src.getExtension(SeekExtension));
if seekable <> nil then begin
offset := seekable.position();
encode(src, length, buffer);
deltaAcceptedChars := seekable.position() - offset;
end else begin
stream := toStream(src, length);
try
encode(stream, length, buffer);
deltaAcceptedChars := stream.position;
finally
stream.free();
end;
end;
end;
deltaGivedBytes := buffer.position();
cmon := fldCountersMonitor;
cmon.beginSynchronized();
try
inc(fldGivedBytes, deltaGivedBytes);
inc(fldAcceptedChars, deltaAcceptedChars);
finally
cmon.endSynchronized();
end;
result := buffer.toByteArray();
finally
buffer.free();
end;
end;
{%endregion}
{%region Charset }
class procedure Charset.register(charsetRef: Charset);
label
break_label0;
var
index: int;
count: int;
charsetName: AnsiString;
currentName: AnsiString;
currentAliases: AnsiString_Array1d;
charsetRegisteredData: Charset_Array1d;
charsetRegisteredCopy: Charset_Array1d;
begin
if charsetRef = nil then begin
raise NullPointerException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'null-pointer.argument'), [ CoAnsiString.create('charsetRef') ]));
end;
charsetName := charsetRef.fldName;
charsetMonitor.beginSynchronized();
try
currentName := charsetName;
if charsetRegisteredTable.contains(currentName) then begin
charsetName := currentName;
goto break_label0;
end;
currentAliases := charsetRef.fldAliases;
for index := length(currentAliases) - 1 downto 0 do begin
currentName := currentAliases[index];
if charsetRegisteredTable.contains(currentName) then begin
charsetName := currentName;
goto break_label0;
end;
end;
count := charsetRegisteredCount;
charsetRegisteredData := charsetRegisteredArray;
if count = CoInt.MAX_VALUE then begin
raise BufferTooLargeError.create(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, '!error.buffer-too-large'));
end;
if count = length(charsetRegisteredData) then begin
charsetRegisteredCopy := Charset_Array1d(&Array.newTObject1d((count shl 1) or 1));
&Array.copyObjects(charsetRegisteredData, 0, charsetRegisteredCopy, 0, count);
charsetRegisteredData := charsetRegisteredCopy;
charsetRegisteredArray := charsetRegisteredData;
end;
charsetRegisteredData[count] := charsetRef;
charsetRegisteredCount := count + 1;
charsetRegisteredTable[charsetName] := charsetRef;
for index := length(currentAliases) - 1 downto 0 do begin
charsetRegisteredTable[currentAliases[index]] := charsetRef;
end;
charsetName := '';
break_label0:
finally
charsetMonitor.endSynchronized();
end;
if length(charsetName) > 0 then begin
raise RegisteredCharsetNameException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.io.charset.UNIT_NAME, 'registered-charset-name'), [
CoAnsiString.create(charsetName)
]));
end;
end;
class function Charset.isSupported(const charsetName: AnsiString): boolean;
begin
charsetMonitor.beginSynchronized();
try
result := charsetRegisteredTable.contains(charsetName);
finally
charsetMonitor.endSynchronized();
end;
end;
class function Charset.enumerate(): Charset_Collection1d;
begin
result := CharsetCollection.create(charsetRegisteredArray, charsetRegisteredCount);
end;
class function Charset.getDefault(): Charset;
begin
result := charsetDefault;
end;
class function Charset.getConsoleInput(): Charset;
{$IFDEF WINDOWS}
var
charsetName: AnsiString;
{$ENDIF}
begin
{$IFDEF WINDOWS}
if charsetConsoleInput = nil then begin
charsetName := 'CP-' + CoInt.toString(int(windows.getConsoleCP()));
charsetMonitor.beginSynchronized();
try
if charsetConsoleInput = nil then begin
charsetConsoleInput := charsetRegisteredTable[charsetName];
end;
finally
charsetMonitor.endSynchronized();
end;
if charsetConsoleInput = nil then begin
raise UnsupportedCharsetNameException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.io.charset.UNIT_NAME, 'unsupported-charset-name'), [
CoAnsiString.create(charsetName)
]));
end;
end;
result := charsetConsoleInput;
{$ELSE}
result := charsetDefault;
{$ENDIF}
end;
class function Charset.getConsoleOutput(): Charset;
{$IFDEF WINDOWS}
var
charsetName: AnsiString;
{$ENDIF}
begin
{$IFDEF WINDOWS}
if charsetConsoleOutput = nil then begin
charsetName := 'CP-' + CoInt.toString(int(windows.getConsoleOutputCP()));
charsetMonitor.beginSynchronized();
try
if charsetConsoleOutput = nil then begin
charsetConsoleOutput := charsetRegisteredTable[charsetName];
end;
finally
charsetMonitor.endSynchronized();
end;
if charsetConsoleOutput = nil then begin
raise UnsupportedCharsetNameException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.io.charset.UNIT_NAME, 'unsupported-charset-name'), [
CoAnsiString.create(charsetName)
]));
end;
end;
result := charsetConsoleOutput;
{$ELSE}
result := charsetDefault;
{$ENDIF}
end;
class function Charset.get(const charsetName: AnsiString): Charset;
var
charsetFound: Charset;
begin
charsetMonitor.beginSynchronized();
try
charsetFound := charsetRegisteredTable[charsetName];
finally
charsetMonitor.endSynchronized();
end;
if charsetFound = nil then begin
raise UnsupportedCharsetNameException.create(AnsiString.format(AResource.readUnitResourceAsAnsiString(pascalx.io.charset.UNIT_NAME, 'unsupported-charset-name'), [
CoAnsiString.create(charsetName)
]));
end;
result := charsetFound;
end;
constructor Charset.create(const name: AnsiString; const aliases: AnsiString_Array1d);
label
continue_label0;
var
ali: int;
alj: int;
sourcesLength: int;
aliasesLength: int;
aliasInstance: AnsiString;
aliasesData: AnsiString_Array1d;
aliasesCopy: AnsiString_Array1d;
begin
inherited create();
aliasesLength := length(aliases);
aliasesData := &Array.newAnsiString1d(aliasesLength);
sourcesLength := aliasesLength;
aliasesLength := 0;
for ali := 0 to sourcesLength - 1 do begin
aliasInstance := aliases[ali];
if (length(aliasInstance) <= 0) or (aliasInstance = name) then goto continue_label0;
for alj := aliasesLength - 1 downto 0 do if aliasInstance = aliasesData[alj] then goto continue_label0;
aliasesData[aliasesLength] := aliasInstance;
inc(aliasesLength);
continue_label0:
end;
if aliasesLength <> length(aliasesData) then begin
aliasesCopy := &Array.newAnsiString1d(aliasesLength);
&Array.copyStrings(aliasesData, 0, aliasesCopy, 0, aliasesLength);
aliasesData := aliasesCopy;
end;
fldName := name;
fldAliases := aliasesData;
end;
function Charset.contains(charset: Charset): boolean;
begin
result := false;
end;
function Charset.displayName(): UnicodeString;
begin
result := fldName.toUTF16();
end;
function Charset.equals(anot: TObject): boolean;
begin
result := (anot = self) or (anot is Charset) and (Charset(anot).fldName = fldName);
end;
function Charset.getHashCode(): long;
begin
result := (CoAnsiString.create(fldName) as RefCountInterface).getHashCode();
end;
function Charset.toString(): AnsiString;
begin
result := 'charset ' + fldName;
end;
function Charset.isRegistered(): boolean;
begin
charsetMonitor.beginSynchronized();
try
result := charsetRegisteredTable[fldName] = self;
finally
charsetMonitor.endSynchronized();
end;
end;
function Charset.isName(const charsetName: AnsiString): boolean;
var
index: int;
locAliases: AnsiString_Array1d;
begin
if fldName = charsetName then begin
result := true;
exit;
end;
locAliases := fldAliases;
for index := length(locAliases) - 1 downto 0 do if locAliases[index] = charsetName then begin
result := true;
exit;
end;
result := false;
end;
function Charset.decode(const src: byte_Array1d): uchar_Array1d;
begin
try
with newDecoder() do try
result := decode(src);
finally
free();
end;
except
on e: RuntimeException do begin
raise;
end;
on e: Exception do begin
raise UnsupportedOperationException.create(e.message, e.helpContext);
end;
end;
end;
function Charset.decode(const src: byte_Array1d; offset, length: int): uchar_Array1d;
begin
try
with newDecoder() do try
result := decode(src, offset, length);
finally
free();
end;
except
on e: RuntimeException do begin
raise;
end;
on e: Exception do begin
raise UnsupportedOperationException.create(e.message, e.helpContext);
end;
end;
end;
function Charset.encode(const src: uchar_Array1d): byte_Array1d;
begin
try
with newEncoder() do try
result := encode(src);
finally
free();
end;
except
on e: RuntimeException do begin
raise;
end;
on e: Exception do begin
raise UnsupportedOperationException.create(e.message, e.helpContext);
end;
end;
end;
function Charset.encode(const src: uchar_Array1d; offset, length: int): byte_Array1d;
begin
try
with newEncoder() do try
result := encode(src, offset, length);
finally
free();
end;
except
on e: RuntimeException do begin
raise;
end;
on e: Exception do begin
raise UnsupportedOperationException.create(e.message, e.helpContext);
end;
end;
end;
function Charset.aliases(): AnsiString_Array1d;
var
length: int;
aliasesData: AnsiString_Array1d;
aliasesCopy: AnsiString_Array1d;
begin
aliasesData := fldAliases;
length := system.length(aliasesData);
aliasesCopy := &Array.newAnsiString1d(length);
&Array.copyStrings(aliasesData, 0, aliasesCopy, 0, length);
result := aliasesCopy;
end;
{%endregion}
{%region CharsetCollection }
constructor CharsetCollection.create(const charsets: Charset_Array1d; length: int);
begin
inherited create();
fldLength := length;
fldCharsets := charsets;
end;
procedure CharsetCollection.copyInto(const dstArray; dstOffset: int);
begin
&Array.copyObjects(fldCharsets, 0, dstArray, dstOffset, fldLength);
end;
function CharsetCollection.getLength(): int;
begin
result := fldLength;
end;
function CharsetCollection.componentAt(index: int): Charset;
begin
if (index < 0) or (index >= fldLength) then begin
raise ArrayIndexOutOfBoundsException.create(AResource.readUnitResourceAsAnsiString(pascalx.lang.UNIT_NAME, 'out-of-bounds.array-index'));
end;
result := fldCharsets[index];
end;
function CharsetCollection.toArray(): Charset_Array1d;
var
length: int;
charsetsData: Charset_Array1d;
charsetsCopy: Charset_Array1d;
begin
charsetsData := fldCharsets;
length := fldLength;
charsetsCopy := Charset_Array1d(&Array.newTObject1d(length));
&Array.copyObjects(charsetsData, 0, charsetsCopy, 0, length);
result := charsetsCopy;
end;
{%endregion}
{%region} initialization
Lang.registerInterfaces([
typeInfo(CharacterDecodingErrorHandler),
typeInfo(CharacterEncodingErrorHandler)
]);
charsetRegisteredArray := Charset_Array1d(&Array.newTObject1d($0f));
charsetRegisteredTable := HashtableOfAnsiStringToCharset.create();
charsetMonitor := Mutex.create();
charsetDefault := UTF8.create();
Charset.register(charsetDefault);
Charset.register(UTF16BE.create());
Charset.register(UTF16LE.create());
{%endregion}
{%region} finalization
charsetMonitor.free();
charsetRegisteredTable.free();
charsetRegisteredArrayFinalize();
{%endregion}
end.