delphimvcframework/lib/dmustache/mormot.core.text.pas

10326 lines
316 KiB
ObjectPascal
Raw Normal View History

2024-04-29 15:40:45 +02:00
/// Framework Core Low-Level Text Processing
// - this unit is a part of the Open Source Synopse mORMot framework 2,
// licensed under a MPL/GPL/LGPL three license - see LICENSE.md
unit mormot.core.text;
{
*****************************************************************************
Text Processing functions shared by all framework units
- CSV-like Iterations over Text Buffers
- TTextWriter parent class for Text Generation
- Numbers (integers or floats) and Variants to Text Conversion
- Text Formatting functions
- Resource and Time Functions
- ESynException class
- Hexadecimal Text And Binary Conversion
*****************************************************************************
}
interface
{$I mormot.defines.inc}
uses
classes,
contnrs,
types,
sysutils,
mormot.core.base,
mormot.core.os,
mormot.core.unicode;
{ ************ CSV-like Iterations over Text Buffers }
/// return true if IdemPChar(source,searchUp) matches, and retrieve the value item
// - typical use may be:
// ! if IdemPCharAndGetNextItem(P,
// ! 'CONTENT-DISPOSITION: FORM-DATA; NAME="',Name,'"') then ...
function IdemPCharAndGetNextItem(var source: PUtf8Char; const searchUp: RawUtf8;
var Item: RawUtf8; Sep: AnsiChar = #13): boolean;
/// return next CSV string from P
// - P=nil after call when end of text is reached
function GetNextItem(var P: PUtf8Char; Sep: AnsiChar = ','): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// return next CSV string from P
// - P=nil after call when end of text is reached
procedure GetNextItem(var P: PUtf8Char; Sep: AnsiChar;
var result: RawUtf8); overload;
/// return next CSV string (unquoted if needed) from P
// - P=nil after call when end of text is reached
procedure GetNextItem(var P: PUtf8Char; Sep, Quote: AnsiChar;
var result: RawUtf8); overload;
/// return next CSV string from P from several separator characters
// - P=nil after call when end of text is reached
// - returns the character which ended the result string, i.e. #0 or one of Sep
function GetNextItemMultiple(var P: PUtf8Char; const Sep: RawUtf8;
var Next: RawUtf8): AnsiChar; overload;
/// return trimmed next CSV string from P
// - P=nil after call when end of text is reached
procedure GetNextItemTrimed(var P: PUtf8Char; Sep: AnsiChar;
var result: RawUtf8);
/// return next CRLF separated value string from P, ending #10 or #13#10 trimmed
// - any kind of line feed (CRLF or LF) will be handled, on all operating systems
// - as used e.g. by TSynNameValue.InitFromCsv and TDocVariantData.InitCsv
// - P=nil after call when end of text is reached
procedure GetNextItemTrimedCRLF(var P: PUtf8Char; var result: RawUtf8);
/// return next CSV string from P, nil if no more
// - this function returns the RTL string type of the compiler, and
// therefore can be used with ready to be displayed text (e.g. for the UI)
function GetNextItemString(var P: PChar; Sep: Char = ','): string;
/// extract a file extension from a file name, then compare with a comma
// separated list of extensions
// - e.g. GetFileNameExtIndex('test.log','exe,log,map')=1
// - will return -1 if no file extension match
// - will return any matching extension, starting count at 0
// - extension match is case-insensitive
function GetFileNameExtIndex(const FileName, CsvExt: TFileName): integer;
/// return next CSV string from P, nil if no more
// - output text would be trimmed from any left or right space
// - will always append a trailing #0 - excluded from Dest length (0..254)
procedure GetNextItemShortString(var P: PUtf8Char; Dest: PShortString;
Sep: AnsiChar = ',');
/// append some text lines with the supplied Values[]
// - if any Values[] item is '', no line is added
// - otherwise, appends 'Caption: Value', with Caption taken from CSV
procedure AppendCsvValues(const Csv: string; const Values: array of string;
var result: string; const AppendBefore: string = #13#10);
/// return a CSV list of the iterated same value
// - e.g. CsvOfValue('?',3)='?,?,?'
function CsvOfValue(const Value: RawUtf8; Count: cardinal; const Sep: RawUtf8 = ','): RawUtf8;
/// retrieve the next CSV separated bit index
// - each bit was stored as BitIndex+1, i.e. 0 to mark end of CSV chunk
// - several bits set to one can be regrouped via 'first-last,' syntax
procedure SetBitCsv(var Bits; BitsCount: integer; var P: PUtf8Char);
/// convert a set of bit into a CSV content
// - each bit is stored as BitIndex+1, and separated by a ','
// - several bits set to one can be regrouped via 'first-last,' syntax
// - ',0' is always appended at the end of the CSV chunk to mark its end
function GetBitCsv(const Bits; BitsCount: integer): RawUtf8;
/// decode next CSV hexadecimal string from P, nil if no more or not matching BinBytes
// - Bin is filled with 0 if the supplied CSV content is invalid
// - if Sep is #0, it will read the hexadecimal chars until a whitespace is reached
function GetNextItemHexDisplayToBin(var P: PUtf8Char; Bin: PByte; BinBytes: PtrInt;
Sep: AnsiChar = ','): boolean;
type
/// some stack-allocated zero-terminated character buffer
// - as used by GetNextTChar64
TChar64 = array[0..63] of AnsiChar;
/// return next CSV string from P as a #0-ended buffer, false if no more
// - if Sep is #0, will copy all characters until next whitespace char
// - returns the number of bytes stored into Buf[]
function GetNextTChar64(var P: PUtf8Char; Sep: AnsiChar; out Buf: TChar64): PtrInt;
/// return next CSV string as unsigned integer from P, 0 if no more
// - if Sep is #0, it won't be searched for
function GetNextItemCardinal(var P: PUtf8Char; Sep: AnsiChar = ','): PtrUInt;
/// return next CSV string as signed integer from P, 0 if no more
// - if Sep is #0, it won't be searched for
function GetNextItemInteger(var P: PUtf8Char; Sep: AnsiChar = ','): PtrInt;
/// return next CSV string as 64-bit signed integer from P, 0 if no more
// - if Sep is #0, it won't be searched for
function GetNextItemInt64(var P: PUtf8Char; Sep: AnsiChar = ','): Int64;
/// return next CSV string as 64-bit unsigned integer from P, 0 if no more
// - if Sep is #0, it won't be searched for
function GetNextItemQWord(var P: PUtf8Char; Sep: AnsiChar = ','): QWord;
/// return next CSV hexadecimal string as 64-bit unsigned integer from P
// - returns 0 if no valid hexadecimal text is available in P
// - if Sep is #0, it won't be searched for
// - will first fill the 64-bit value with 0, then decode each two hexadecimal
// characters available in P
// - could be used to decode TTextWriter.AddBinToHexDisplayMinChars() output
function GetNextItemHexa(var P: PUtf8Char; Sep: AnsiChar = ','): QWord;
/// return next CSV string as unsigned integer from P, 0 if no more
// - P^ will point to the first non digit character (the item separator, e.g.
// ',' for CSV)
function GetNextItemCardinalStrict(var P: PUtf8Char): PtrUInt;
/// return next CSV string as unsigned integer from P, 0 if no more
// - this version expects P^ to point to an Unicode char array
function GetNextItemCardinalW(var P: PWideChar; Sep: WideChar = ','): PtrUInt;
/// return next CSV string as double from P, 0.0 if no more
// - if Sep is #0, will return all characters until next whitespace char
function GetNextItemDouble(var P: PUtf8Char; Sep: AnsiChar = ','): double;
/// return next CSV string as currency from P, 0.0 if no more
// - if Sep is #0, will return all characters until next whitespace char
function GetNextItemCurrency(var P: PUtf8Char; Sep: AnsiChar = ','): currency; overload;
{$ifdef HASINLINE}inline;{$endif}
/// return next CSV string as currency from P, 0.0 if no more
// - if Sep is #0, will return all characters until next whitespace char
procedure GetNextItemCurrency(var P: PUtf8Char; out result: currency;
Sep: AnsiChar = ','); overload;
/// return n-th indexed CSV string in P, starting at Index=0 for first one
function GetCsvItem(P: PUtf8Char; Index: PtrUInt; Sep: AnsiChar = ','): RawUtf8; overload;
/// return n-th indexed CSV string (unquoted if needed) in P, starting at Index=0 for first one
function GetUnQuoteCsvItem(P: PUtf8Char; Index: PtrUInt; Sep: AnsiChar = ',';
Quote: AnsiChar = ''''): RawUtf8; overload;
/// return n-th indexed CSV string in P, starting at Index=0 for first one
// - this function return the RTL string type of the compiler, and
// therefore can be used with ready to be displayed text
function GetCsvItemString(P: PChar; Index: PtrUInt; Sep: Char = ','): string;
/// return first CSV string in the supplied UTF-8 content
function GetFirstCsvItem(const Csv: RawUtf8; Sep: AnsiChar = ','): RawUtf8;
{$ifdef HASINLINE} inline; {$endif}
/// return last CSV string in the supplied UTF-8 content
function GetLastCsvItem(const Csv: RawUtf8; Sep: AnsiChar = ','): RawUtf8;
{$ifdef HASINLINE} inline; {$endif}
/// quickly check if Value is in Csv with no temporary memory allocation
function CsvContains(const Csv, Value: RawUtf8; Sep: AnsiChar = ',';
CaseSensitive: boolean = true): boolean;
/// return the index of a Value in a CSV string
// - start at Index=0 for first one
// - return -1 if specified Value was not found in CSV items
function FindCsvIndex(Csv: PUtf8Char; const Value: RawUtf8; Sep: AnsiChar = ',';
CaseSensitive: boolean = true; TrimValue: boolean = false): integer;
/// add the strings in the specified CSV text into a dynamic array of UTF-8 strings
// - warning: will add the strings, so List := nil may be needed before call
procedure CsvToRawUtf8DynArray(Csv: PUtf8Char; var List: TRawUtf8DynArray;
Sep: AnsiChar = ','; TrimItems: boolean = false; AddVoidItems: boolean = false;
Quote: AnsiChar = #0); overload;
/// add the strings in the specified CSV text into a dynamic array of UTF-8 strings
// - warning: will add the strings, so List := nil may be needed before call
procedure CsvToRawUtf8DynArray(const Csv, Sep, SepEnd: RawUtf8;
var List: TRawUtf8DynArray); overload;
/// convert the strings in the specified CSV text into a dynamic array of UTF-8 strings
function CsvToRawUtf8DynArray(const Csv: RawUtf8; const Sep: RawUtf8 = ',';
const SepEnd: RawUtf8 = ''): TRawUtf8DynArray; overload;
/// return the corresponding CSV text from a dynamic array of UTF-8 strings
function RawUtf8ArrayToCsv(const Values: array of RawUtf8;
const Sep: RawUtf8 = ','; HighValues: integer = -1): RawUtf8;
/// return the corresponding CSV quoted text from a dynamic array of UTF-8 strings
// - apply QuoteStr() function to each Values[] item
function RawUtf8ArrayToQuotedCsv(const Values: array of RawUtf8;
const Sep: RawUtf8 = ','; Quote: AnsiChar = ''''): RawUtf8;
/// append some prefix to all CSV values
// ! AddPrefixToCsv('One,Two,Three','Pre')='PreOne,PreTwo,PreThree'
function AddPrefixToCsv(Csv: PUtf8Char; const Prefix: RawUtf8;
Sep: AnsiChar = ','): RawUtf8;
/// append a Value to a CSV string
procedure AddToCsv(const Value: RawUtf8; var Csv: RawUtf8; const Sep: RawUtf8 = ',');
{$ifdef HASINLINE}inline;{$endif}
/// change a Value within a CSV string
function RenameInCsv(const OldValue, NewValue: RawUtf8; var Csv: RawUtf8;
const Sep: RawUtf8 = ','): boolean;
/// recognize #9 ';' or ',' as separator in a CSV text
// - to implement a separator-tolerant CSV parser
function CsvGuessSeparator(const Csv: RawUtf8): AnsiChar;
/// append the strings in the specified CSV text into a dynamic array of integer
procedure CsvToIntegerDynArray(Csv: PUtf8Char; var List: TIntegerDynArray;
Sep: AnsiChar = ',');
/// append the strings in the specified CSV text into a dynamic array of integer
procedure CsvToInt64DynArray(Csv: PUtf8Char; var List: TInt64DynArray;
Sep: AnsiChar = ','); overload;
/// convert the strings in the specified CSV text into a dynamic array of integer
function CsvToInt64DynArray(Csv: PUtf8Char; Sep: AnsiChar = ','): TInt64DynArray; overload;
/// return the corresponding CSV text from a dynamic array of 32-bit integer
// - you can set some custom Prefix and Suffix text
function IntegerDynArrayToCsv(Values: PIntegerArray; ValuesCount: integer;
const Prefix: RawUtf8 = ''; const Suffix: RawUtf8 = '';
InlinedValue: boolean = false; SepChar: AnsiChar = ','): RawUtf8; overload;
/// return the corresponding CSV text from a dynamic array of 32-bit integer
// - you can set some custom Prefix and Suffix text
function IntegerDynArrayToCsv(const Values: TIntegerDynArray;
const Prefix: RawUtf8 = ''; const Suffix: RawUtf8 = '';
InlinedValue: boolean = false; SepChar: AnsiChar = ','): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// return the corresponding CSV text from a dynamic array of 64-bit integers
// - you can set some custom Prefix and Suffix text
function Int64DynArrayToCsv(Values: PInt64Array; ValuesCount: integer;
const Prefix: RawUtf8 = ''; const Suffix: RawUtf8 = '';
InlinedValue: boolean = false; SepChar: AnsiChar = ','): RawUtf8; overload;
/// return the corresponding CSV text from a dynamic array of 64-bit integers
// - you can set some custom Prefix and Suffix text
function Int64DynArrayToCsv(const Values: TInt64DynArray;
const Prefix: RawUtf8 = ''; const Suffix: RawUtf8 = '';
InlinedValue: boolean = false; SepChar: AnsiChar = ','): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
{ ************ TTextWriter parent class for Text Generation }
type
/// event signature for TTextWriter.OnFlushToStream callback
TOnTextWriterFlush = procedure(Text: PUtf8Char; Len: PtrInt) of object;
/// defines how text is to be added into TTextWriter / TJsonWriter
// - twNone will write the supplied text with no escaping
// - twJsonEscape will properly escape " and \ as expected by JSON
// - twOnSameLine will convert any line feeds or control chars into spaces
TTextWriterKind = (
twNone,
twJsonEscape,
twOnSameLine);
/// available global options for a TTextWriter / TTextWriter instance
// - TTextWriter.WriteObject() method behavior would be set via their own
// TTextWriterWriteObjectOptions, and work in conjunction with those settings
// - twoStreamIsOwned would be set if the associated TStream is owned by the
// TTextWriter instance - as a TRawByteStringStream if twoStreamIsRawByteString
// - twoFlushToStreamNoAutoResize would forbid FlushToStream to resize the
// internal memory buffer when it appears undersized - FlushFinal will set it
// before calling a last FlushToStream
// - by default, custom serializers set via TRttiJson.RegisterCustomSerializer()
// would let AddRecordJson() and AddDynArrayJson() write enumerates and sets
// as integer numbers, unless twoEnumSetsAsTextInRecord or
// twoEnumSetsAsBooleanInRecord (exclusively) are set - for Mustache data
// context, twoEnumSetsAsBooleanInRecord will return a JSON object with
// "setname":true/false fields
// - variants and nested objects would be serialized with their default
// JSON serialization options, unless twoForceJsonExtended or
// twoForceJsonStandard is defined
// - when enumerates and sets are serialized as text into JSON, you may force
// the identifiers to be left-trimed for all their lowercase characters
// (e.g. sllError -> 'Error') by setting twoTrimLeftEnumSets: this option
// may default to the deprecated global TTextWriter.SetDefaultEnumTrim setting
// - twoEndOfLineCRLF would reflect the TEchoWriter.EndOfLineCRLF property
// - twoBufferIsExternal would be set if the temporary buffer is not handled
// by the instance, but specified at constructor, maybe from the stack
// - twoIgnoreDefaultInRecord will force custom record serialization to avoid
// writing the fields with default values, i.e. enable soWriteIgnoreDefault
// when published properties are serialized
// - twoDateTimeWithZ appends an ending 'Z' to TDateTime/TDateTimeMS values
// - twoNonExpandedArrays will force the 'non expanded' optimized JSON layout
// for array of records or classes, ignoring other formatting options:
// $ {"fieldCount":2,"values":["f1","f2","1v1",1v2,"2v1",2v2...],"rowCount":20}
// - twoNoSharedStream will force to create a new stream for each instance
// - twoNoWriteToStreamException let TTextWriter.WriteToStream silently fail
TTextWriterOption = (
twoStreamIsOwned,
twoStreamIsRawByteString,
twoFlushToStreamNoAutoResize,
twoEnumSetsAsTextInRecord,
twoEnumSetsAsBooleanInRecord,
twoFullSetsAsStar,
twoTrimLeftEnumSets,
twoForceJsonExtended,
twoForceJsonStandard,
twoEndOfLineCRLF,
twoBufferIsExternal,
twoIgnoreDefaultInRecord,
twoDateTimeWithZ,
twoNonExpandedArrays,
twoNoSharedStream,
twoNoWriteToStreamException);
/// options set for a TTextWriter / TTextWriter instance
// - allows to override e.g. AddRecordJson() and AddDynArrayJson() behavior;
// or set global process customization for a TTextWriter
TTextWriterOptions = set of TTextWriterOption;
/// may be used to allocate on stack a 8KB work buffer for a TTextWriter
// - via the TTextWriter.CreateOwnedStream overloaded constructor
TTextWriterStackBuffer = array[0..8191] of AnsiChar;
PTextWriterStackBuffer = ^TTextWriterStackBuffer;
/// available options for TTextWriter.WriteObject() method
// - woHumanReadable will add some line feeds and indentation to the content,
// to make it more friendly to the human eye
// - woDontStoreDefault (which is set by default for WriteObject method) will
// avoid serializing properties including a default value (JsonToObject function
// will set the default values, so it may help saving some bandwidth or storage)
// - woFullExpand will generate a debugger-friendly layout, including instance
// class name, sets/enumerates as text, and reference pointer - as used by
// TSynLog and ObjectToJsonFull()
// - woStoreClassName will add a "ClassName":"TMyClass" field
// - woStorePointer will add a "Address":"0431298A" field, and .map/.dbg/.mab
// source code line number corresponding to ESynException.RaisedAt
// - woStoreStoredFalse will write the 'stored false' properties, even
// if they are marked as such (used e.g. to persist all settings on file,
// but disallow the sensitive - password - fields be logged)
// - woHumanReadableFullSetsAsStar will store an human-readable set with
// all its enumerates items set to be stored as ["*"]
// - woHumanReadableEnumSetAsComment will add a comment at the end of the
// line, containing all available values of the enumaration or set, e.g:
// $ "Enum": "Destroying", // Idle,Started,Finished,Destroying
// - woEnumSetsAsText will store sets and enumerables as text (is also
// included in woFullExpand or woHumanReadable)
// - woDateTimeWithMagic will append the JSON_SQLDATE_MAGIC_C (i.e. U+FFF1)
// before the ISO-8601 encoded TDateTime value
// - woDateTimeWithZSuffix will append the Z suffix to the ISO-8601 encoded
// TDateTime value, to identify the content as strict UTC value
// - TTimeLog would be serialized as Int64, unless woTimeLogAsText is defined
// - since TOrm.ID could be huge Int64 numbers, they may be truncated
// on client side, e.g. to 53-bit range in JavaScript: you could define
// woIDAsIDstr to append an additional "ID_str":"##########" field
// - by default, RawBlob properties are serialized as null, unless
// woRawBlobAsBase64 is defined or a custom serialization is used (e.g. TOrm)
// - if woHideSensitivePersonalInformation is set, rcfSpi types (e.g. the
// TSynPersistentWithPassword.Password field) will be serialized as "***"
// to prevent security issues (e.g. in log)
// - by default, TObjectList will set the woStoreClassName for its nested
// objects, unless woObjectListWontStoreClassName is defined
// - all inherited properties would be serialized, unless woDontStoreInherited
// is defined, and only the topmost class level properties would be serialized
// - woInt64AsHex will force Int64/QWord to be written as hexadecimal string -
// see j2oAllowInt64Hex reverse option fot Json2Object
// - woDontStoreVoid will avoid serializing numeric properties equal to 0 and
// string properties equal to '' (replace both deprecated woDontStore0 and
// woDontStoreEmptyString flags)
// - woPersistentLock paranoid setting will call TSynPersistentLock.Lock/Unlock
// during serialization
TTextWriterWriteObjectOption = (
woHumanReadable,
woDontStoreDefault,
woFullExpand,
woStoreClassName,
woStorePointer,
woStoreStoredFalse,
woHumanReadableFullSetsAsStar,
woHumanReadableEnumSetAsComment,
woEnumSetsAsText,
woDateTimeWithMagic,
woDateTimeWithZSuffix,
woTimeLogAsText,
woIDAsIDstr,
woRawBlobAsBase64,
woHideSensitivePersonalInformation,
woObjectListWontStoreClassName,
woDontStoreInherited,
woInt64AsHex,
woDontStoreVoid,
woPersistentLock);
/// options set for TTextWriter.WriteObject() method
TTextWriterWriteObjectOptions = set of TTextWriterWriteObjectOption;
/// the potential places were TJsonWriter.AddHtmlEscape should process
// proper HTML string escaping, unless hfNone is used
// $ < > & " -> &lt; &gt; &amp; &quote;
// by default (hfAnyWhere)
// $ < > & -> &lt; &gt; &amp;
// outside HTML attributes (hfOutsideAttributes)
// $ & " -> &amp; &quote;
// within HTML attributes (hfWithinAttributes)
TTextWriterHtmlFormat = (
hfNone,
hfAnyWhere,
hfOutsideAttributes,
hfWithinAttributes);
/// the available JSON format, for TTextWriter.AddJsonReformat() and its
// JsonBufferReformat() and JsonReformat() wrappers
// - jsonCompact is the default machine-friendly single-line layout
// - jsonHumanReadable will add line feeds and indentation, for a more
// human-friendly result
// - jsonUnquotedPropName will emit the jsonHumanReadable layout, but
// with all property names being quoted only if necessary: this format
// could be used e.g. for configuration files - this format, similar to the
// one used in the MongoDB extended syntax, is not JSON compatible: do not
// use it e.g. with AJAX clients, but is would be handled as expected by all
// our units as valid JSON input, without previous correction
// - jsonUnquotedPropNameCompact will emit single-line layout with unquoted
// property names, which is the smallest data output within mORMot instances
// - by default we rely on UTF-8 encoding (which is mandatory in the RFC 8259)
// but you can use jsonEscapeUnicode to produce pure 7-bit ASCII output,
// with \u#### escape of non-ASCII chars, e.g. as default python json.dumps
// - jsonNoEscapeUnicode will search for any \u#### pattern and generate pure
// UTF-8 output instead
// - those features are not implemented in this unit, but in mormot.core.json
TTextWriterJsonFormat = (
jsonCompact,
jsonHumanReadable,
jsonUnquotedPropName,
jsonUnquotedPropNameCompact,
jsonEscapeUnicode,
jsonNoEscapeUnicode);
/// parent to T*Writer text processing classes, with the minimum set of methods
// - use an internal buffer, so much faster than naive string+string
// - see TTextDateWriter in mormot.core.datetime for date/time methods
// - see TJsonWriter in mormot.core.json for proper JSON support
// - see TResultsWriter in mormot.db.core for SQL resultset export
// - see TOrmWriter in mormot.orm.core for ORM oriented serialization
// - note: mORMot 1.18 TTextWriter.RegisterCustomJSONSerializerFromText()
// are moved into Rtti.RegisterFromText() as other similar methods
TTextWriter = class
protected
fStream: TStream;
fInitialStreamPosition: PtrUInt;
fTotalFileSize: PtrUInt;
fHumanReadableLevel: integer;
// internal temporary buffer
fTempBufSize: integer;
fTempBuf: PUtf8Char;
fOnFlushToStream: TOnTextWriterFlush;
fCustomOptions: TTextWriterOptions;
function GetTextLength: PtrUInt;
procedure SetStream(aStream: TStream);
procedure SetBuffer(aBuf: pointer; aBufSize: integer);
procedure WriteToStream(data: pointer; len: PtrUInt); virtual;
procedure InternalSetBuffer(aBuf: PUtf8Char; const aBufSize: PtrUInt);
{$ifdef FPC} inline; {$endif}
public
/// direct access to the low-level current position in the buffer
// - you should not use this field directly
B: PUtf8Char;
/// direct access to the low-level last position in the buffer
// - you should not use this field directly
// - points in fact to 16 bytes before the buffer ending
BEnd: PUtf8Char;
/// the data will be written to the specified Stream
// - aStream may be nil: in this case, it MUST be set before using any
// Add*() method
// - default internal buffer size if 8192
constructor Create(aStream: TStream; aBufSize: integer = 8192); overload;
/// the data will be written to the specified Stream
// - aStream may be nil: in this case, it MUST be set before using any
// Add*() method
// - will use an external buffer (which may be allocated on stack)
constructor Create(aStream: TStream; aBuf: pointer; aBufSize: integer); overload;
/// the data will be written to an internal TRawByteStringStream
// - default internal buffer size if 4096 (enough for most JSON objects)
// - consider using a stack-allocated buffer and the overloaded method
constructor CreateOwnedStream(aBufSize: integer = 4096;
NoSharedStream: boolean = false); overload;
/// the data will be written to an internal TRawByteStringStream
// - will use an external buffer (which may be allocated on stack)
constructor CreateOwnedStream(aBuf: pointer; aBufSize: integer;
NoSharedStream: boolean = false); overload;
/// the data will be written to an internal TRawByteStringStream
// - will use the stack-allocated TTextWriterStackBuffer if possible
constructor CreateOwnedStream(var aStackBuf: TTextWriterStackBuffer;
aBufSize: integer; NoSharedStream: boolean = false); overload;
/// the data will be written to an internal TRawByteStringStream
// - will use the stack-allocated TTextWriterStackBuffer
constructor CreateOwnedStream(var aStackBuf: TTextWriterStackBuffer;
NoSharedStream: boolean = false); overload;
/// the data will be written to an external file
// - you should call explicitly FlushFinal or FlushToStream to write
// any pending data to the file
constructor CreateOwnedFileStream(const aFileName: TFileName;
aBufSize: integer = 16384);
/// release all internal structures
// - e.g. free fStream if the instance was owned by this class
destructor Destroy; override;
{$ifndef PUREMORMOT2}
/// allow to override the default (JSON) serialization of enumerations and
// sets as text, which would write the whole identifier (e.g. 'sllError')
// - calling SetDefaultEnumTrim(true) would force the enumerations to
// be trimmed for any lower case char, e.g. sllError -> 'Error'
// - this is global to the current process, and should be use mainly for
// compatibility purposes for the whole process
// - you may change the default behavior by setting twoTrimLeftEnumSets
// in the TTextWriter.CustomOptions property of a given serializer
// - note that unserialization process would recognize both formats
class procedure SetDefaultEnumTrim(aShouldTrimEnumsAsText: boolean);
{$endif PUREMORMOT2}
/// write pending data, then retrieve the whole text as a UTF-8 string
function Text: RawUtf8;
{$ifdef HASINLINE}inline;{$endif}
/// write pending data, then retrieve the whole text as a UTF-8 string
procedure SetText(var result: RawUtf8; reformat: TTextWriterJsonFormat = jsonCompact);
/// set the internal stream content with the supplied UTF-8 text
procedure ForceContent(const text: RawUtf8);
/// write pending data to the Stream, with automatic buffer resizal
// - you should not have to call FlushToStream in most cases, but FlushFinal
// at the end of the process, just before using the resulting Stream
// - FlushToStream may be used to force immediate writing of the internal
// memory buffer to the destination Stream
// - you can set FlushToStreamNoAutoResize=true or call FlushFinal if you
// do not want the automatic memory buffer resizal to take place
procedure FlushToStream; virtual;
/// write pending data to the Stream, without automatic buffer resizal
// - will append the internal memory buffer to the Stream
// - in short, FlushToStream may be called during the adding process, and
// FlushFinal at the end of the process, just before using the resulting Stream
// - if you don't call FlushToStream or FlushFinal, some pending characters
// may not be copied to the Stream: you should call it before using the Stream
procedure FlushFinal;
{$ifdef HASINLINE}inline;{$endif}
/// append one ASCII char to the buffer
procedure Add(c: AnsiChar); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append one ASCII char to the buffer with no buffer check
// - to be called after a regular Add(), within the 16 bytes buffer overhead
procedure AddDirect(c: AnsiChar); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append one ASCII char to the buffer with no buffer check
// - to be called after a regular Add(), within the 16 bytes buffer overhead
procedure AddDirect(c1, c2: AnsiChar); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append one comma (',') character
// - to be called after a regular Add(), within the 16 bytes buffer overhead
procedure AddComma;
{$ifdef HASINLINE}inline;{$endif}
/// append one ASCII char to the buffer, if not already there as LastChar
procedure AddOnce(c: AnsiChar); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append two chars to the buffer
procedure Add(c1, c2: AnsiChar); overload;
{$ifdef HASINLINE}inline;{$endif}
{$ifdef CPU32} // already implemented by Add(Value: PtrInt) method on CPU64
/// append a 64-bit signed integer Value as text
procedure Add(Value: Int64); overload;
{$endif CPU32}
/// append a 32-bit signed integer Value as text
procedure Add(Value: PtrInt); overload;
{$ifdef FPC_OR_DELPHIXE4}{$ifdef ASMINTEL}inline;{$endif}{$endif} // URW1111
/// append a boolean Value as text
// - write either 'true' or 'false'
procedure Add(Value: boolean); overload;
/// append a Currency from its Int64 in-memory representation
// - expects a PInt64 to avoid ambiguity with the AddCurr() method
procedure AddCurr64(Value: PInt64);
/// append a Currency value
// - just an inlined wrapper around AddCurr64(PInt64(@Value))
procedure AddCurr(const Value: currency);
{$ifdef HASINLINE}inline;{$endif}
/// append an Unsigned 32-bit integer Value as a String
procedure AddU(Value: cardinal);
{$ifdef FPC_OR_DELPHIXE4}{$ifdef ASMINTEL}inline;{$endif}{$endif} // URW1111
/// append an Unsigned 32-bit integer Value as a quoted hexadecimal String
procedure AddUHex(Value: cardinal; QuotedChar: AnsiChar = '"');
{$ifdef HASINLINE}inline;{$endif}
/// append an Unsigned 64-bit integer Value as a String
procedure AddQ(Value: QWord);
/// append an Unsigned 64-bit integer Value as a quoted hexadecimal String
procedure AddQHex(Value: Qword; QuotedChar: AnsiChar = '"');
{$ifdef HASINLINE}inline;{$endif}
/// append a GUID value, encoded as text without any {}
// - will store e.g. '3F2504E0-4F89-11D3-9A0C-0305E82C3301'
procedure Add(Value: PGuid; QuotedChar: AnsiChar = #0); overload;
/// append a floating-point Value as a String
// - write "Infinity", "-Infinity", and "NaN" for corresponding IEEE values
// - noexp=true will call ExtendedToShortNoExp() to avoid any scientific
// notation in the resulting text
procedure AddDouble(Value: double; noexp: boolean = false);
/// append a floating-point Value as a String
// - write "Infinity", "-Infinity", and "NaN" for corresponding IEEE values
// - noexp=true will call ExtendedToShortNoExp() to avoid any scientific
// notation in the resulting text
procedure AddSingle(Value: single; noexp: boolean = false);
/// append a floating-point Value as a String
// - write "Infinity", "-Infinity", and "NaN" for corresponding IEEE values
// - noexp=true will call ExtendedToShortNoExp() to avoid any scientific
// notation in the resulting text
procedure Add(Value: Extended; precision: integer; noexp: boolean = false); overload;
/// append a floating-point text buffer
// - will correct on the fly '.5' -> '0.5' and '-.5' -> '-0.5'
// - is used when the input comes from a third-party source with no regular
// output, e.g. a database driver
procedure AddFloatStr(P: PUtf8Char);
/// append CR+LF (#13#10) chars
// - this method won't call TEchoWriter.EchoAdd() registered events - use
// TEchoWriter.AddEndOfLine() method instead
// - TEchoWriter.AddEndOfLine() will append either CR+LF (#13#10) or
// only LF (#10) depending on its internal options
procedure AddCR;
{$ifdef HASINLINE}inline;{$endif}
/// append CR+LF (#13#10) chars and #9 indentation
// - indentation depth is defined by the HumanReadableLevel value
procedure AddCRAndIndent; virtual;
/// write the same character multiple times
procedure AddChars(aChar: AnsiChar; aCount: PtrInt);
/// append an integer Value as a 2 digits text with comma
procedure Add2(Value: PtrUInt);
/// append an integer Value as a 3 digits text without any comma
procedure Add3(Value: cardinal);
/// append an integer Value as a 4 digits text with comma
procedure Add4(Value: PtrUInt);
/// append a time period, specified in micro seconds, in 00.000.000 TSynLog format
procedure AddMicroSec(MicroSec: cardinal);
/// append an array of integers as CSV
procedure AddCsvInteger(const Integers: array of integer);
/// append an array of doubles as CSV
procedure AddCsvDouble(const Doubles: array of double);
/// append some UTF-8 chars to the buffer
// - input length is calculated from zero-ended char
// - don't escapes chars according to the JSON RFC
procedure AddNoJsonEscape(P: Pointer); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append some UTF-8 chars to the buffer
// - don't escapes chars according to the JSON RFC
// - called by inlined AddNoJsonEscape() if Len >= fTempBufSize
procedure AddNoJsonEscapeBig(P: Pointer; Len: PtrInt);
/// append some UTF-8 chars to the buffer - inlined for small content
// - don't escapes chars according to the JSON RFC
procedure AddNoJsonEscape(P: Pointer; Len: PtrInt); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append some UTF-8 chars to the buffer
// - don't escapes chars according to the JSON RFC
procedure AddNoJsonEscapeUtf8(const text: RawByteString);
{$ifdef HASINLINE}inline;{$endif}
/// append some UTF-8 encoded chars to the buffer, from a RTL string type
// - don't escapes chars according to the JSON RFC
// - if s is a UnicodeString, will convert UTF-16 into UTF-8
procedure AddNoJsonEscapeString(const s: string);
/// append some unicode chars to the buffer
// - WideCharCount is the unicode chars count, not the byte size; if it is
// 0, then it will convert until an ending #0 (fastest way)
// - don't escapes chars according to the JSON RFC
// - will convert the Unicode chars into UTF-8
procedure AddNoJsonEscapeW(WideChar: PWord; WideCharCount: integer);
/// append some Ansi text as UTF-8 chars to the buffer
// - don't escapes chars according to the JSON RFC
procedure AddNoJsonEscape(P: PAnsiChar; Len: PtrInt; CodePage: cardinal); overload;
/// append some UTF-8 content to the buffer, with no JSON escape
// - if supplied json is '', will write 'null' so that valid JSON is written
// - redirect to AddNoJsonEscape() otherwise
procedure AddRawJson(const json: RawJson);
/// append a line of text with CR+LF at the end
procedure AddLine(const Text: ShortString);
/// append some chars to the buffer in one line
// - P should be ended with a #0
// - will write #1..#31 chars as spaces (so content will stay on the same line)
procedure AddOnSameLine(P: PUtf8Char); overload;
/// append some chars to the buffer in one line
// - will write #0..#31 chars as spaces (so content will stay on the same line)
procedure AddOnSameLine(P: PUtf8Char; Len: PtrInt); overload;
/// append some wide chars to the buffer in one line
// - will write #0..#31 chars as spaces (so content will stay on the same line)
procedure AddOnSameLineW(P: PWord; Len: PtrInt);
/// append some RTL string to the buffer in one line
// - will write #0..#31 chars as spaces (so content will stay on the same line)
procedure AddOnSameLineString(const Text: string);
/// append an UTF-8 String, with no JSON escaping
procedure AddString(const Text: RawUtf8);
/// append several UTF-8 strings
procedure AddStrings(const Text: array of RawUtf8); overload;
/// append an UTF-8 string several times
procedure AddStrings(const Text: RawUtf8; count: PtrInt); overload;
/// append a ShortString
procedure AddShort(const Text: ShortString); overload;
/// append a ShortString - or at least a small buffer < 256 chars
procedure AddShort(Text: PUtf8Char; TextLen: PtrInt); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append a TShort8 - Text should be not '', and up to 8 chars long
// - this method is aggressively inlined, so may be preferred to AddShort()
// for appending simple UTF-8 constant text
procedure AddShorter(const Short8: TShort8);
{$ifdef HASINLINE}inline;{$endif}
/// append 'null' as text
procedure AddNull;
{$ifdef HASINLINE}inline;{$endif}
/// append a sub-part of an UTF-8 String
// - emulates AddString(copy(Text,start,len))
procedure AddStringCopy(const Text: RawUtf8; start, len: PtrInt);
/// append after trim first lowercase chars ('otDone' will add 'Done' e.g.)
procedure AddTrimLeftLowerCase(Text: PShortString);
/// append a UTF-8 String excluding any space or control char
// - this won't escape the text as expected by JSON
procedure AddTrimSpaces(const Text: RawUtf8); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append a #0-terminated UTF-8 buffer excluding any space or control char
// - this won't escape the text as expected by JSON
procedure AddTrimSpaces(P: PUtf8Char); overload;
/// append some text with left-filled spaces up to Width characters count
procedure AddSpaced(const Text: RawUtf8; Width: PtrInt;
SepChar: AnsiChar = #0); overload;
/// append some text with left-filled spaces up to Width characters count
// - if the value too big to fit, will truncate up to the first Width chars
procedure AddSpaced(Text: PUtf8Char; TextLen, Width: PtrInt); overload;
/// append some number with left-filled spaces up to Width characters count
// - if the value too big to fit in Width, will append K(Value) abbreviation
procedure AddSpaced(Value: QWord; Width: PtrInt;
SepChar: AnsiChar = #0); overload;
/// append some UTF-8 chars, replacing a given character with another
procedure AddReplace(Text: PUtf8Char; Orig, Replaced: AnsiChar);
/// append some UTF-8 chars, quoting all " chars
// - same algorithm than AddString(QuotedStr()) - without memory allocation,
// and with an optional maximum text length (truncated with ending '...')
// - this function implements what is specified in the official SQLite3
// documentation: "A string constant is formed by enclosing the string in
// single quotes ('). A single quote within the string can be encoded by
// putting two single quotes in a row - as in Pascal."
procedure AddQuotedStr(Text: PUtf8Char; TextLen: PtrUInt; Quote: AnsiChar;
TextMaxLen: PtrInt = 0);
/// append an URI-decoded domain name, also normalizing dual // into /
// - only parameters - i.e. after '?' - may have ' ' replaced by '+'
procedure AddUrlNameNormalize(U: PUtf8Char; L: PtrInt);
/// append some UTF-8 chars, escaping all HTML special chars as expected
procedure AddHtmlEscape(Text: PUtf8Char; Fmt: TTextWriterHtmlFormat = hfAnyWhere); overload;
/// append some UTF-8 chars, escaping all HTML special chars as expected
procedure AddHtmlEscape(Text: PUtf8Char; TextLen: PtrInt;
Fmt: TTextWriterHtmlFormat = hfAnyWhere); overload;
/// append some UTF-16 chars, escaping all HTML special chars as expected
procedure AddHtmlEscapeW(Text: PWideChar;
Fmt: TTextWriterHtmlFormat = hfAnyWhere); overload;
/// append some RTL string chars, escaping all HTML special chars as expected
procedure AddHtmlEscapeString(const Text: string;
Fmt: TTextWriterHtmlFormat = hfAnyWhere);
/// append some UTF-8 chars, escaping all HTML special chars as expected
procedure AddHtmlEscapeUtf8(const Text: RawUtf8;
Fmt: TTextWriterHtmlFormat = hfAnyWhere);
/// append some chars, escaping all XML special chars as expected
// - i.e. < > & " ' as &lt; &gt; &amp; &quote; &apos;
// - and all control chars (i.e. #1..#31) as &#..;
// - see @http://www.w3.org/TR/xml/#syntax
procedure AddXmlEscape(Text: PUtf8Char);
/// append a property name, as '"PropName":'
// - PropName content should not need any JSON escape (e.g. no " within,
// and only ASCII 7-bit characters)
// - if twoForceJsonExtended is defined in CustomOptions, it would append
// 'PropName:' without the double quotes
procedure AddProp(PropName: PUtf8Char; PropNameLen: PtrInt); overload;
/// append a property name, as '"PropName":'
// - just a wrapper around AddProp(PropName, StrLen(PropName))
procedure AddProp(PropName: PUtf8Char); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append a ShortString property name, as '"PropName":'
// - PropName content should not need any JSON escape (e.g. no " within,
// and only ASCII 7-bit characters)
// - if twoForceJsonExtended is defined in CustomOptions, it would append
// 'PropName:' without the double quotes
// - is a wrapper around AddProp()
procedure AddPropName(const PropName: ShortString);
{$ifdef HASINLINE}inline;{$endif}
/// append a JSON field name, followed by a number value and a comma (',')
procedure AddPropInt64(const PropName: ShortString; Value: Int64;
WithQuote: AnsiChar = #0);
/// append a RawUtf8 property name, as '"FieldName":'
// - FieldName content should not need any JSON escape (e.g. no " within)
// - if twoForceJsonExtended is defined in CustomOptions, it would append
// 'PropName:' without the double quotes
// - is a wrapper around AddProp()
procedure AddFieldName(const FieldName: RawUtf8);
{$ifdef HASINLINE}inline;{$endif}
/// append a RawUtf8 property name, as '"FieldName"
// - FieldName content should not need any JSON escape (e.g. no " within)
procedure AddQuotedFieldName(const FieldName: RawUtf8;
const VoidPlaceHolder: RawUtf8 = ''); overload;
{$ifdef HASINLINE}inline;{$endif}
/// append a RawUtf8 property name, as '"FieldName"
// - FieldName content should not need any JSON escape (e.g. no " within)
procedure AddQuotedFieldName(FieldName: PUtf8Char; FieldNameLen: PtrInt;
const VoidPlaceHolder: RawUtf8 = ''); overload;
/// append the class name of an Object instance as text
procedure AddClassName(aClass: TClass);
/// append an Instance name and pointer, as '"TObjectList(00425E68)"'+SepChar
// - append "void" if Instance = nil
procedure AddInstanceName(Instance: TObject; SepChar: AnsiChar);
/// append an Instance name and pointer, as 'TObjectList(00425E68)'+SepChar
procedure AddInstancePointer(Instance: TObject; SepChar: AnsiChar;
IncludeUnitName, IncludePointer: boolean);
/// append some binary data as hexadecimal text conversion
procedure AddBinToHex(Bin: Pointer; BinBytes: PtrInt; LowerHex: boolean = false);
/// append some binary data as hexadecimal text conversion
// - append its minimal chars, i.e. excluding last bytes containing 0
procedure AddBinToHexMinChars(Bin: Pointer; BinBytes: PtrInt; LowerHex: boolean = false);
/// fast conversion from binary data into hexa chars, ready to be displayed
// - using this function with Bin^ as an integer value will serialize it
// in big-endian order (most-significant byte first), as used by humans
// - up to the internal buffer bytes may be converted
procedure AddBinToHexDisplay(Bin: pointer; BinBytes: PtrInt);
/// fast conversion from binary data into MSB hexa chars
// - up to the internal buffer bytes may be converted
procedure AddBinToHexDisplayLower(Bin: pointer; BinBytes: PtrInt;
QuotedChar: AnsiChar = #0);
/// fast conversion from binary data into quoted MSB lowercase hexa chars
// - up to the internal buffer bytes may be converted
procedure AddBinToHexDisplayQuoted(Bin: pointer; BinBytes: PtrInt);
{$ifdef HASINLINE}inline;{$endif}
/// append a Value as significant hexadecimal text
// - expects BinBytes to be > 0
// - append its minimal chars, i.e. excluding highest bytes containing 0
// - use GetNextItemHexa() to decode such a text value
procedure AddBinToHexDisplayMinChars(Bin: pointer; BinBytes: PtrInt;
QuotedChar: AnsiChar = #0);
/// add the pointer into significant hexa chars, ready to be displayed
// - append its minimal chars i.e. excluding highest bytes containing 0
procedure AddPointer(P: PtrUInt; QuotedChar: AnsiChar = #0);
/// write a byte as two hexa chars
procedure AddByteToHex(Value: PtrUInt);
{$ifdef HASINLINE}inline;{$endif}
/// write a byte as two hexa chars
procedure AddByteToHexLower(Value: PtrUInt);
{$ifdef HASINLINE}inline;{$endif}
/// write a Int18 value (0..262143) as 3 chars
// - this encoding is faster than Base64, and has spaces on the left side
// - use function Chars3ToInt18() to decode the textual content
procedure AddInt18ToChars3(Value: cardinal);
/// append strings or integers with a specified format
// - this class implementation will raise an exception for twJsonEscape,
// and simply call FormatUtf8() over a temp RawUtf8 for twNone/twOnSameLine
// - use faster and more complete overriden TJsonWriter.Add instead!
procedure Add(const Format: RawUtf8; const Values: array of const;
Escape: TTextWriterKind = twNone;
WriteObjectOptions: TTextWriterWriteObjectOptions = [woFullExpand]); overload; virtual;
/// this class implementation will raise an exception
// - use overriden TJsonWriter version instead!
function AddJsonReformat(Json: PUtf8Char; Format: TTextWriterJsonFormat;
EndOfObject: PUtf8Char): PUtf8Char; virtual;
/// this class implementation will raise an exception
// - use overriden TJsonWriter version instead!
procedure AddVariant(const Value: variant; Escape: TTextWriterKind = twJsonEscape;
WriteOptions: TTextWriterWriteObjectOptions = []); virtual;
/// this class implementation will raise an exception
// - use overriden TJsonWriter version instead!
// - TypeInfo is a PRttiInfo instance - but not available in this early unit
procedure AddTypedJson(Value: pointer; TypeInfo: pointer;
WriteOptions: TTextWriterWriteObjectOptions = []); virtual;
/// write some #0 ended UTF-8 text, according to the specified format
// - use overriden TJsonWriter version instead!
procedure Add(P: PUtf8Char; Escape: TTextWriterKind); overload; virtual;
/// write some #0 ended UTF-8 text, according to the specified format
// - use overriden TJsonWriter version instead!
procedure Add(P: PUtf8Char; Len: PtrInt; Escape: TTextWriterKind); overload; virtual;
/// write some data Base64 encoded
// - use overriden TJsonWriter version instead!
procedure WrBase64(P: PAnsiChar; Len: PtrUInt; withMagic: boolean); virtual;
/// serialize as JSON the given object
// - use overriden TJsonWriter version instead!
procedure WriteObject(Value: TObject;
WriteOptions: TTextWriterWriteObjectOptions = [woDontStoreDefault]); virtual;
/// append a T*ObjArray dynamic array as a JSON array
// - for proper serialization on Delphi 7-2009, use Rtti.RegisterObjArray()
procedure AddObjArrayJson(const aObjArray;
aOptions: TTextWriterWriteObjectOptions = [woDontStoreDefault]);
/// return the last char appended
// - returns #0 if no char has been written yet, or the buffer has been just
// flushed: so this method is to be handled only in some particular usecases
function LastChar: AnsiChar;
/// how many bytes are currently in the internal buffer and not on disk/stream
// - see TextLength for the total number of bytes, on both stream and memory
function PendingBytes: PtrUInt;
{$ifdef HASINLINE}inline;{$endif}
/// how many bytes were currently written on disk/stream
// - excluding the bytes in the internal buffer (see PendingBytes)
// - see TextLength for the total number of bytes, on both stream and memory
property WrittenBytes: PtrUInt
read fTotalFileSize;
/// low-level access to the current indentation level
property HumanReadableLevel: integer
read fHumanReadableLevel write fHumanReadableLevel;
/// the last char appended is canceled
// - only one char cancelation is allowed at the same position: don't call
// CancelLastChar/CancelLastComma more than once without appending text inbetween
procedure CancelLastChar; overload;
{$ifdef HASINLINE}inline;{$endif}
/// the last char appended is canceled, if match the supplied one
// - only one char cancelation is allowed at the same position: don't call
// CancelLastChar/CancelLastComma more than once without appending text inbetween
procedure CancelLastChar(aCharToCancel: AnsiChar); overload;
{$ifdef HASINLINE}inline;{$endif}
/// the last char appended is canceled if it was a ','
// - only one char cancelation is allowed at the same position: don't call
// CancelLastChar/CancelLastComma more than once without appending text inbetween
procedure CancelLastComma; overload;
{$ifdef HASINLINE}inline;{$endif}
/// the last char appended is canceled if it was a ',' and replaced
// - only one char cancelation is allowed at the same position: don't call
// CancelLastChar/CancelLastComma more than once without appending text inbetween
procedure CancelLastComma(aReplaceChar: AnsiChar); overload;
{$ifdef HASINLINE}inline;{$endif}
/// rewind the Stream to the position when Create() was called
// - note that this does not clear the Stream content itself, just
// move back its writing position to its initial place
procedure CancelAll;
/// same as CancelAll, and also reset the CustomOptions
procedure CancelAllAsNew;
{$ifdef HASINLINE}inline;{$endif}
/// same as CancelAll, and also use a new local TTextWriterStackBuffer
procedure CancelAllWith(var temp: TTextWriterStackBuffer);
/// count of added bytes to the stream
// - see PendingBytes for the number of bytes currently in the memory buffer
// or WrittenBytes for the number of bytes already written to disk/stream
property TextLength: PtrUInt
read GetTextLength;
/// the internal TStream used for storage
// - you should call the FlushFinal (or FlushToStream) methods before using
// this TStream content, to flush all pending characters
// - if the TStream instance has not been specified when calling the
// TTextWriter constructor, it can be forced via this property, before
// any writing
property Stream: TStream
read fStream write SetStream;
/// global options to customize this TTextWriter instance process
// - allows to override e.g. AddRecordJson() and AddDynArrayJson() behavior
property CustomOptions: TTextWriterOptions
read fCustomOptions write fCustomOptions;
/// optional event called before FlushToStream method process
// - used e.g. by TEchoWriter to perform proper content echoing
property OnFlushToStream: TOnTextWriterFlush
read fOnFlushToStream write fOnFlushToStream;
end;
/// class of our simple TEXT format writer to a Stream
TBaseWriterClass = class of TTextWriter;
var
/// contains the default JSON serialization class for the framework
// - used internally by ObjectToJson/VariantSaveJson to avoid circular references
// - will be set to TJsonWriter by mormot.core.json; default TTextWriter
// would raise an exception on any JSON processing attempt
DefaultJsonWriter: TBaseWriterClass = TTextWriter;
/// will serialize any TObject into its UTF-8 JSON representation
/// - serialize as JSON the published integer, Int64, floating point values,
// TDateTime (stored as ISO 8601 text), string, variant and enumerate
// (e.g. boolean) properties of the object (and its parents)
// - would set twoForceJsonStandard to force standard (non-extended) JSON
// - the enumerates properties are stored with their integer index value
// - will write also the properties published in the parent classes
// - nested properties are serialized as nested JSON objects
// - any TCollection property will also be serialized as JSON arrays
// - you can add some custom serializers for ANY class, via mormot.core.json.pas
// TRttiJson.RegisterCustomSerializer() class method
// - call internally TTextWriter.WriteObject() method from DefaultJsonWriter
function ObjectToJson(Value: TObject;
Options: TTextWriterWriteObjectOptions = [woDontStoreDefault]): RawUtf8; overload;
{$ifdef HASINLINE} inline; {$endif}
/// will serialize any TObject into its UTF-8 JSON representation
procedure ObjectToJson(Value: TObject; var result: RawUtf8;
Options: TTextWriterWriteObjectOptions = [woDontStoreDefault]); overload;
/// will serialize any TObject into its expanded UTF-8 JSON representation
// - includes debugger-friendly information, similar to TSynLog, i.e.
// class name and sets/enumerates as text
// - redirect to ObjectToJson() with the proper TTextWriterWriteObjectOptions,
// since our JSON serialization detects and serialize Exception.Message
function ObjectToJsonDebug(Value: TObject;
Options: TTextWriterWriteObjectOptions = [woDontStoreDefault,
woHumanReadable, woStoreClassName, woStorePointer,
woHideSensitivePersonalInformation]): RawUtf8;
/// a wrapper around ConsoleWrite(ObjectToJson(Value))
procedure ConsoleObject(Value: TObject;
Options: TTextWriterWriteObjectOptions = [woHumanReadable]);
/// check if some UTF-8 text would need HTML escaping
function NeedsHtmlEscape(text: PUtf8Char; fmt: TTextWriterHtmlFormat): boolean;
/// escape some UTF-8 text into HTML
// - just a wrapper around TTextWriter.AddHtmlEscape() process,
// replacing < > & " chars depending on the HTML layer
function HtmlEscape(const text: RawUtf8;
fmt: TTextWriterHtmlFormat = hfAnyWhere): RawUtf8;
/// escape some RTL string text into UTF-8 HTML
// - just a wrapper around TTextWriter.AddHtmlEscapeString() process,
// replacing < > & " chars depending on the HTML layer
function HtmlEscapeString(const text: string;
fmt: TTextWriterHtmlFormat = hfAnyWhere): RawUtf8;
/// escape as \xx hexadecimal some chars from a set into a pre-allocated buffer
// - dest^ should have at least srclen * 3 bytes, for \## trios
function EscapeHexBuffer(src, dest: PUtf8Char; srclen: integer;
const toescape: TSynAnsicharSet; escape: AnsiChar = '\'): PUtf8Char;
/// escape as \xx hexadecimal some chars from a set into a new RawUtf8 string
// - as used e.g. by LdapEscape()
function EscapeHex(const src: RawUtf8;
const toescape: TSynAnsicharSet; escape: AnsiChar = '\'): RawUtf8;
/// un-escape \xx or \c encoded chars from a pre-allocated buffer
// - any CR/LF after \ will also be ignored
// - dest^ should have at least the same length than src^
function UnescapeHexBuffer(src, dest: PUtf8Char; escape: AnsiChar = '\'): PUtf8Char;
/// un-escape \xx or \c encoded chars into a new RawUtf8 string
// - any CR/LF after \ will also be ignored
function UnescapeHex(const src: RawUtf8; escape: AnsiChar = '\'): RawUtf8;
/// escape as \char pair some chars from a set into a pre-allocated buffer
// - dest^ should have at least srclen * 2 bytes, for \char pairs
// - by definition, escape should be part of the toescape set
function EscapeCharBuffer(src, dest: PUtf8Char; srclen: integer;
const toescape: TSynAnsicharSet; escape: AnsiChar = '\'): PUtf8Char;
/// escape as \char pair some chars from a set into a new RawUtf8 string
// - by definition, escape should be part of the toescape set
function EscapeChar(const src: RawUtf8;
const toescape: TSynAnsicharSet; escape: AnsiChar = '\'): RawUtf8;
const
/// TTextWriter JSON serialization options focusing of sets support
// - as used e.g. by TJsonWriter.AddRecordJson/AddDynArrayJson and
// TDynArray.SaveJson methods, and SaveJson/RecordSaveJson functions
// - to be used as TEXTWRITEROPTIONS_TEXTSET[EnumSetsAsText]
TEXTWRITEROPTIONS_SETASTEXT: array[boolean] of TTextWriterOptions = (
[twoFullSetsAsStar],
[twoFullSetsAsStar, twoEnumSetsAsTextInRecord]);
/// TTextWriter JSON serialization options including twoEnumSetsAsTextInRecord
TEXTWRITEROPTIONS_ENUMASTEXT: array[boolean] of TTextWriterOptions = (
[],
[twoEnumSetsAsTextInRecord]);
/// TTextWriter JSON serialization options including woEnumSetsAsText
TEXTWRITEROBJECTOPTIONS_ENUMASTEXT: array[boolean] of TTextWriterWriteObjectOptions = (
[],
[woEnumSetsAsText]);
/// TTextWriter JSON serialization options which should be preserved
// - used e.g. by TTextWriter.CancelAllAsNew to reset its CustomOptions
TEXTWRITEROPTIONS_RESET =
[twoStreamIsOwned, twoStreamIsRawByteString, twoBufferIsExternal];
type
TEchoWriter = class;
/// callback used to echo each line of TEchoWriter class
// - should return TRUE on success, FALSE if the log was not echoed: but
// TSynLog will continue logging, even if this event returned FALSE
TOnTextWriterEcho = function(Sender: TEchoWriter; Level: TSynLogLevel;
const Text: RawUtf8): boolean of object;
TEchoWriterBack = record
Level: TSynLogLevelDynArray;
Text: TRawUtf8DynArray;
Count: PtrInt;
end;
/// add optional echoing of the lines to TTextWriter
// - as used e.g. by TSynLog writer for log optional redirection
// - is defined as a nested class to reduce plain TTextWriter scope, and
// better follow the SOLID principles
TEchoWriter = class
protected
fWriter: TTextWriter;
fEchoStart: PtrInt;
fEchoBuf: RawUtf8;
fEchos: array of TOnTextWriterEcho;
fBack: TEchoWriterBack;
fBackSafe: TLightLock; // protect fBack.Level/Text
fEchoPendingExecuteBackground: boolean;
function EchoFlush: PtrInt;
procedure EchoPendingToBackground(aLevel: TSynLogLevel);
function GetEndOfLineCRLF: boolean;
{$ifdef HASINLINE}inline;{$endif}
procedure SetEndOfLineCRLF(aEndOfLineCRLF: boolean);
public
/// prepare for the echoing process
constructor Create(Owner: TTextWriter); reintroduce;
/// end the echoing process
destructor Destroy; override;
/// should be called from TTextWriter.FlushToStream
// - write pending data to the Stream, with automatic buffer resizal and echoing
// - this overriden method will handle proper echoing
procedure FlushToStream(Text: PUtf8Char; Len: PtrInt);
/// mark an end of line, ready to be "echoed" to registered listeners
// - append a LF (#10) char or CR+LF (#13#10) chars to the buffer, depending
// on the EndOfLineCRLF property value (default is LF, to minimize storage)
// - any callback registered via EchoAdd() will monitor this line in the
// current thread, or calling EchoPendingExecute from a background thread
// - used e.g. by TSynLog for console output, as stated by Level parameter
procedure AddEndOfLine(aLevel: TSynLogLevel = sllNone);
/// add a callback to echo each line written by this class
// - this class expects AddEndOfLine to mark the end of each line
procedure EchoAdd(const aEcho: TOnTextWriterEcho);
/// remove a callback to echo each line written by this class
// - event should have been previously registered by a EchoAdd() call
procedure EchoRemove(const aEcho: TOnTextWriterEcho);
/// reset the internal buffer used for echoing content
procedure EchoReset;
/// run all pending EchoPendingExecuteBackground notifications
// - should be executed from a background thread
procedure EchoPendingExecute;
/// the associated TTextWriter instance
property Writer: TTextWriter
read fWriter;
/// define how AddEndOfLine method stores its line feed characters
// - by default (FALSE), it will append a LF (#10) char to the buffer
// - you can set this property to TRUE, so that CR+LF (#13#10) chars will
// be appended instead
// - is just a wrapper around twoEndOfLineCRLF item in CustomOptions
property EndOfLineCRLF: boolean
read GetEndOfLineCRLF write SetEndOfLineCRLF;
/// if EchoPendingExecute is about to be executed in the background
property EchoPendingExecuteBackground: boolean
read fEchoPendingExecuteBackground write fEchoPendingExecuteBackground;
end;
{ ************ Numbers (integers or floats) and Variants to Text Conversion }
var
/// naive but efficient cache to avoid string memory allocation for
// 0..999 small numbers by Int32ToUtf8/UInt32ToUtf8
// - use around 16KB of heap (since each item consumes 16 bytes), but increase
// overall performance and reduce memory allocation (and fragmentation),
// especially during multi-threaded execution
// - noticeable when strings are used as array indexes (e.g.
// in mormot.db.nosql.bson)
// - is defined globally, since may be used from an inlined function
SmallUInt32Utf8: array[0..999] of RawUtf8;
/// fast RawUtf8 version of 32-bit IntToStr()
function Int32ToUtf8(Value: PtrInt): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast RawUtf8 version of 32-bit IntToStr()
// - result as var parameter saves a local assignment and a try..finally
procedure Int32ToUtf8(Value: PtrInt; var result: RawUtf8); overload;
/// fast RawUtf8 version of 64-bit IntToStr()
function Int64ToUtf8(Value: Int64): RawUtf8; overload;
{$ifdef HASSAFEINLINE}inline;{$endif} // Delphi 2007 has trouble inlining this
/// fast RawUtf8 version of 64-bit IntToStr()
// - result as var parameter saves a local assignment and a try..finally
procedure Int64ToUtf8(Value: Int64; var result: RawUtf8); overload;
/// fast RawUtf8 version of 32-bit IntToStr()
function ToUtf8(Value: PtrInt): RawUtf8; overload;
{$ifdef CPU32}
/// fast RawUtf8 version of 64-bit IntToStr()
function ToUtf8(Value: Int64): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
{$endif CPU32}
/// optimized conversion of a cardinal into RawUtf8
function UInt32ToUtf8(Value: PtrUInt): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// optimized conversion of a cardinal into RawUtf8
procedure UInt32ToUtf8(Value: PtrUInt; var result: RawUtf8); overload;
/// fast RawUtf8 version of 64-bit IntToStr(), with proper QWord support
procedure UInt64ToUtf8(Value: QWord; var result: RawUtf8);
/// convert a string into its INTEGER Curr64 (value*10000) representation
// - this type is compatible with currency memory mapping with PInt64(@Curr)^
// - fast conversion, using only integer operations
// - if NoDecimal is defined, will be set to TRUE if there is no decimal, AND
// the returned value will be an Int64 (not a PInt64(@Curr)^)
function StrToCurr64(P: PUtf8Char; NoDecimal: PBoolean = nil): Int64;
/// convert a string into its currency representation
// - will call StrToCurr64()
function StrToCurrency(P: PUtf8Char): currency;
{$ifdef HASINLINE}inline;{$endif}
/// convert a currency value into a string
// - fast conversion, using only integer operations
// - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
function CurrencyToStr(const Value: currency): RawUtf8;
{$ifdef HASINLINE}inline;{$endif}
/// convert an INTEGER Curr64 (value*10000) into a string
// - this type is compatible with currency memory mapping with PInt64(@Curr)^
// - fast conversion, using only integer operations
// - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
function Curr64ToStr(const Value: Int64): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert an INTEGER Curr64 (value*10000) into a string
// - this type is compatible with currency memory mapping with PInt64(@Curr)^
// - fast conversion, using only integer operations
// - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
procedure Curr64ToStr(const Value: Int64; var result: RawUtf8); overload;
/// convert an INTEGER Curr64 (value*10000) into a string
// - this type is compatible with currency memory mapping with PInt64(@Curr)^
// - fast conversion, using only integer operations
// - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
// - return the number of chars written to Dest^
function Curr64ToPChar(const Value: Int64; Dest: PUtf8Char): PtrInt;
/// internal fast INTEGER Curr64 (value*10000) value to text conversion
// - expect the last available temporary char position in P
// - return the last written char position (write in reverse order in P^)
// - will return 0 for Value=0, or a string representation with always 4 decimals
// (e.g. 1->'0.0001' 500->'0.0500' 25000->'2.5000' 30000->'3.0000')
// - is called by Curr64ToPChar() and Curr64ToStr() functions
function StrCurr64(P: PAnsiChar; const Value: Int64): PAnsiChar;
/// faster than default SysUtils.IntToStr implementation
function IntToString(Value: integer): string; overload;
/// faster than default SysUtils.IntToStr implementation
function IntToString(Value: cardinal): string; overload;
/// faster than default SysUtils.IntToStr implementation
function IntToString(Value: Int64): string; overload;
/// convert a floating-point value to its numerical text equivalency
function DoubleToString(Value: Double): string;
/// convert a currency value from its Int64 binary representation into
// its numerical text equivalency
// - decimals are joined by 2 (no decimal, 2 decimals, 4 decimals)
function Curr64ToString(Value: Int64): string;
/// convert a floating-point value to its numerical text equivalency
// - on Delphi Win32, calls FloatToText() in ffGeneral mode; on FPC uses str()
// - DOUBLE_PRECISION will redirect to DoubleToShort() and its faster Fabian
// Loitsch's Grisu algorithm if available
// - returns the count of chars stored into S, i.e. length(S)
function ExtendedToShort(S: PShortString;
Value: TSynExtended; Precision: integer): integer;
/// convert a floating-point value to its numerical text equivalency without
// scientification notation
// - DOUBLE_PRECISION will redirect to DoubleToShortNoExp() and its faster Fabian
// Loitsch's Grisu algorithm if available - or calls str(Value:0:precision,S)
// - returns the count of chars stored into S, i.e. length(S)
function ExtendedToShortNoExp(S: PShortString; Value: TSynExtended;
Precision: integer): integer;
/// check if the supplied text is NAN/INF/+INF/-INF, i.e. not a number
// - as returned by ExtendedToShort/DoubleToShort textual conversion
// - such values do appear as IEEE floating points, but are not defined in JSON
function FloatToShortNan(const s: ShortString): TFloatNan;
{$ifdef HASINLINE}inline;{$endif}
/// check if the supplied text is NAN/INF/+INF/-INF, i.e. not a number
// - as returned e.g. by ExtendedToStr/DoubleToStr textual conversion
// - such values do appear as IEEE floating points, but are not defined in JSON
function FloatToStrNan(const s: RawUtf8): TFloatNan;
{$ifdef HASINLINE}inline;{$endif}
/// convert a floating-point value to its numerical text equivalency
function ExtendedToStr(Value: TSynExtended; Precision: integer): RawUtf8; overload;
/// convert a floating-point value to its numerical text equivalency
procedure ExtendedToStr(Value: TSynExtended; Precision: integer;
var result: RawUtf8); overload;
/// recognize if the supplied text is NAN/INF/+INF/-INF, i.e. not a number
// - returns the number as text (stored into tmp variable), or "Infinity",
// "-Infinity", and "NaN" for corresponding IEEE special values
// - result is a PShortString either over tmp, or JSON_NAN[]
function FloatToJsonNan(s: PShortString): PShortString;
{$ifdef HASINLINE}inline;{$endif}
/// convert a floating-point value to its JSON text equivalency
// - depending on the platform, it may either call str() or FloatToText()
// in ffGeneral mode (the shortest possible decimal string using fixed or
// scientific format)
// - returns the number as text (stored into tmp variable), or "Infinity",
// "-Infinity", and "NaN" for corresponding IEEE special values
// - result is a PShortString either over tmp, or JSON_NAN[]
function ExtendedToJson(tmp: PShortString; Value: TSynExtended;
Precision: integer; NoExp: boolean): PShortString;
/// convert a 64-bit floating-point value to its numerical text equivalency
// - on Delphi Win32, calls FloatToText() in ffGeneral mode
// - on other platforms, i.e. Delphi Win64 and all FPC targets, will use our own
// faster Fabian Loitsch's Grisu algorithm implementation
// - returns the count of chars stored into S, i.e. length(S)
function DoubleToShort(S: PShortString; const Value: double): integer;
/// convert a 64-bit floating-point value to its numerical text equivalency
// without scientific notation
// - on Delphi Win32, calls FloatToText() in ffGeneral mode
// - on other platforms, i.e. Delphi Win64 and all FPC targets, will use our own
// faster Fabian Loitsch's Grisu algorithm implementation
// - returns the count of chars stored into S, i.e. length(S)
function DoubleToShortNoExp(S: PShortString; const Value: double): integer;
{$ifdef DOUBLETOSHORT_USEGRISU}
const
// special text returned if the double is not a number
C_STR_INF: string[3] = 'Inf';
C_STR_QNAN: string[3] = 'Nan';
// min_width parameter special value, as used internally by FPC for str(d,s)
// - DoubleToAscii() only accept C_NO_MIN_WIDTH or 0 for min_width: space
// trailing has been removed in this cut-down version
C_NO_MIN_WIDTH = -32767;
/// raw function to convert a 64-bit double into a ShortString, stored in str
// - implements Fabian Loitsch's Grisu algorithm dedicated to double values
// - currently, this unit only set min_width=0 (for DoubleToShortNoExp to avoid
// any scientific notation ) or min_width=C_NO_MIN_WIDTH (for DoubleToShort to
// force the scientific notation when the double cannot be represented as
// a simple fractinal number)
procedure DoubleToAscii(min_width, frac_digits: integer;
const v: double; str: PAnsiChar);
{$endif DOUBLETOSHORT_USEGRISU}
/// convert a 64-bit floating-point value to its JSON text equivalency
// - on Delphi Win32, calls FloatToText() in ffGeneral mode
// - on other platforms, i.e. Delphi Win64 and all FPC targets, will use our own
// faster Fabian Loitsch's Grisu algorithm
// - returns the number as text (stored into tmp variable), or "Infinity",
// "-Infinity", and "NaN" for corresponding IEEE special values
// - result is a PShortString either over tmp, or JSON_NAN[]
function DoubleToJson(tmp: PShortString; Value: double;
NoExp: boolean): PShortString;
/// convert a 64-bit floating-point value to its numerical text equivalency
function DoubleToStr(Value: Double): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert a 64-bit floating-point value to its numerical text equivalency
procedure DoubleToStr(Value: Double; var result: RawUtf8); overload;
/// copy a floating-point text buffer with proper correction and validation
// - will correct on the fly '.5' -> '0.5' and '-.5' -> '-0.5'
// - will end not only on #0 but on any char not matching 1[.2[e[-]3]] pattern
// - is used when the input comes from a third-party source with no regular
// output, e.g. a database driver, via TTextWriter.AddFloatStr
function FloatStrCopy(s, d: PUtf8Char): PUtf8Char;
/// fast conversion of 2 digit characters into a 0..99 value
// - returns FALSE on success, TRUE if P^ is not correct
function Char2ToByte(P: PUtf8Char; out Value: cardinal;
ConvertHexToBinTab: PByteArray): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion of 3 digit characters into a 0..9999 value
// - returns FALSE on success, TRUE if P^ is not correct
function Char3ToWord(P: PUtf8Char; out Value: cardinal;
ConvertHexToBinTab: PByteArray): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion of 4 digit characters into a 0..9999 value
// - returns FALSE on success, TRUE if P^ is not correct
function Char4ToWord(P: PUtf8Char; out Value: cardinal;
ConvertHexToBinTab: PByteArray): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// convert any Variant into UTF-8 encoded String
// - use VariantSaveJson() instead if you need a conversion to JSON with
// custom parameters
// - note: null will be returned as 'null'
function VariantToUtf8(const V: Variant): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert any Variant into UTF-8 encoded String
// - use VariantSaveJson() instead if you need a conversion to JSON with
// custom parameters
// - note: null will be returned as 'null'
function ToUtf8(const V: Variant): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert any Variant/TVarData into UTF-8 encoded String
// - use VariantSaveJson() instead if you need a conversion to JSON with
// custom parameters
// - note: null will be returned as 'null'
function ToUtf8(const V: TVarData): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert any Variant into UTF-8 encoded String
// - use VariantSaveJson() instead if you need a conversion to JSON with
// custom parameters
// - wasString is set if the V value was a text
// - empty and null variants will be stored as 'null' text - as expected by JSON
// - custom variant types (e.g. TDocVariant) will be stored as JSON
procedure VariantToUtf8(const V: Variant; var result: RawUtf8;
var wasString: boolean); overload;
/// convert any Variant into UTF-8 encoded String
// - use VariantSaveJson() instead if you need a conversion to JSON with
// custom parameters
// - returns TRUE if the V value was a text, FALSE if was not (e.g. a number)
// - empty and null variants will be stored as 'null' text - as expected by JSON
// - custom variant types (e.g. TDocVariant) will be stored as JSON
function VariantToUtf8(const V: Variant; var Text: RawUtf8): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert any non-null Variant into UTF-8 encoded String
// - empty and null variants will return false
function VariantToText(const V: Variant; var Text: RawUtf8): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// save a variant value into a JSON content
// - just a wrapper around the _VariantSaveJson procedure redirection
function VariantSaveJson(const Value: variant;
Escape: TTextWriterKind = twJsonEscape): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// save a variant value into a JSON content
// - just a wrapper around the _VariantSaveJson procedure redirection
procedure VariantSaveJson(const Value: variant; Escape: TTextWriterKind;
var result: RawUtf8); overload;
{$ifdef HASINLINE}inline;{$endif}
/// internal low-level function to compare two variants with RawUt8 conversion
// - as used e.g. by FastVarDataComp() for complex or diverse VType
function VariantCompAsText(A, B: PVarData; caseInsensitive: boolean): integer;
var
/// save a variant value into a JSON content
// - is implemented by mormot.core.json.pas and mormot.core.variants.pas:
// will raise an exception if none of these units is included in the project
// - follows the TTextWriter.AddVariant() and VariantLoadJson() format
// - is able to handle simple and custom variant types, for instance:
// ! VariantSaveJson(1.5)='1.5'
// ! VariantSaveJson('test')='"test"'
// ! o := _Json('{ BSON: [ "test", 5.05, 1986 ] }');
// ! VariantSaveJson(o)='{"BSON":["test",5.05,1986]}'
// ! o := _Obj(['name','John','doc',_Obj(['one',1,'two',_Arr(['one',2])])]);
// ! VariantSaveJson(o)='{"name":"John","doc":{"one":1,"two":["one",2]}}'
// - note that before Delphi 2009, any varString value is expected to be
// a RawUtf8 instance - which does make sense in the mORMot area
_VariantSaveJson: procedure(const Value: variant; Escape: TTextWriterKind;
var result: RawUtf8);
/// unserialize a JSON content into a variant
// - is properly implemented by mormot.core.json.pas: if this unit is not
// included in the project, this function is nil
// - used by mormot.core.data.pas RTTI_BINARYLOAD[tkVariant]() for complex types
BinaryVariantLoadAsJson: procedure(var Value: variant; Json: PUtf8Char;
TryCustomVariant: pointer);
/// write a TDateTime into strict ISO-8601 date and/or time text
// - is implemented by DateTimeToIso8601TextVar from mormot.core.datetime.pas:
// if this unit is not included in the project, an ESynException is raised
// - used by VariantToUtf8() for TDateTime conversion
_VariantToUtf8DateTimeToIso8601: procedure(DT: TDateTime; FirstChar: AnsiChar;
var result: RawUtf8; WithMS: boolean);
/// Date/Time conversion from ISO-8601 text
// - is implemented by Iso8601ToDateTime() from mormot.core.datetime.pas:
// if this unit is not included in the project, this function is nil
// - used by TRttiProp.SetValue() for TDateTime properties with a getter
_Iso8601ToDateTime: function(const iso: RawByteString): TDateTime;
type
/// used e.g. by UInt4DigitsToShort/UInt3DigitsToShort/UInt2DigitsToShort
// - such result type would avoid a string allocation on heap
TShort4 = string[4];
/// revert the value as encoded by TTextWriter.AddInt18ToChars3() or Int18ToChars3()
// - no range check is performed: you should ensure that the incoming text
// follows the expected 3-chars layout
function Chars3ToInt18(P: pointer): cardinal;
{$ifdef HASINLINE}inline;{$endif}
/// compute the value as encoded by TTextWriter.AddInt18ToChars3() method
function Int18ToChars3(Value: cardinal): RawUtf8; overload;
/// compute the value as encoded by TTextWriter.AddInt18ToChars3() method
procedure Int18ToChars3(Value: cardinal; var result: RawUtf8); overload;
/// creates a 3 digits string from a 0..999 value as '000'..'999'
// - consider using UInt3DigitsToShort() to avoid temporary memory allocation,
// e.g. when used as FormatUtf8() parameter
function UInt3DigitsToUtf8(Value: cardinal): RawUtf8;
/// creates a 4 digits string from a 0..9999 value as '0000'..'9999'
// - consider using UInt4DigitsToShort() to avoid temporary memory allocation,
// e.g. when used as FormatUtf8() parameter
function UInt4DigitsToUtf8(Value: cardinal): RawUtf8;
/// creates a 4 digits short string from a 0..9999 value
// - using TShort4 as returned string would avoid a string allocation on heap
// - could be used e.g. as parameter to FormatUtf8()
function UInt4DigitsToShort(Value: cardinal): TShort4;
/// creates a 3 digits short string from a 0..999 value
// - using TShort4 as returned string would avoid a string allocation on heap
// - could be used e.g. as parameter to FormatUtf8()
function UInt3DigitsToShort(Value: cardinal): TShort4;
/// creates a 2 digits short string from a 0..99 value
// - using TShort4 as returned string would avoid a string allocation on heap
// - could be used e.g. as parameter to FormatUtf8()
function UInt2DigitsToShort(Value: byte): TShort4;
{$ifdef HASINLINE}inline;{$endif}
/// creates a 2 digits short string from a 0..99 value
// - won't test Value>99 as UInt2DigitsToShort()
function UInt2DigitsToShortFast(Value: byte): TShort4;
{$ifdef HASINLINE}inline;{$endif}
/// convert an IPv4 'x.x.x.x' text into its 32-bit value
// - result is in little endian order, not network order: 1.2.3.4 becomes $04030201
// - returns TRUE if the text was a valid IPv4 text, unserialized as 32-bit aValue
// - returns FALSE on parsing error, also setting aValue=0
// - '' or '127.0.0.1' will also return false
function IPToCardinal(aIP: PUtf8Char; out aValue: cardinal): boolean; overload;
/// convert an IPv4 'x.x.x.x' text into its 32-bit value
// - result is in little endian order, not network order: 1.2.3.4 becomes $04030201
// - returns TRUE if the text was a valid IPv4 text, unserialized as 32-bit aValue
// - returns FALSE on parsing error, also setting aValue=0
// - '' or '127.0.0.1' will also return false
function IPToCardinal(const aIP: RawUtf8; out aValue: cardinal): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert an IPv4 'x.x.x.x' text into its 32-bit value, 0 or localhost
// - result is in little endian order, not network order: 1.2.3.4 becomes $04030201
// - returns <> 0 value if the text was a valid IPv4 text, 0 on parsing error
// - '' or '127.0.0.1' will also return 0
function IPToCardinal(const aIP: RawUtf8): cardinal; overload;
{$ifdef HASINLINE}inline;{$endif}
{ ************ Text Formatting functions }
type
/// a memory structure which avoids a temporary RawUtf8 allocation
// - used by VarRecToTempUtf8/VariantToTempUtf8 and FormatUtf8/FormatShort
TTempUtf8 = record
Len: PtrInt;
Text: PUtf8Char;
TempRawUtf8: pointer;
Temp: array[0..23] of AnsiChar;
end;
PTempUtf8 = ^TTempUtf8;
/// convert any Variant into a JSON-compatible UTF-8 encoded temporary buffer
// - this function would allocate a RawUtf8 in Res.TempRawUtf8 only if needed,
// but use the supplied Res.Temp[] buffer for numbers to text conversion -
// caller should ensure to make RawUtf8(Res.TempRawUtf8) := '' once done with it
// - wasString is set if the V value was a text
// - empty and null variants will be stored as 'null' text - as expected by JSON
// - booleans will be stored as 'true' or 'false' - as expected by JSON
// - custom variant types (e.g. TDocVariant) will be stored as JSON
procedure VariantToTempUtf8(const V: variant; var Res: TTempUtf8;
var wasString: boolean);
const
/// which TVarRec.VType are numbers, i.e. don't need to be quoted
// - vtVariant is a number by default, unless detected e.g. by VariantToUtf8()
vtNotString = [vtBoolean, vtInteger, vtInt64, {$ifdef FPC} vtQWord, {$endif}
vtCurrency, vtExtended, vtVariant];
/// convert an open array (const Args: array of const) argument to an UTF-8
// encoded text
// - note that, due to a Delphi compiler limitation, cardinal values should be
// type-casted to Int64() (otherwise the integer mapped value will be converted)
// - any supplied TObject instance will be written as their class name
procedure VarRecToUtf8(const V: TVarRec; var result: RawUtf8;
wasString: PBoolean = nil);
/// convert an open array (const Args: array of const) argument to an UTF-8
// encoded text, using a specified temporary buffer
// - this function would allocate a RawUtf8 in Res.TempRawUtf8 only if needed,
// but use the supplied Res.Temp[] buffer for numbers to text conversion -
// caller should ensure to make RawUtf8(Res.TempRawUtf8) := '' once done with it
// - it would return the number of UTF-8 bytes, i.e. Res.Len
// - note that, due to a Delphi compiler limitation, cardinal values should be
// type-casted to Int64() (otherwise the integer mapped value will be converted)
// - any supplied TObject instance will be written as their class name
function VarRecToTempUtf8(const V: TVarRec; var Res: TTempUtf8;
wasString: PBoolean = nil): PtrInt;
/// convert an open array (const Args: array of const) argument to an UTF-8
// encoded text, returning FALSE if the argument was not a string value
function VarRecToUtf8IsString(const V: TVarRec; var value: RawUtf8): boolean;
{$ifdef HASINLINE}inline;{$endif}
/// convert an open array (const Args: array of const) argument to an Int64
// - returns TRUE and set Value if the supplied argument is a vtInteger, vtInt64
// or vtBoolean
// - returns FALSE if the argument is not an integer
// - note that, due to a Delphi compiler limitation, cardinal values should be
// type-casted to Int64() (otherwise the integer mapped value will be converted)
function VarRecToInt64(const V: TVarRec; out value: Int64): boolean;
/// convert an open array (const Args: array of const) argument to a floating
// point value
// - returns TRUE and set Value if the supplied argument is a number (e.g.
// vtInteger, vtInt64, vtCurrency or vtExtended)
// - returns FALSE if the argument is not a number
// - note that, due to a Delphi compiler limitation, cardinal values should be
// type-casted to Int64() (otherwise the integer mapped value will be converted)
function VarRecToDouble(const V: TVarRec; out value: double): boolean;
/// convert an open array (const Args: array of const) argument to a value
// encoded as with :(...): inlined parameters in FormatUtf8(Format,Args,Params)
// - note that, due to a Delphi compiler limitation, cardinal values should be
// type-casted to Int64() (otherwise the integer mapped value will be converted)
// - any supplied TObject instance will be written as their class name
procedure VarRecToInlineValue(const V: TVarRec; var result: RawUtf8);
/// get an open array (const Args: array of const) character argument
// - only handle varChar and varWideChar kind of arguments
function VarRecAsChar(const V: TVarRec): integer;
{$ifdef HASINLINE}inline;{$endif}
/// check if a supplied "array of const" argument is an instance of a given class
function VarRecAs(const aArg: TVarRec; aClass: TClass): pointer;
/// fast Format() function replacement, optimized for RawUtf8
// - only supported token is %, which will be written in the resulting string
// according to each Args[] supplied items - so you will never get any exception
// as with the SysUtils.Format() when a specifier is incorrect
// - resulting string has no length limit and uses fast concatenation
// - there is no escape char, so to output a '%' character, you need to use '%'
// as place-holder, and specify '%' as value in the Args array
// - note that, due to a Delphi compiler limitation, cardinal values should be
// type-casted to Int64() (otherwise the integer mapped value will be converted)
// - any supplied TObject instance will be written as their class name
// - see FormatSql() and FormatJson() from mormot.core.json for ? placeholders
function FormatUtf8(const Format: RawUtf8; const Args: array of const): RawUtf8; overload;
/// fast Format() function replacement, optimized for RawUtf8
// - overloaded function, which avoid a temporary RawUtf8 instance on stack
procedure FormatUtf8(const Format: RawUtf8; const Args: array of const;
out result: RawUtf8); overload;
/// fast Format() function replacement, tuned for direct memory buffer write
// - use the same single token % (and implementation) than FormatUtf8()
// - returns the number of UTF-8 bytes appended to Dest^
function FormatBuffer(const Format: RawUtf8; const Args: array of const;
Dest: pointer; DestLen: PtrInt): PtrInt;
/// fast Format() function replacement, for UTF-8 content stored in ShortString
// - use the same single token % (and implementation) than FormatUtf8()
// - ShortString allows fast stack allocation, so is perfect for small content
// - truncate result if the text size exceeds 255 bytes
procedure FormatShort(const Format: RawUtf8; const Args: array of const;
var result: ShortString);
/// fast Format() function replacement, for UTF-8 content stored in ShortString
function FormatToShort(const Format: RawUtf8; const Args: array of const): ShortString;
/// fast Format() function replacement, tuned for small content
// - use the same single token % (and implementation) than FormatUtf8()
procedure FormatString(const Format: RawUtf8; const Args: array of const;
out result: string); overload;
/// fast Format() function replacement, tuned for small content
// - use the same single token % (and implementation) than FormatUtf8()
function FormatString(const Format: RawUtf8; const Args: array of const): string; overload;
{$ifdef FPC}inline;{$endif} // Delphi don't inline "array of const" parameters
/// fast Format() function replacement, for UTF-8 content stored in TShort16
// - truncate result if the text size exceeds 16 bytes
procedure FormatShort16(const Format: RawUtf8; const Args: array of const;
var result: TShort16);
/// fast Format() function replacement, for UTF-8 content stored in variant
function FormatVariant(const Format: RawUtf8; const Args: array of const): variant;
/// concatenate several arguments into an UTF-8 string
function Make(const Args: array of const): RawUtf8; overload;
/// concatenate several arguments into an UTF-8 string
procedure Make(const Args: array of const; var Result: RawUtf8); overload;
/// concatenate several arguments into a RTL string
function MakeString(const Args: array of const): string;
/// append some text items to a RawUtf8 variable
// - see also AppendLine() below if you need a separator
procedure Append(var Text: RawUtf8; const Args: array of const); overload;
/// append one text item to a RawUtf8 variable with no code page conversion
procedure Append(var Text: RawUtf8; const Added: RawByteString); overload;
{$ifdef HASINLINE} inline; {$endif}
/// append two text items to a RawUtf8 variable with no code page conversion
procedure Append(var Text: RawUtf8; const Added1, Added2: RawByteString); overload;
/// append one char to a RawUtf8 variable with no code page conversion
procedure Append(var Text: RawUtf8; Added: AnsiChar); overload;
{$ifdef HASINLINE} inline; {$endif}
/// append one text buffer to a RawUtf8 variable with no code page conversion
procedure Append(var Text: RawUtf8; Added: pointer; AddedLen: PtrInt); overload;
{$ifdef HASINLINE} inline; {$endif}
/// append some text items to a RawByteString variable
procedure Append(var Text: RawByteString; const Args: array of const); overload;
/// append one text item to a RawByteString variable with no code page conversion
procedure Append(var Text: RawByteString; const Added: RawByteString); overload;
{$ifdef HASINLINE} inline; {$endif}
/// append one text buffer to a RawByteString variable with no code page conversion
procedure Append(var Text: RawByteString; Added: pointer; AddedLen: PtrInt); overload;
/// prepend some text to a RawByteString variable with no code page conversion
procedure Prepend(var Text: RawByteString; const Added: RawByteString); overload;
/// prepend one char to a RawByteString variable with no code page conversion
procedure Prepend(var Text: RawByteString; Added: AnsiChar); overload;
/// prepend some text items at the beginning of a RawUtf8 variable
procedure Prepend(var Text: RawUtf8; const Args: array of const); overload;
/// prepend some text items at the beginning of a RawByteString variable
procedure Prepend(var Text: RawByteString; const Args: array of const); overload;
/// append some text to a RawUtf8, ensuring previous text is separated with CRLF
// - could be used e.g. to update HTTP headers
procedure AppendLine(var Text: RawUtf8; const Args: array of const;
const Separator: shortstring = #13#10);
/// append some path parts into a single file name with proper path delimiters
// - set EndWithDelim=true if you want to create e.g. a full folder name
// - similar to os.path.join() in the Python RTL
// - e.g. on Windows: MakePath(['abc', 1, 'toto.json']) = 'abc\1\toto.json'
function MakePath(const Part: array of const; EndWithDelim: boolean = false;
Delim: AnsiChar = PathDelim): TFileName;
/// MakePath() variant which can handle the file extension specifically
function MakeFileName(const Part: array of const; LastIsExt: boolean = true): TFileName;
/// create a CSV text from some values
function MakeCsv(const Value: array of const; EndWithComma: boolean = false;
Comma: AnsiChar = ','): RawUtf8;
/// direct conversion of a RTL string into a console OEM-encoded String
// - under Windows, will use the CP_OEMCP encoding
// - under Linux, will expect the console to be defined with UTF-8 encoding
function StringToConsole(const S: string): RawByteString;
/// write some text to the console using a given color
procedure ConsoleWrite(const Fmt: RawUtf8; const Args: array of const;
Color: TConsoleColor = ccLightGray; NoLineFeed: boolean = false); overload;
/// write some text to the console using a given color
procedure ConsoleWrite(const Args: array of const;
Color: TConsoleColor = ccLightGray; NoLineFeed: boolean = false); overload;
/// could be used in the main program block of a console application to
// handle unexpected fatal exceptions
// - WaitForEnterKey=true won't do anything on POSIX (to avoid locking a daemon)
// - typical use may be:
// !begin
// ! try
// ! ... // main console process
// ! except
// ! on E: Exception do
// ! ConsoleShowFatalException(E);
// ! end;
// !end.
procedure ConsoleShowFatalException(E: Exception; WaitForEnterKey: boolean = true);
{ ************ Resource and Time Functions }
/// convert a size to a human readable value power-of-two metric value
// - append EB, PB, TB, GB, MB, KB or B symbol with or without preceding space
// - for EB, PB, TB, GB, MB and KB, add one fractional digit
procedure KB(bytes: Int64; out result: TShort16; nospace: boolean); overload;
/// convert a size to a human readable value
// - append EB, PB, TB, GB, MB, KB or B symbol with preceding space
// - for EB, PB, TB, GB, MB and KB, add one fractional digit
function KB(bytes: Int64): TShort16; overload;
{$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
/// convert a size to a human readable value
// - append EB, PB, TB, GB, MB, KB or B symbol without preceding space
// - for EB, PB, TB, GB, MB and KB, add one fractional digit
function KBNoSpace(bytes: Int64): TShort16;
{$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
/// convert a size to a human readable value
// - append EB, PB, TB, GB, MB, KB or B symbol with or without preceding space
// - for EB, PB, TB, GB, MB and KB, add one fractional digit
function KB(bytes: Int64; nospace: boolean): TShort16; overload;
{$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
/// convert a string size to a human readable value
// - append EB, PB, TB, GB, MB, KB or B symbol
// - for EB, PB, TB, GB, MB and KB, add one fractional digit
function KB(const buffer: RawByteString): TShort16; overload;
{$ifdef FPC_OR_UNICODE}inline;{$endif}
/// convert a size to a human readable value
// - append EB, PB, TB, GB, MB, KB or B symbol
// - for EB, PB, TB, GB, MB and KB, add one fractional digit
procedure KBU(bytes: Int64; var result: RawUtf8);
/// convert a count to a human readable value power-of-two metric value
// - append E, P, T, G, M, K symbol, with one fractional digit
procedure K(value: Int64; out result: TShort16); overload;
/// convert a count to a human readable value power-of-two metric value
// - append E, P, T, G, M, K symbol, with one fractional digit
function K(value: Int64): TShort16; overload;
{$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
/// convert a seconds elapsed time into a human readable value
// - append 's', 'm', 'h' and 'd' symbol for the given value range,
// with two fractional digits
function SecToString(S: QWord): TShort16;
{$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
/// convert a milliseconds elapsed time into a human readable value
// - append 'ms', 's', 'm', 'h' and 'd' symbol for the given value range,
// with two fractional digits
function MilliSecToString(MS: QWord): TShort16;
{$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
/// convert a micro seconds elapsed time into a human readable value
// - append 'us', 'ms', 's', 'm', 'h' and 'd' symbol for the given value range,
// with two fractional digits
function MicroSecToString(Micro: QWord): TShort16; overload;
{$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
/// compute elapsed time into a human readable value, from a Start value
// - will get current QueryPerformanceMicroSeconds() and compute against Start
// - append 'us', 'ms', 's', 'm', 'h' and 'd' symbol for the given value range,
// with two fractional digits
function MicroSecFrom(Start: QWord): TShort16;
{$ifdef FPC_OR_UNICODE}inline;{$endif} // Delphi 2007 is buggy as hell
/// convert a micro seconds elapsed time into a human readable value
// - append 'us', 'ms', 's', 'm', 'h' and 'd' symbol for the given value range,
// with two fractional digits
procedure MicroSecToString(Micro: QWord; out result: TShort16); overload;
/// convert a nano seconds elapsed time into a human readable value
// - append 'ns', 'us', 'ms', 's', 'm', 'h' and 'd' symbol for the given value
// range, with two fractional digits
procedure NanoSecToString(Nano: QWord; out result: TShort16);
/// convert "valueunit" values into x or x.xx text with up to 2 digits
// - supplied value should be the actual unit value * 100
procedure By100ToTwoDigitString(value: cardinal; const valueunit: ShortString;
var result: TShort16);
/// convert an integer value into its textual representation with thousands marked
// - ThousandSep is the character used to separate thousands in numbers with
// more than three digits to the left of the decimal separator
function IntToThousandString(Value: integer;
const ThousandSep: TShort4 = ','): ShortString;
{ ************ ESynException class }
{$ifndef NOEXCEPTIONINTERCEPT}
type
/// global hook callback to customize exceptions logged by TSynLog
// - should return TRUE if all needed information has been logged by the
// event handler
// - should return FALSE if Context.EAddr and Stack trace is to be appended
TSynLogExceptionToStr = function(WR: TTextWriter;
const Context: TSynLogExceptionContext): boolean;
var
/// allow to customize the ESynException logging message
TSynLogExceptionToStrCustom: TSynLogExceptionToStr = nil;
/// the default Exception handler for logging
// - defined here to be called e.g. by ESynException.CustomLog() as default
function DefaultSynLogExceptionToStr(WR: TTextWriter;
const Context: TSynLogExceptionContext): boolean;
{$endif NOEXCEPTIONINTERCEPT}
type
{$M+}
/// generic parent class of all custom Exception types of this unit
// - all our classes inheriting from ESynException are serializable,
// so you could use ObjectToJsonDebug(anyESynException) to retrieve some
// extended information
ESynException = class(Exception)
protected
fRaisedAt: pointer;
fMessageUtf8: RawUtf8;
procedure CreateAfterSetMessageUtf8; virtual;
public
/// constructor which will use FormatUtf8() instead of Format()
// - expect % as delimiter, so is less error prone than %s %d %g
// - will handle vtPointer/vtClass/vtObject/vtVariant kind of arguments,
// appending class name for any class or object, the hexa value for a
// pointer, or the JSON representation of any supplied TDocVariant
constructor CreateUtf8(const Format: RawUtf8; const Args: array of const);
/// constructor will accept RawUtf8 instead of string as message text
constructor CreateU(const Msg: RawUtf8);
/// constructor appending some FormatUtf8() content to the GetLastError
// - message will contain GetLastError value followed by the formatted text
// - expect % as delimiter, so is less error prone than %s %d %g
// - will handle vtPointer/vtClass/vtObject/vtVariant kind of arguments,
// appending class name for any class or object, the hexa value for a
// pointer, or the JSON representation of any supplied TDocVariant
constructor CreateLastOSError(const Format: RawUtf8; const Args: array of const;
const Trailer: ShortString = 'OSError');
{$ifndef NOEXCEPTIONINTERCEPT}
/// can be used to customize how the exception is logged
// - this default implementation will call the TSynLogExceptionToStrCustom
// global callback, if defined, or a default handler internal to this unit
// - override this method to provide a custom logging content
// - should return TRUE if Context.EAddr and Stack trace is not to be
// written (i.e. as for any TSynLogExceptionToStr callback)
function CustomLog(WR: TTextWriter;
const Context: TSynLogExceptionContext): boolean; virtual;
{$endif NOEXCEPTIONINTERCEPT}
/// the code location when this exception was triggered
// - populated by mormot.core.log unit, during interception - so may be nil
// - you can use TDebugFile.FindLocation(ESynException) class function to
// guess the corresponding source code line
// - will be serialized as "Address": hexadecimal and source code location,
// using TDebugFile .map/.dbg/.mab information, by JSON WriteObject
// when woStorePointer option is defined - e.g. with ObjectToJsonDebug()
property RaisedAt: pointer
read fRaisedAt write fRaisedAt;
/// the Exception Message UTF-8 text, as generated by CreateUtf8()
property MessageUtf8: RawUtf8
read fMessageUtf8;
published
/// the Exception Message string, as defined in parent Exception class
property Message;
end;
{$M-}
/// meta-class of the ESynException hierarchy
ESynExceptionClass = class of ESynException;
/// convert any HTTP_* constant to an integer error code and its English text
// - returns e.g. 'HTTP Error 404 - Not Found', calling StatusCodeToText()
function StatusCodeToErrorMsg(Code: integer): RawUtf8;
{ **************** Hexadecimal Text And Binary Conversion }
type
/// type of a lookup table used for fast hexadecimal conversion
THexToDualByte = packed array[0..511] of byte;
/// type of a lookup table used for fast XML/HTML conversion
TAnsiCharToByte = array[AnsiChar] of byte;
PAnsiCharToByte = ^TAnsiCharToByte;
/// type of a lookup table used for fast two-digit chars conversion
TAnsiCharToWord = array[AnsiChar] of word;
PAnsiCharToWord = ^TAnsiCharToWord;
/// type of a lookup table used for fast two-digit chars conversion
TByteToWord = array[byte] of word;
PByteToWord = ^TByteToWord;
var
/// a conversion table from hexa chars into binary data
// - [0..255] range maps the 0..15 binary, [256..511] maps 0..15 binary shl 4
// - returns 255 for any character out of 0..9,A..Z,a..z range
// - used e.g. by HexToBin() function
// - is defined globally, since may be used from an inlined function
ConvertHexToBin: THexToDualByte;
/// fast lookup table for converting hexadecimal numbers from 0 to 15
// into their ASCII equivalence
// - is local for better code generation
TwoDigitsHex: array[byte] of array[1..2] of AnsiChar;
TwoDigitsHexW: TAnsiCharToWord absolute TwoDigitsHex;
TwoDigitsHexWB: TByteToWord absolute TwoDigitsHex;
/// lowercase hexadecimal lookup table
TwoDigitsHexLower: array[byte] of array[1..2] of AnsiChar;
TwoDigitsHexWLower: TAnsiCharToWord absolute TwoDigitsHexLower;
TwoDigitsHexWBLower: TByteToWord absolute TwoDigitsHexLower;
/// fast conversion from hexa chars into binary data
// - BinBytes contain the bytes count to be converted: Hex^ must contain
// at least BinBytes*2 chars to be converted, and Bin^ enough space
// - if Bin=nil, no output data is written, but the Hex^ format is checked
// - return false if any invalid (non hexa) char is found in Hex^
// - using this function with Bin^ as an integer value will decode in big-endian
// order (most-signignifican byte first)
function HexToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: PtrInt): boolean; overload;
/// fast conversion with no validity check from hexa chars into binary data
procedure HexToBinFast(Hex: PAnsiChar; Bin: PByte; BinBytes: PtrInt);
/// fast conversion from one hexa char pair into a 8-bit AnsiChar
// - return false if any invalid (non hexa) char is found in Hex^
// - similar to HexToBin(Hex,nil,1)
function HexToCharValid(Hex: PAnsiChar): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// internal conversion from hexa pair into a AnsiChar for PIC, ARM and x86_64
function HexToCharValid(Hex: PAnsiChar; HexToBin: PByteArray): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast check if the supplied Hex buffer is an hexadecimal representation
// of a binary buffer of a given number of bytes
function IsHex(const Hex: RawByteString; BinBytes: PtrInt): boolean;
/// fast conversion from one hexa char pair into a 8-bit AnsiChar
// - return false if any invalid (non hexa) char is found in Hex^
// - similar to HexToBin(Hex,Bin,1) but with Bin<>nil
// - use HexToCharValid if you want to check a hexadecimal char content
function HexToChar(Hex: PAnsiChar; Bin: PUtf8Char): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// internal conversion from hexa pair into a AnsiChar for PIC, ARM and x86_64
function HexToChar(Hex: PAnsiChar; Bin: PUtf8Char; HexToBin: PByteArray): boolean; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion from two hexa bytes into a 16-bit UTF-16 WideChar
// - as used by JsonUnicodeEscapeToUtf8() for \u#### chars unescape
// - similar to HexDisplayToBin(Hex,@wordvar,2)
// - returns 0 on malformated input
function HexToWideChar(Hex: PUtf8Char): cardinal;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion from binary data into hexa chars
// - BinBytes contain the bytes count to be converted: Hex^ must contain
// enough space for at least BinBytes*2 chars
// - using this function with BinBytes^ as an integer value will encode it
// in low-endian order (less-signignifican byte first): don't use it for display
procedure BinToHex(Bin, Hex: PAnsiChar; BinBytes: PtrInt); overload;
/// fast conversion from hexa chars into binary data
function HexToBin(const Hex: RawUtf8): RawByteString; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion from hexa chars into binary data
function HexToBin(Hex: PAnsiChar; HexLen: PtrInt;
var Bin: RawByteString): boolean; overload;
/// fast conversion from ToHumanHex() hexa chars into binary data
function HumanHexToBin(const hex: RawUtf8; var Bin: RawByteString): boolean; overload;
/// fast conversion from ToHumanHex() hexa chars into binary data
function HumanHexToBin(const hex: RawUtf8): RawByteString; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast comparison between two ToHumanHex() hexa values
function HumanHexCompare(const a, b: RawUtf8): integer; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast comparison between two ToHumanHex() hexa values
function HumanHexCompare(a, b: PUtf8Char): integer; overload;
/// fast conversion from binary data into hexa chars
function BinToHex(const Bin: RawByteString): RawUtf8; overload;
/// fast conversion from binary data into hexa chars
function BinToHex(Bin: PAnsiChar; BinBytes: PtrInt): RawUtf8; overload;
/// fast conversion from binary data into hexa chars, ready to be displayed
// - BinBytes contain the bytes count to be converted: Hex^ must contain
// enough space for at least BinBytes*2 chars
// - using this function with Bin^ as an integer value will encode it
// in big-endian order (most-signignifican byte first): use it for display
procedure BinToHexDisplay(Bin, Hex: PAnsiChar; BinBytes: PtrInt); overload;
/// fast conversion from binary data into hexa chars, ready to be displayed
function BinToHexDisplay(Bin: PAnsiChar; BinBytes: PtrInt): RawUtf8; overload;
/// fast conversion from binary data into lowercase hexa chars
// - BinBytes contain the bytes count to be converted: Hex^ must contain
// enough space for at least BinBytes*2 chars
// - using this function with BinBytes^ as an integer value will encode it
// in low-endian order (less-signignifican byte first): don't use it for display
procedure BinToHexLower(Bin, Hex: PAnsiChar; BinBytes: PtrInt); overload;
/// fast conversion from binary data into lowercase hexa chars
function BinToHexLower(const Bin: RawByteString): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion from binary data into lowercase hexa chars
function BinToHexLower(Bin: PAnsiChar; BinBytes: PtrInt): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion from binary data into lowercase hexa chars
procedure BinToHexLower(Bin: PAnsiChar; BinBytes: PtrInt; var result: RawUtf8); overload;
/// fast conversion from binary data into lowercase hexa chars
// - BinBytes contain the bytes count to be converted: Hex^ must contain
// enough space for at least BinBytes*2 chars
// - using this function with Bin^ as an integer value will encode it
// in big-endian order (most-signignifican byte first): use it for display
procedure BinToHexDisplayLower(Bin, Hex: PAnsiChar; BinBytes: PtrInt); overload;
/// fast conversion from binary data into lowercase hexa chars
function BinToHexDisplayLower(Bin: PAnsiChar; BinBytes: PtrInt): RawUtf8; overload;
/// fast conversion from up to 127 bytes of binary data into lowercase hexa chars
function BinToHexDisplayLowerShort(Bin: PAnsiChar; BinBytes: PtrInt): ShortString;
/// fast conversion from up to 64-bit of binary data into lowercase hexa chars
function BinToHexDisplayLowerShort16(Bin: Int64; BinBytes: PtrInt): TShort16;
/// fast conversion from up to 64-bit of binary data into lowercase hexa chars
// - warning: here binary size is in bits (typically 1..64), not bytes
procedure BinBitsToHexDisplayLowerShort16(Bin: Int64; BinBits: PtrInt;
var Result: TShort16);
/// fast conversion from binary data into hexa lowercase chars, ready to be
// used as a convenient TFileName prefix
function BinToHexDisplayFile(Bin: PAnsiChar; BinBytes: PtrInt): TFileName;
/// append one byte as hexadecimal char pairs, into a text buffer
function ByteToHex(P: PAnsiChar; Value: byte): PAnsiChar;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion from a pointer data into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
function PointerToHex(aPointer: Pointer): RawUtf8; overload;
{$ifdef HASINLINE}inline;{$endif}
/// fast conversion from a pointer data into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
procedure PointerToHex(aPointer: Pointer; var result: RawUtf8); overload;
/// fast conversion from a pointer data into hexa chars, ready to be displayed
// - use internally DisplayMinChars() and BinToHexDisplay()
// - such result type would avoid a string allocation on heap
function PointerToHexShort(aPointer: Pointer): TShort16; overload;
/// fast conversion from a cardinal value into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
// - reverse function of HexDisplayToCardinal()
function CardinalToHex(aCardinal: cardinal): RawUtf8;
/// fast conversion from a cardinal value into hexa chars, ready to be displayed
// - use internally BinToHexDisplayLower()
// - reverse function of HexDisplayToCardinal()
function CardinalToHexLower(aCardinal: cardinal): RawUtf8;
/// fast conversion from a cardinal value into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
// - such result type would avoid a string allocation on heap
function CardinalToHexShort(aCardinal: cardinal): TShort16;
/// compute the hexadecimal representation of the crc32 checkum of a given text
// - wrapper around CardinalToHex(crc32c(...))
function crc32cUtf8ToHex(const str: RawUtf8): RawUtf8;
/// fast conversion from a Int64 value into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
// - reverse function of HexDisplayToInt64()
function Int64ToHex(aInt64: Int64): RawUtf8; overload;
/// fast conversion from a Int64 value into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
// - reverse function of HexDisplayToInt64()
procedure Int64ToHex(aInt64: Int64; var result: RawUtf8); overload;
/// fast conversion from a Int64 value into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
// - such result type would avoid a string allocation on heap
procedure Int64ToHexShort(aInt64: Int64; out result: TShort16); overload;
/// fast conversion from a Int64 value into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
// - such result type would avoid a string allocation on heap
function Int64ToHexShort(aInt64: Int64): TShort16; overload;
/// fast conversion for up to 256-bit of little-endian input into non-zero hexa
// - Len should be <= 32 bytes, to fit in a TShort64 result
// - use internally DisplayMinChars() and BinToHexDisplay()
function ToHexShort(P: pointer; Len: PtrInt): TShort64;
/// fast conversion from a pointer data into hexa chars, ready to be displayed
// - use internally DisplayMinChars() and BinToHexDisplay()
function Int64ToHexLower(aInt64: Int64): RawUtf8; overload;
/// fast conversion from a Int64 value into hexa chars, ready to be displayed
// - use internally BinToHexDisplay()
// - reverse function of HexDisplayToInt64()
function Int64ToHexString(aInt64: Int64): string;
/// fast conversion from hexa chars in reverse order into a binary buffer
function HexDisplayToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: PtrInt): boolean;
/// fast conversion from hexa chars in reverse order into a cardinal
// - reverse function of CardinalToHex()
// - returns false and set aValue=0 if Hex is not a valid hexadecimal 32-bit
// unsigned integer
// - returns true and set aValue with the decoded number, on success
function HexDisplayToCardinal(Hex: PAnsiChar; out aValue: cardinal): boolean;
{$ifdef ISDELPHI}{$ifdef HASINLINE}inline;{$endif}{$endif}
// inline gives an error under release conditions with (old?) FPC
/// fast conversion from hexa chars in reverse order into a cardinal
// - reverse function of Int64ToHex()
// - returns false and set aValue=0 if Hex is not a valid hexadecimal 64-bit
// signed integer
// - returns true and set aValue with the decoded number, on success
function HexDisplayToInt64(Hex: PAnsiChar; out aValue: Int64): boolean; overload;
{$ifdef ISDELPHI}{$ifdef HASINLINE}inline;{$endif}{$endif}
{ inline gives an error under release conditions with FPC }
/// fast conversion from hexa chars in reverse order into a cardinal
// - reverse function of Int64ToHex()
// - returns 0 if the supplied text buffer is not a valid hexadecimal 64-bit
// signed integer
function HexDisplayToInt64(const Hex: RawByteString): Int64; overload;
{$ifdef HASINLINE}inline;{$endif}
/// conversion from octal C-like escape into binary data
// - \xxx is converted into a single xxx byte from octal, and \\ into \
// - will stop the conversion when Oct^=#0 or when invalid \xxx is reached
// - returns the number of bytes written to Bin^
function OctToBin(Oct: PAnsiChar; Bin: PByte): PtrInt; overload;
/// conversion from octal C-like escape into binary data
// - \xxx is converted into a single xxx byte from octal, and \\ into \
function OctToBin(const Oct: RawUtf8): RawByteString; overload;
/// append a TGuid binary content as 36 chars text
// - will store e.g. '3F2504E0-4F89-11D3-9A0C-0305E82C3301' (without any {})
// - this will be the format used for JSON encoding, e.g.
// $ { "UID": "C9A646D3-9C61-4CB7-BFCD-EE2522C8F633" }
function GuidToText(P: PUtf8Char; guid: PByteArray): PUtf8Char;
/// convert a TGuid into 38 chars encoded { text } as RawUtf8
// - will return e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
// - if you do not need the embracing { }, use ToUtf8() overloaded function
function GuidToRawUtf8(const guid: TGuid): RawUtf8;
/// convert a TGuid into 36 chars encoded text as RawUtf8
// - will return e.g. '3F2504E0-4F89-11D3-9A0C-0305E82C3301' (without the {})
// - if you need the embracing { }, use GuidToRawUtf8() function instead
function ToUtf8(const guid: TGuid): RawUtf8; overload;
/// convert a TGuid into into 38 chars encoded { text } as RTL string
// - will return e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
// - this version is faster than the one supplied by SysUtils
function GuidToString(const guid: TGuid): string;
type
/// stack-allocated ASCII string, used by GuidToShort() function
TGuidShortString = string[38];
PGuidShortString = ^TGuidShortString;
/// convert a TGuid into text
// - will return e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
// - using a ShortString will allow fast allocation on the stack, so is
// preferred e.g. when providing a Guid to a ESynException.CreateUtf8()
function GuidToShort(const guid: TGuid): TGuidShortString; overload;
{$ifdef HASINLINE}inline;{$endif}
/// convert a TGuid into text
// - will return e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
// - using a ShortString will allow fast allocation on the stack, so is
// preferred e.g. when providing a Guid to a ESynException.CreateUtf8()
procedure GuidToShort(const
guid: TGuid; out dest: TGuidShortString); overload;
/// convert some text into its TGuid binary value
// - expect e.g. '3F2504E0-4F89-11D3-9A0C-0305E82C3301' (without any {}) but
// will ignore internal '-' so '3F2504E04F8911D39A0C0305E82C3301' is also fine
// - note: TGuid binary order does not follow plain HexToBin or HexDisplayToBin
// - return nil if the supplied text buffer is not a valid TGuid
// - this will be the format used for JSON encoding, e.g.
// $ { "Uid": "C9A646D3-9C61-4CB7-BFCD-EE2522C8F633" }
function TextToGuid(P: PUtf8Char; Guid: PByteArray): PUtf8Char;
/// convert some RTL string text into a TGuid
// - expect e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
// - return {00000000-0000-0000-0000-000000000000} if the supplied text buffer
// is not a valid TGuid
function StringToGuid(const text: string): TGuid;
/// convert some UTF-8 encoded text into a TGuid
// - expect e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
// or '3F2504E0-4F89-11D3-9A0C-0305E82C3301' (without the {}) or even
// '3F2504E04F8911D39A0C0305E82C3301' following TGuid order (not HexToBin)
// - return {00000000-0000-0000-0000-000000000000} if the supplied text buffer
// is not a valid TGuid
function RawUtf8ToGuid(const text: RawByteString): TGuid; overload;
/// convert some UTF-8 encoded text into a TGuid
// - expect e.g. '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' (with the {})
// or '3F2504E0-4F89-11D3-9A0C-0305E82C3301' (without the {}) or even
// '3F2504E04F8911D39A0C0305E82C3301' following TGuid order (not HexToBin)
function RawUtf8ToGuid(const text: RawByteString; out guid: TGuid): boolean; overload;
/// trim any space and '{' '-' '}' chars from input to get a 32-char TGuid hexa
// - change in-place the text into lowercase hexadecimal
// - returns true if resulting text is a 128-bit cleaned hexa, false otherwise
function TrimGuid(var text: RawUtf8): boolean;
/// read a TStream content into a String
// - it will read binary or text content from the current position until the
// end (using TStream.Size)
// - uses RawByteString for byte storage, whatever the codepage is
function StreamToRawByteString(aStream: TStream; aSize: Int64 = -1;
aCodePage: integer = CP_RAWBYTESTRING): RawByteString;
/// iterative function to retrieve the new content appended to a stream
// - aPosition should be set to 0 before the initial call
function StreamChangeToRawByteString(
aStream: TStream; var aPosition: Int64): RawByteString;
/// create a TStream from a string content
// - uses RawByteString for byte storage, whatever the codepage is
// - in fact, the returned TStream is a TRawByteString instance, since this
// function is just a wrapper around:
// ! result := TRawByteStringStream.Create(aString);
function RawByteStringToStream(const aString: RawByteString): TStream;
{$ifdef HASINLINE}inline;{$endif}
/// read UTF-8 text from a TStream saved with len prefix by WriteStringToStream
// - format is Length(integer):Text - use StreamToRawByteString for raw data
// - will return '' if there is no such text in the stream
// - you can set a MaxAllowedSize value, if you know how long the size should be
// - it will read from the current position in S: so if you just write into S,
// it could be a good idea to rewind it before call, e.g.:
// ! WriteStringToStream(Stream,aUtf8Text);
// ! Stream.Seek(0,soBeginning);
// ! str := ReadStringFromStream(Stream);
function ReadStringFromStream(S: TStream; MaxAllowedSize: integer = 255): RawUtf8;
/// write an UTF-8 text into a TStream with a len prefix - see ReadStringFromStream
// - format is Length(integer):Text - use RawByteStringToStream for raw data
function WriteStringToStream(S: TStream; const Text: RawUtf8): boolean;
implementation
{$ifdef FPC}
// globally disable some FPC paranoid warnings - rely on x86_64 as reference
{$WARN 4056 off : Conversion between ordinals and pointers is not portable }
{$endif FPC}
{ ************ CSV-like Iterations over Text Buffers }
function IdemPCharAndGetNextItem(var source: PUtf8Char; const searchUp: RawUtf8;
var Item: RawUtf8; Sep: AnsiChar): boolean;
begin
if source <> nil then
if IdemPChar(source, Pointer(searchUp)) then
begin
inc(source, Length(searchUp));
GetNextItem(source, Sep, Item);
result := true;
exit;
end;
result := false;
end;
function GetNextItem(var P: PUtf8Char; Sep: AnsiChar): RawUtf8;
begin
GetNextItem(P, Sep, result);
end;
procedure GetNextItem(var P: PUtf8Char; Sep: AnsiChar; var result: RawUtf8);
var
S: PUtf8Char;
begin
if P = nil then
result := ''
else
begin
S := P;
{$ifdef CPUINTEL}
S := PosChar(S, Sep); // SSE2 asm on i386 and x86_64
if S = nil then
S := P + mormot.core.base.StrLen(P);
{$else}
while (S^ <> #0) and
(S^ <> Sep) do
inc(S);
{$endif CPUINTEL}
FastSetString(result, P, S - P);
if S^ <> #0 then
P := S + 1
else
P := nil;
end;
end;
function GetNextItemMultiple(var P: PUtf8Char; const Sep: RawUtf8;
var Next: RawUtf8): AnsiChar;
var
len: PtrInt;
begin
if P = nil then
begin
Next := '';
result := #0;
end
else
begin
len := strcspn(P, pointer(Sep)); // search size of P which are not in Sep
FastSetString(Next, P, len);
inc(P, len);
result := P^;
if result <> #0 then
inc(P)
else
P := nil;
end;
end;
procedure GetNextItem(var P: PUtf8Char; Sep, Quote: AnsiChar; var result: RawUtf8);
begin
if P = nil then
result := ''
else if P^ = Quote then
begin
P := UnQuoteSqlStringVar(P, result);
if P = nil then
result := ''
else if P^ = #0 then
P := nil
else
inc(P);
end
else
GetNextItem(P, Sep, result);
end;
procedure GetNextItemTrimed(var P: PUtf8Char; Sep: AnsiChar; var result: RawUtf8);
var
S, E: PUtf8Char;
begin
if (P = nil) or
(Sep <= ' ') then
result := ''
else
begin
while (P^ <= ' ') and
(P^ <> #0) do
inc(P); // trim left
S := P;
while (S^ <> #0) and
(S^ <> Sep) do
inc(S);
E := S;
while (E > P) and
(E[-1] in [#1..' ']) do
dec(E); // trim right
FastSetString(result, P, E - P);
if S^ <> #0 then
P := S + 1
else
P := nil;
end;
end;
procedure GetNextItemTrimedCRLF(var P: PUtf8Char; var result: RawUtf8);
var
S, E: PUtf8Char;
begin
if P = nil then
result := ''
else
begin
S := P;
while (S^ <> #0) and
(S^ <> #10) do
inc(S);
E := S;
if (E > P) and
(E[-1] = #13) then
dec(E);
FastSetString(result, P, E - P);
if S^ <> #0 then
P := S + 1
else
P := nil;
end;
end;
function GetNextItemString(var P: PChar; Sep: Char): string;
var
S: PChar;
begin
if P = nil then
result := ''
else
begin
S := P;
while (S^ <> #0) and
(S^ <> Sep) do
inc(S);
SetString(result, P, S - P);
if S^ <> #0 then
P := S + 1
else
P := nil;
end;
end;
function GetFileNameExtIndex(const FileName, CsvExt: TFileName): integer;
var
Ext: TFileName;
P: PChar;
begin
result := -1;
P := pointer(CsvExt);
Ext := ExtractFileExt(FileName);
if (P = nil) or
(Ext = '') or
(Ext[1] <> '.') then
exit;
delete(Ext, 1, 1);
repeat
inc(result);
if SameText(GetNextItemString(P), Ext) then
exit;
until P = nil;
result := -1;
end;
procedure AppendCsvValues(const Csv: string; const Values: array of string;
var Result: string; const AppendBefore: string);
var
s: string;
i, bool: integer;
P: PChar;
first: boolean;
begin
P := pointer(Csv);
if P = nil then
exit;
first := True;
for i := 0 to high(Values) do
begin
s := GetNextItemString(P);
if Values[i] <> '' then
begin
if first then
begin
Result := Result + #13#10;
first := false;
end
else
Result := Result + AppendBefore;
bool := FindCsvIndex('0,-1', RawUtf8(Values[i]));
Result := Result + s + ': ';
if bool < 0 then
Result := Result + Values[i]
else
Result := Result + GetCsvItemString(pointer(GetNextItemString(P)), bool, '/');
end;
end;
end;
procedure GetNextItemShortString(var P: PUtf8Char; Dest: PShortString; Sep: AnsiChar);
var
S, D: PUtf8Char;
c: AnsiChar;
len: PtrInt;
begin
S := P;
D := pointer(Dest); // better FPC codegen with a dedicated variable
if S <> nil then
begin
len := 0;
if S^ <= ' ' then
while (S^ <= ' ') and
(S^ <> #0) do
inc(S); // trim left space
repeat
c := S^;
inc(S);
if c = Sep then
break;
if c <> #0 then
if len < 254 then // avoid shortstring buffer overflow
begin
inc(len);
D[len] := c;
continue;
end
else
len := 0;
S := nil; // reached #0: end of input
break;
until false;
if len <> 0 then
repeat
if D[len] >= ' ' then
break;
dec(len); // trim right space
until len = 0;
D[0] := AnsiChar(len);
D[len + 1] := #0; // trailing #0
P := S;
end
else
PCardinal(D)^ := 0 // Dest='' with trailing #0
end;
function GetNextItemHexDisplayToBin(var P: PUtf8Char;
Bin: PByte; BinBytes: PtrInt; Sep: AnsiChar): boolean;
var
S: PUtf8Char;
len: integer;
begin
result := false;
FillCharFast(Bin^, BinBytes, 0);
if P = nil then
exit;
while (P^ <= ' ') and
(P^ <> #0) do
inc(P);
S := P;
if Sep = #0 then
while S^ > ' ' do
inc(S)
else
while (S^ <> #0) and
(S^ <> Sep) do
inc(S);
len := S - P;
while (P[len - 1] in [#1..' ']) and
(len > 0) do
dec(len); // trim right spaces
if len <> BinBytes * 2 then
exit;
if not HexDisplayToBin(PAnsiChar(P), Bin, BinBytes) then
FillCharFast(Bin^, BinBytes, 0)
else
begin
if S^ = #0 then
P := nil
else if Sep <> #0 then
P := S + 1
else
P := S;
result := true;
end;
end;
function GetNextItemCardinal(var P: PUtf8Char; Sep: AnsiChar): PtrUInt;
var
c: PtrUInt;
begin
if P = nil then
begin
result := 0;
exit;
end;
if P^ = ' ' then
repeat
inc(P)
until P^ <> ' ';
c := byte(P^) - 48;
if c > 9 then
result := 0
else
begin
result := c;
inc(P);
repeat
c := byte(P^) - 48;
if c > 9 then
break
else
result := result * 10 + c;
inc(P);
until false;
end;
if Sep <> #0 then
while (P^ <> #0) and
(P^ <> Sep) do
inc(P); // go to end of CSV item (ignore any decimal)
if P^ = #0 then
P := nil
else if Sep <> #0 then
inc(P);
end;
function GetNextItemCardinalStrict(var P: PUtf8Char): PtrUInt;
var
c: PtrUInt;
begin
if P = nil then
begin
result := 0;
exit;
end;
c := byte(P^) - 48;
if c > 9 then
result := 0
else
begin
result := c;
inc(P);
repeat
c := byte(P^) - 48;
if c > 9 then
break
else
result := result * 10 + c;
inc(P);
until false;
end;
if P^ = #0 then
P := nil;
end;
function CsvOfValue(const Value: RawUtf8; Count: cardinal; const Sep: RawUtf8): RawUtf8;
var
ValueLen, SepLen: PtrUInt;
i: cardinal;
P: PAnsiChar;
begin
// CsvOfValue('?',3)='?,?,?'
result := '';
if Count = 0 then
exit;
ValueLen := length(Value);
SepLen := Length(Sep);
FastSetString(result, ValueLen * Count + SepLen * pred(Count));
P := pointer(result);
i := 1;
repeat
if ValueLen = 1 then
begin
P^ := Value[1]; // optimized for the Value='?' common case
inc(P);
end
else
begin
MoveFast(Pointer(Value)^, P^, ValueLen);
inc(P, ValueLen);
end;
if i = Count then
break;
if SepLen = 1 then
begin
P^ := Sep[1]; // optimized for the Sep=',' most common case
inc(P);
inc(i);
end
else if SepLen > 0 then
begin
MoveFast(Pointer(Sep)^, P^, SepLen);
inc(P, SepLen);
inc(i);
end;
until false;
// assert(P-pointer(result)=length(result));
end;
procedure SetBitCsv(var Bits; BitsCount: integer; var P: PUtf8Char);
var
bit, last: cardinal;
begin
while P <> nil do
begin
bit := GetNextItemCardinalStrict(P) - 1; // '0' marks end of list
if bit >= cardinal(BitsCount) then
break; // avoid GPF
if (P = nil) or
(P^ = ',') then
SetBitPtr(@Bits, bit)
else if P^ = '-' then
begin
inc(P);
last := GetNextItemCardinalStrict(P) - 1; // '0' marks end of list
if last >= cardinal(BitsCount) then
exit;
while bit <= last do
begin
SetBitPtr(@Bits, bit);
inc(bit);
end;
end;
if (P <> nil) and
(P^ = ',') then
inc(P);
end;
if (P <> nil) and
(P^ = ',') then
inc(P);
end;
function GetBitCsv(const Bits; BitsCount: integer): RawUtf8;
var
i, j: integer;
begin
result := '';
i := 0;
while i < BitsCount do
if GetBitPtr(@Bits, i) then
begin
j := i;
while (j + 1 < BitsCount) and
GetBitPtr(@Bits, j + 1) do
inc(j);
result := result + UInt32ToUtf8(i + 1);
if j = i then
result := result + ','
else if j = i + 1 then
result := result + ',' + UInt32ToUtf8(j + 1) + ','
else
result := result + '-' + UInt32ToUtf8(j + 1) + ',';
i := j + 1;
end
else
inc(i);
result := result + '0'; // '0' marks end of list
end;
function GetNextItemCardinalW(var P: PWideChar; Sep: WideChar): PtrUInt;
var
c: PtrUInt;
begin
if P = nil then
begin
result := 0;
exit;
end;
c := word(P^) - 48;
if c > 9 then
result := 0
else
begin
result := c;
inc(P);
repeat
c := word(P^) - 48;
if c > 9 then
break
else
result := result * 10 + c;
inc(P);
until false;
end;
while (P^ <> #0) and
(P^ <> Sep) do // go to end of CSV item (ignore any decimal)
inc(P);
if P^ = #0 then
P := nil
else
inc(P);
end;
function GetNextItemInteger(var P: PUtf8Char; Sep: AnsiChar): PtrInt;
var
minus: boolean;
begin
if P = nil then
begin
result := 0;
exit;
end;
if P^ = ' ' then
repeat
inc(P)
until P^ <> ' ';
if P^ in ['+', '-'] then
begin
minus := P^ = '-';
inc(P);
end
else
minus := false;
result := PtrInt(GetNextItemCardinal(P, Sep));
if minus then
result := -result;
end;
function GetNextTChar64(var P: PUtf8Char; Sep: AnsiChar; out Buf: TChar64): PtrInt;
var
S: PUtf8Char;
c: AnsiChar;
begin
result := 0;
S := P;
if S = nil then
exit;
if Sep = #0 then
repeat // store up to next whitespace
c := S[result];
if c <= ' ' then
break;
Buf[result] := c;
inc(result);
if result >= SizeOf(Buf) then
exit; // avoid buffer overflow
until false
else
repeat // store up to Sep or end of string
c := S[result];
if (c = #0) or
(c = Sep) then
break;
Buf[result] := c;
inc(result);
if result >= SizeOf(Buf) then
exit; // avoid buffer overflow
until false;
Buf[result] := #0; // make asciiz
inc(S, result); // S[result]=Sep or #0
if S^ = #0 then
P := nil
else if Sep = #0 then
P := S
else
P := S + 1;
end;
{$ifdef CPU64}
function GetNextItemInt64(var P: PUtf8Char; Sep: AnsiChar): Int64;
begin
result := GetNextItemInteger(P, Sep); // PtrInt=Int64
end;
function GetNextItemQWord(var P: PUtf8Char; Sep: AnsiChar): QWord;
begin
result := GetNextItemCardinal(P, Sep); // PtrUInt=QWord
end;
{$else}
function GetNextItemInt64(var P: PUtf8Char; Sep: AnsiChar): Int64;
var
tmp: TChar64;
begin
if GetNextTChar64(P, Sep, tmp) > 0 then
SetInt64(tmp, result)
else
result := 0;
end;
function GetNextItemQWord(var P: PUtf8Char; Sep: AnsiChar): QWord;
var
tmp: TChar64;
begin
if GetNextTChar64(P, Sep, tmp) > 0 then
SetQWord(tmp, result)
else
result := 0;
end;
{$endif CPU64}
function GetNextItemHexa(var P: PUtf8Char; Sep: AnsiChar): QWord;
var
tmp: TChar64;
L: integer;
begin
result := 0;
L := GetNextTChar64(P, Sep, tmp);
if (L > 0) and
(L and 1 = 0) then
if not HexDisplayToBin(@tmp, @result, L shr 1) then
result := 0;
end;
function GetNextItemDouble(var P: PUtf8Char; Sep: AnsiChar): double;
var
tmp: TChar64;
err: integer;
begin
if GetNextTChar64(P, Sep, tmp) > 0 then
begin
result := GetExtended(tmp, err);
if err <> 0 then
result := 0;
end
else
result := 0;
end;
function GetNextItemCurrency(var P: PUtf8Char; Sep: AnsiChar): currency;
begin
GetNextItemCurrency(P, result, Sep);
end;
procedure GetNextItemCurrency(var P: PUtf8Char; out result: currency; Sep: AnsiChar);
var
tmp: TChar64;
begin
if GetNextTChar64(P, Sep, tmp) > 0 then
PInt64(@result)^ := StrToCurr64(tmp)
else
result := 0;
end;
function GetCsvItem(P: PUtf8Char; Index: PtrUInt; Sep: AnsiChar): RawUtf8;
var
i: PtrUInt;
begin
if P = nil then
result := ''
else
for i := 0 to Index do
GetNextItem(P, Sep, result);
end;
function GetUnQuoteCsvItem(P: PUtf8Char; Index: PtrUInt; Sep, Quote: AnsiChar): RawUtf8;
var
i: PtrUInt;
begin
if P = nil then
result := ''
else
for i := 0 to Index do
GetNextItem(P, Sep, Quote, result);
end;
function GetFirstCsvItem(const Csv: RawUtf8; Sep: AnsiChar): RawUtf8;
var
i: PtrInt;
begin
i := PosExChar(Sep, Csv);
if i = 0 then
result := Csv
else
FastSetString(result, pointer(Csv), i - 1);
end;
function GetLastCsvItem(const Csv: RawUtf8; Sep: AnsiChar): RawUtf8;
begin
result := SplitRight(Csv, Sep, nil);
end;
function GetCsvItemString(P: PChar; Index: PtrUInt; Sep: Char): string;
var
i: PtrUInt;
begin
if P = nil then
result := ''
else
for i := 0 to Index do
result := GetNextItemString(P, Sep);
end;
function CsvContains(const Csv, Value: RawUtf8; Sep: AnsiChar;
CaseSensitive: boolean): boolean;
var
i, l: PtrInt;
p, s: PUtf8Char;
match: TIdemPropNameUSameLen;
begin
if (Csv = '') or
(Value = '') then
begin
result := false;
exit;
end;
// note: all search sub-functions do use fast SSE2 asm on i386 and x86_64
match := IdemPropNameUSameLen[CaseSensitive];
p := pointer(Csv);
l := PStrLen(PAnsiChar(pointer(Value)) - _STRLEN)^;
if l >= PStrLen(p - _STRLEN)^ then
result := (l = PStrLen(p - _STRLEN)^) and
match(p, pointer(Value), l)
else
begin
i := PosExChar(Sep, Csv);
if i <> 0 then
begin
result := true;
s := p + i - 1;
repeat
if (s - p = l) and
match(p, pointer(Value), l) then
exit;
p := s + 1;
s := PosChar(p, Sep);
if s <> nil then
continue;
if (PStrLen(PAnsiChar(pointer(Csv)) - _STRLEN)^ - (p - pointer(Csv)) = l) and
match(p, pointer(Value), l) then
exit;
break;
until false;
end;
result := false;
end;
end;
function FindCsvIndex(Csv: PUtf8Char; const Value: RawUtf8; Sep: AnsiChar;
CaseSensitive, TrimValue: boolean): integer;
var
s: RawUtf8;
begin
result := 0;
while Csv <> nil do
begin
GetNextItem(Csv, Sep, s);
if TrimValue then
TrimSelf(s);
if CaseSensitive then
begin
if SortDynArrayRawByteString(s, Value) = 0 then
exit;
end
else if SameTextU(s, Value) then
exit;
inc(result);
end;
result := -1; // not found
end;
procedure CsvToRawUtf8DynArray(Csv: PUtf8Char; var List: TRawUtf8DynArray;
Sep: AnsiChar; TrimItems, AddVoidItems: boolean; Quote: AnsiChar);
var
s: RawUtf8;
n: integer;
begin
n := length(List);
while (Csv <> nil) and
(Csv^ <> #0) do
begin
if Quote <> #0 then
begin
GetNextItem(Csv, Sep, Quote, s);
if TrimItems then
TrimSelf(s);
end
else if TrimItems then
GetNextItemTrimed(Csv, Sep, s)
else
GetNextItem(Csv, Sep, s);
if (s <> '') or
AddVoidItems then
AddRawUtf8(List, n, s);
end;
if List <> nil then
DynArrayFakeLength(List, n);
end;
procedure CsvToRawUtf8DynArray(const Csv, Sep, SepEnd: RawUtf8;
var List: TRawUtf8DynArray);
var
offs, i, n: integer;
s: RawUtf8;
begin
n := length(List);
offs := 1;
while offs <= length(Csv) do
begin
i := PosEx(Sep, Csv, offs);
if i = 0 then
begin
i := PosEx(SepEnd, Csv, offs);
if i = 0 then
i := length(csv) + 1;
FastSetString(s, @PByteArray(Csv)[offs - 1], i - offs);
AddRawUtf8(List, n, s);
break;
end;
FastSetString(s, @PByteArray(Csv)[offs - 1], i - offs);
AddRawUtf8(List, n, s);
offs := i + length(Sep);
end;
if List <> nil then
DynArrayFakeLength(List, n);
end;
function CsvToRawUtf8DynArray(const Csv, Sep, SepEnd: RawUtf8): TRawUtf8DynArray;
begin
result := nil;
CsvToRawUtf8DynArray(Csv, Sep, SepEnd, result);
end;
function AddPrefixToCsv(Csv: PUtf8Char; const Prefix: RawUtf8; Sep: AnsiChar): RawUtf8;
var
s: RawUtf8;
begin
GetNextItem(Csv, Sep, result);
if result = '' then
exit;
result := Prefix + result;
while Csv <> nil do
begin
GetNextItem(Csv, Sep, s);
if s <> '' then
result := result + ',' + Prefix + s;
end;
end;
procedure AddToCsv(const Value: RawUtf8; var Csv: RawUtf8; const Sep: RawUtf8);
begin
if Csv = '' then
Csv := Value
else
Csv := Csv + Sep + Value;
end;
function RenameInCsv(const OldValue, NewValue: RawUtf8; var Csv: RawUtf8;
const Sep: RawUtf8): boolean;
var
pattern: RawUtf8;
i, j: integer;
begin
result := OldValue = NewValue;
i := length(OldValue);
if result or
(length(Sep) <> 1) or
(length(Csv) < i) or
(PosEx(Sep, OldValue) > 0) or
(PosEx(Sep, NewValue) > 0) then
exit;
if CompareMem(pointer(OldValue), pointer(Csv), i) and // first (or unique) item
((Csv[i + 1] = Sep[1]) or
(Csv[i + 1] = #0)) then
i := 1
else
begin
j := 1;
pattern := Sep + OldValue;
repeat
i := PosEx(pattern, Csv, j);
if i = 0 then
exit;
j := i + length(pattern);
until (Csv[j] = Sep[1]) or
(Csv[j] = #0);
inc(i);
end;
delete(Csv, i, length(OldValue));
insert(NewValue, Csv, i);
result := true;
end;
function CsvGuessSeparator(const Csv: RawUtf8): AnsiChar;
begin
if PosExChar(#9, Csv) <> 0 then
result := #9
else if PosExChar(';', Csv) <> 0 then
result := ';'
else if PosExChar(',', Csv) <> 0 then
result := ','
else
result := #0;
end;
function RawUtf8ArrayToCsv(const Values: array of RawUtf8; const Sep: RawUtf8;
HighValues: integer): RawUtf8;
var
i, len, seplen, L: integer;
P: PAnsiChar;
begin
result := '';
if HighValues < 0 then
HighValues := high(Values);
if HighValues < 0 then
exit;
seplen := length(Sep);
len := seplen * HighValues;
for i := 0 to HighValues do
inc(len, length(Values[i]));
FastSetString(result, len); // allocate the result buffer as once
P := pointer(result);
i := 0;
repeat
L := length(Values[i]);
if L > 0 then
begin
MoveFast(pointer(Values[i])^, P^, L);
inc(P, L);
end;
if i = HighValues then
break;
if seplen > 0 then
begin
MoveFast(pointer(Sep)^, P^, seplen);
inc(P, seplen);
end;
inc(i);
until false;
end;
function RawUtf8ArrayToQuotedCsv(const Values: array of RawUtf8;
const Sep: RawUtf8; Quote: AnsiChar): RawUtf8;
var
i: integer;
tmp: TRawUtf8DynArray;
begin
SetLength(tmp, length(Values));
for i := 0 to High(Values) do
QuotedStr(Values[i], Quote, tmp[i]);
result := RawUtf8ArrayToCsv(tmp, Sep);
end;
procedure CsvToIntegerDynArray(Csv: PUtf8Char; var List: TIntegerDynArray;
Sep: AnsiChar);
var
n: integer;
begin
n := length(List);
while (Csv <> nil) and
(Csv^ <> #0) do
AddInteger(List, n, GetNextItemInteger(Csv, Sep));
if List <> nil then
DynArrayFakeLength(List, n);
end;
procedure CsvToInt64DynArray(Csv: PUtf8Char; var List: TInt64DynArray;
Sep: AnsiChar);
var
n: integer;
begin
n := length(List);
while (Csv <> nil) and
(Csv^ <> #0) do
AddInt64(List, n, GetNextItemInt64(Csv, Sep));
if List <> nil then
DynArrayFakeLength(List, n);
end;
function CsvToInt64DynArray(Csv: PUtf8Char; Sep: AnsiChar): TInt64DynArray;
var
n: integer;
begin
result := nil;
n := 0;
while (Csv <> nil) and
(Csv^ <> #0) do
AddInt64(result, n, GetNextItemInt64(Csv, Sep));
if result <> nil then
DynArrayFakeLength(result, n);
end;
const // first byte is the len, then 20 bytes buffer for the 64-bit integer text
I2T_SIZE = 21; // as TSynTempBuffer = up to 194 integers on stack
procedure IntToText(int: PAnsiChar; len, n: PtrInt; const pref, suf: RawUtf8;
inlin: boolean; sep: AnsiChar; var result: RawUtf8);
var
L: PtrUInt;
P: PAnsiChar;
begin
inc(len, (n - 1) + length(pref) + length(suf));
if inlin then
inc(len, n * 4); // :( ): markers
FastSetString(result, len);
P := pointer(result);
if pref <> '' then
begin
L := length(pref);
MoveFast(pointer(pref)^, P^, L);
inc(P, L);
end;
if inlin then
repeat
PCardinal(P)^ := ord(':') + ord('(') shl 8;
inc(P, 2);
MoveFast(int[I2T_SIZE - ord(int^)], P^, ord(int^));
inc(P, ord(int^));
PCardinal(P)^ := ord(')') + ord(':') shl 8;
inc(P, 2);
dec(n);
if n = 0 then
break;
inc(int, I2T_SIZE);
P^ := sep;
inc(P);
until false
else
repeat
L := ord(int^);
MoveFast(PAnsiChar(int)[I2T_SIZE - L], P^, L);
inc(P, L);
dec(n);
if n = 0 then
break;
inc(int, I2T_SIZE);
P^ := sep;
inc(P);
until false;
if suf <> '' then
MoveFast(pointer(suf)^, P^, length(suf));
end;
function IntegerDynArrayToCsv(Values: PIntegerArray; ValuesCount: integer;
const Prefix, Suffix: RawUtf8; InlinedValue: boolean; SepChar: AnsiChar): RawUtf8;
var
i, L, Len: PtrInt;
int, P: PAnsiChar;
temp: TSynTempBuffer; // faster than a dynamic array
begin
result := '';
if ValuesCount = 0 then
exit;
int := temp.Init(ValuesCount * I2T_SIZE);
try
Len := 0;
for i := 0 to ValuesCount - 1 do
begin
P := StrInt32(int + I2T_SIZE, Values[i]);
L := int + I2T_SIZE - P;
int^ := AnsiChar(L);
inc(Len, L);
inc(int, I2T_SIZE);
end;
IntToText(temp.buf, Len, ValuesCount, Prefix, Suffix, InlinedValue, SepChar, result);
finally
temp.Done;
end;
end;
function Int64DynArrayToCsv(Values: PInt64Array; ValuesCount: integer;
const Prefix, Suffix: RawUtf8; InlinedValue: boolean; SepChar: AnsiChar): RawUtf8;
var
i, L, Len: PtrInt;
int, P: PAnsiChar;
temp: TSynTempBuffer; // faster than a dynamic array
begin
result := '';
if ValuesCount = 0 then
exit;
int := temp.Init(ValuesCount * I2T_SIZE);
try
Len := 0;
for i := 0 to ValuesCount - 1 do
begin
P := StrInt64(int + I2T_SIZE, Values[i]);
L := int + I2T_SIZE - P;
int^ := AnsiChar(L);
inc(Len, L);
inc(int, I2T_SIZE);
end;
IntToText(temp.buf, Len, ValuesCount, Prefix, Suffix, InlinedValue, SepChar, result);
finally
temp.Done;
end;
end;
function IntegerDynArrayToCsv(const Values: TIntegerDynArray;
const Prefix, Suffix: RawUtf8; InlinedValue: boolean; SepChar: AnsiChar): RawUtf8;
begin
result := IntegerDynArrayToCsv(pointer(Values), length(Values),
Prefix, Suffix, InlinedValue, SepChar);
end;
function Int64DynArrayToCsv(const Values: TInt64DynArray;
const Prefix, Suffix: RawUtf8; InlinedValue: boolean; SepChar: AnsiChar): RawUtf8;
begin
result := Int64DynArrayToCsv(pointer(Values), length(Values),
Prefix, Suffix, InlinedValue, SepChar);
end;
{ ************ TTextWriter parent class for Text Generation }
function HexToChar(Hex: PAnsiChar; Bin: PUtf8Char): boolean; // for inlining
var
b, c: byte;
{$ifdef CPUX86NOTPIC}
tab: THexToDualByte absolute ConvertHexToBin;
{$else}
tab: PByteArray; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
begin
if Hex <> nil then
begin
{$ifndef CPUX86NOTPIC}
tab := @ConvertHexToBin;
{$endif CPUX86NOTPIC}
b := tab[ord(Hex[0]) + 256]; // + 256 for shl 4
c := tab[ord(Hex[1])];
if (b <> 255) and
(c <> 255) then
begin
if Bin <> nil then
begin
inc(c, b);
Bin^ := AnsiChar(c);
end;
result := true;
exit;
end;
end;
result := false; // return false if any invalid char
end;
{ TTextWriter }
{$ifndef PUREMORMOT2}
var
DefaultTextWriterTrimEnum: boolean; // see TTextWriter.SetDefaultEnumTrim()
class procedure TTextWriter.SetDefaultEnumTrim(aShouldTrimEnumsAsText: boolean);
begin
DefaultTextWriterTrimEnum := aShouldTrimEnumsAsText;
end;
{$endif PUREMORMOT2}
procedure TTextWriter.InternalSetBuffer(aBuf: PUtf8Char; const aBufSize: PtrUInt);
begin
fTempBufSize := aBufSize;
fTempBuf := aBuf;
dec(aBuf);
B := aBuf; // Add() methods will append at B+1
BEnd := @aBuf[aBufSize - 15]; // BEnd := B-16 to avoid overwrite/overread
{$ifndef PUREMORMOT2}
if DefaultTextWriterTrimEnum then
Include(fCustomOptions, twoTrimLeftEnumSets);
{$endif PUREMORMOT2}
end;
constructor TTextWriter.Create(aStream: TStream; aBufSize: integer);
begin
SetStream(aStream);
if aBufSize < 256 then
aBufSize := 256;
SetBuffer(nil, aBufSize);
end;
constructor TTextWriter.Create(aStream: TStream; aBuf: pointer; aBufSize: integer);
begin
SetStream(aStream);
SetBuffer(aBuf, aBufSize);
end;
var
TextWriterSharedStreamSafe: TLightLock; // thread-safe instance acquisition
TextWriterSharedStream: TRawByteStringStream;
constructor TTextWriter.CreateOwnedStream(
aBuf: pointer; aBufSize: integer; NoSharedStream: boolean);
begin
if (not NoSharedStream) and TextWriterSharedStreamSafe.TryLock then
fStream := TextWriterSharedStream
else
fStream := TRawByteStringStream.Create; // inlined SetStream()
fCustomOptions := [twoStreamIsOwned, twoStreamIsRawByteString];
SetBuffer(aBuf, aBufSize); // aBuf may be nil
end;
constructor TTextWriter.CreateOwnedStream(aBufSize: integer; NoSharedStream: boolean);
begin
CreateOwnedStream(nil, aBufSize, NoSharedStream);
end;
constructor TTextWriter.CreateOwnedStream(var aStackBuf: TTextWriterStackBuffer;
aBufSize: integer; NoSharedStream: boolean);
begin
if aBufSize > SizeOf(aStackBuf) then // too small -> allocate on heap
CreateOwnedStream(nil, aBufSize, NoSharedStream)
else
CreateOwnedStream(aStackBuf, NoSharedStream);
end;
constructor TTextWriter.CreateOwnedStream(
var aStackBuf: TTextWriterStackBuffer; NoSharedStream: boolean);
begin
if (not NoSharedStream) and TextWriterSharedStreamSafe.TryLock then
fStream := TextWriterSharedStream
else
fStream := TRawByteStringStream.Create; // inlined SetStream()
fCustomOptions := [twoStreamIsOwned, twoStreamIsRawByteString, twoBufferIsExternal];
InternalSetBuffer(@aStackBuf, SizeOf(aStackBuf));
end;
constructor TTextWriter.CreateOwnedFileStream(
const aFileName: TFileName; aBufSize: integer);
begin
DeleteFile(aFileName);
fStream := TFileStreamEx.Create(aFileName, fmCreate or fmShareRead);
fCustomOptions := [twoStreamIsOwned];
SetBuffer(nil, aBufSize);
end;
destructor TTextWriter.Destroy;
begin
if twoStreamIsOwned in fCustomOptions then
if fStream = TextWriterSharedStream then
begin
TRawByteStringStream(fStream).Clear; // for proper reuse
TextWriterSharedStreamSafe.UnLock;
end
else
fStream.Free;
if not (twoBufferIsExternal in fCustomOptions) then
FreeMem(fTempBuf);
inherited Destroy;
end;
function TTextWriter.PendingBytes: PtrUInt;
begin
result := B - fTempBuf + 1;
end;
procedure TTextWriter.Add(c: AnsiChar);
begin
if B >= BEnd then
FlushToStream; // may rewind B -> not worth any local PUtf8Char variable
B[1] := c;
inc(B);
end;
procedure TTextWriter.AddDirect(c: AnsiChar);
begin
B[1] := c;
inc(B);
end;
procedure TTextWriter.AddDirect(c1, c2: AnsiChar);
begin
PCardinal(B + 1)^ := byte(c1) + PtrUInt(byte(c2)) shl 8;
inc(B, 2); // with proper constant propagation above when inlined
end;
procedure TTextWriter.AddComma;
begin
B[1] := ',';
inc(B);
end;
procedure TTextWriter.Add(c1, c2: AnsiChar);
begin
if B >= BEnd then
FlushToStream;
PCardinal(B + 1)^ := byte(c1) + PtrUInt(byte(c2)) shl 8;
inc(B, 2); // with proper constant propagation above when inlined
end;
procedure TTextWriter.Add(const Format: RawUtf8; const Values: array of const;
Escape: TTextWriterKind; WriteObjectOptions: TTextWriterWriteObjectOptions);
var
tmp: RawUtf8;
begin
// basic implementation: see faster and more complete version in TJsonWriter
FormatUtf8(Format, Values, tmp);
case Escape of
twNone:
AddString(tmp);
twOnSameLine:
AddOnSameLine(pointer(tmp)); // minimalistic version for TSynLog
twJsonEscape:
raise ESynException.CreateUtf8(
'%.Add(twJsonEscape) unimplemented: use TJsonWriter', [self]);
end;
end;
procedure TTextWriter.AddVariant(const Value: variant; Escape: TTextWriterKind;
WriteOptions: TTextWriterWriteObjectOptions);
begin
raise ESynException.CreateUtf8(
'%.AddVariant unimplemented: use TJsonWriter', [self]);
end;
procedure TTextWriter.AddTypedJson(Value, TypeInfo: pointer;
WriteOptions: TTextWriterWriteObjectOptions);
begin
raise ESynException.CreateUtf8(
'%.AddTypedJson unimplemented: use TJsonWriter', [self]);
end;
function TTextWriter.{%H-}AddJsonReformat(Json: PUtf8Char;
Format: TTextWriterJsonFormat; EndOfObject: PUtf8Char): PUtf8Char;
begin
raise ESynException.CreateUtf8(
'%.AddJsonReformat unimplemented: use TJsonWriter', [self]);
end;
procedure TTextWriter.Add(P: PUtf8Char; Escape: TTextWriterKind);
begin
raise ESynException.CreateUtf8(
'%.Add(..,Escape: TTextWriterKind) unimplemented: use TJsonWriter', [self]);
end;
procedure TTextWriter.Add(P: PUtf8Char; Len: PtrInt; Escape: TTextWriterKind);
begin
raise ESynException.CreateUtf8(
'%.Add(..,Escape: TTextWriterKind) unimplemented: use TJsonWriter', [self]);
end;
procedure TTextWriter.WrBase64(P: PAnsiChar; Len: PtrUInt; withMagic: boolean);
begin
raise ESynException.CreateUtf8(
'%.WrBase64() unimplemented: use TJsonWriter', [self]);
end;
procedure TTextWriter.AddShorter(const Short8: TShort8);
begin
if B >= BEnd then
FlushToStream;
PInt64(B + 1)^ := PInt64(@Short8[1])^;
inc(B, ord(Short8[0]));
end;
procedure TTextWriter.AddNull;
begin
if B >= BEnd then
FlushToStream;
PCardinal(B + 1)^ := NULL_LOW;
inc(B, 4);
end;
procedure TTextWriter.WriteObject(Value: TObject;
WriteOptions: TTextWriterWriteObjectOptions);
begin
raise ESynException.CreateUtf8(
'%.WriteObject unimplemented: use TJsonWriter', [self]);
end;
procedure TTextWriter.AddObjArrayJson(const aObjArray;
aOptions: TTextWriterWriteObjectOptions);
var
i: PtrInt;
a: TObjectDynArray absolute aObjArray;
begin
Add('[');
for i := 0 to length(a) - 1 do
begin
WriteObject(a[i], aOptions);
AddComma;
end;
CancelLastComma(']');
end;
procedure TTextWriter.WriteToStream(data: pointer; len: PtrUInt);
var
written: PtrUInt;
begin
if Assigned(fOnFlushToStream) then
fOnFlushToStream(data, len);
if (len <> 0) and
Assigned(fStream) then
repeat
written := fStream.Write(data^, len);
if PtrInt(written) <= 0 then
if twoNoWriteToStreamException in fCustomOptions then
break // silent failure
else
raise ESynException.CreateUtf8(
'%.WriteToStream failed on %', [self, fStream]);
inc(fTotalFileSize, written);
dec(len, written);
if len = 0 then
break;
inc(PByte(data), written); // several calls to Write() may be needed
until false;
end;
function TTextWriter.GetTextLength: PtrUInt;
begin
result := PtrUInt(self);
if self <> nil then
result := PtrUInt(B - fTempBuf + 1) + fTotalFileSize - fInitialStreamPosition;
end;
procedure TTextWriter.SetBuffer(aBuf: pointer; aBufSize: integer);
begin
if aBufSize <= 16 then
raise ESynException.CreateUtf8('%.SetBuffer(size=%)', [self, aBufSize]);
if aBuf = nil then
GetMem(aBuf, aBufSize)
else
Include(fCustomOptions, twoBufferIsExternal);
InternalSetBuffer(aBuf, aBufSize);
end;
procedure TTextWriter.SetStream(aStream: TStream);
begin
exclude(fCustomOptions, twoStreamIsRawByteString);
if fStream <> nil then
if twoStreamIsOwned in fCustomOptions then
begin
if fStream = TextWriterSharedStream then
begin
TRawByteStringStream(fStream).Clear; // for proper reuse
TextWriterSharedStreamSafe.UnLock;
fStream := nil;
end
else
FreeAndNilSafe(fStream);
exclude(fCustomOptions, twoStreamIsOwned);
end;
if aStream = nil then
exit;
fStream := aStream;
fInitialStreamPosition := fStream.Position;
fTotalFileSize := fInitialStreamPosition;
if aStream.InheritsFrom(TRawByteStringStream) then
include(fCustomOptions, twoStreamIsRawByteString);
end;
procedure TTextWriter.FlushFinal;
var
len: PtrInt;
begin // don't mess with twoFlushToStreamNoAutoResize: it may not be final
len := B - fTempBuf + 1;
if len > 0 then
WriteToStream(fTempBuf, len);
B := fTempBuf - 1;
end;
procedure TTextWriter.FlushToStream;
var
tmp, written: PtrUInt;
begin
FlushFinal;
if twoFlushToStreamNoAutoResize in fCustomOptions then
exit;
written := fTotalFileSize - fInitialStreamPosition;
tmp := fTempBufSize;
if (tmp < 49152) and
(written > PtrUInt(tmp) * 4) then
// tune small (stack-allocated?) buffer to grow by twice its size
fTempBufSize := fTempBufSize * 2
else if (written > 40 shl 20) and
(tmp < 1 shl 20) then
// total > 40MB -> grow internal buffer to 1MB
fTempBufSize := 1 shl 20
else
// nothing to change about internal buffer size
exit;
if twoBufferIsExternal in fCustomOptions then
// use heap, not stack from now on
exclude(fCustomOptions, twoBufferIsExternal)
else
// from big content comes bigger buffer - but no need to realloc/move
FreeMem(fTempBuf);
GetMem(fTempBuf, fTempBufSize);
BEnd := fTempBuf + (fTempBufSize - 16); // as in SetBuffer()
B := fTempBuf - 1;
end;
procedure TTextWriter.ForceContent(const text: RawUtf8);
begin
CancelAll;
if (fInitialStreamPosition = 0) and
(twoStreamIsRawByteString in fCustomOptions) then
TRawByteStringStream(fStream).DataString := text
else
fStream.WriteBuffer(pointer(text)^, length(text));
fTotalFileSize := fInitialStreamPosition + PtrUInt(length(text));
end;
procedure TTextWriter.SetText(var result: RawUtf8; reformat: TTextWriterJsonFormat);
var
Len: PtrUInt;
temp: TTextWriter;
begin
FlushFinal;
Len := fTotalFileSize - fInitialStreamPosition;
if Len = 0 then
begin
result := '';
exit;
end;
if twoStreamIsRawByteString in fCustomOptions then
TRawByteStringStream(fStream).GetAsText(fInitialStreamPosition, Len, result)
else if fStream.InheritsFrom(TCustomMemoryStream) then
with TCustomMemoryStream(fStream) do
FastSetString(result, PAnsiChar(Memory) + fInitialStreamPosition, Len)
else
begin
FastSetString(result, Len);
fStream.Seek(fInitialStreamPosition, soBeginning);
fStream.Read(pointer(result)^, Len);
end;
if reformat <> jsonCompact then
begin
// reformat using the very same temp buffer but not the same RawUtf8
temp := DefaultJsonWriter.CreateOwnedStream(fTempBuf, fTempBufSize);
try
temp.AddJsonReformat(pointer(result), reformat, nil);
temp.SetText(result);
finally
temp.Free;
end;
end;
end;
function TTextWriter.Text: RawUtf8;
begin
SetText(result);
end;
procedure TTextWriter.CancelAll;
begin
if self = nil then
exit; // avoid GPF
if fTotalFileSize <> 0 then
fTotalFileSize := fStream.Seek(fInitialStreamPosition, soBeginning);
B := fTempBuf - 1;
end;
procedure TTextWriter.CancelAllAsNew;
begin
CancelAll;
fCustomOptions := fCustomOptions * TEXTWRITEROPTIONS_RESET;
end;
procedure TTextWriter.CancelAllWith(var temp: TTextWriterStackBuffer);
begin
if fTotalFileSize <> 0 then
fTotalFileSize := fStream.Seek(fInitialStreamPosition, soBeginning);
InternalSetBuffer(@temp, SizeOf(temp));
end;
procedure TTextWriter.CancelLastChar(aCharToCancel: AnsiChar);
var
P: PUtf8Char;
begin
P := B;
if (P >= fTempBuf) and
(P^ = aCharToCancel) then
dec(B);
end;
procedure TTextWriter.CancelLastChar;
begin
if B >= fTempBuf then // Add() methods append at B+1
dec(B);
end;
procedure TTextWriter.CancelLastComma;
var
P: PUtf8Char;
begin
P := B;
if (P >= fTempBuf) and
(P^ = ',') then
dec(B);
end;
procedure TTextWriter.CancelLastComma(aReplaceChar: AnsiChar);
var
P: PUtf8Char;
begin
P := B;
if (P < fTempBuf) or
(P^ <> ',') then
begin
inc(P);
B := P;
end;
P^ := aReplaceChar;
end;
function TTextWriter.LastChar: AnsiChar;
begin
if B >= fTempBuf then
result := B^
else
result := #0;
end;
procedure TTextWriter.AddOnce(c: AnsiChar);
begin
if (B >= fTempBuf) and
(B^ = c) then
exit; // no duplicate
if B >= BEnd then
FlushToStream;
B[1] := c;
inc(B);
end;
procedure TTextWriter.Add(Value: PtrInt);
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
Len: PtrInt;
begin
if BEnd - B <= 23 then
FlushToStream;
{$ifndef ASMINTEL} // our StrInt32 asm has less CPU cache pollution
if PtrUInt(Value) <= high(SmallUInt32Utf8) then
begin
P := pointer(SmallUInt32Utf8[Value]);
Len := PStrLen(P - _STRLEN)^;
end
else
{$endif ASMINTEL}
begin
P := StrInt32(@tmp[23], Value);
Len := @tmp[23] - P;
end;
MoveFast(P^, B[1], Len);
inc(B, Len);
end;
{$ifdef CPU32} // Add(Value: PtrInt) already implemented it for CPU64
procedure TTextWriter.Add(Value: Int64);
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
Len: integer;
begin
if BEnd - B <= 24 then
FlushToStream;
if Value < 0 then
begin
P := StrUInt64(@tmp[23], -Value) - 1;
P^ := '-';
Len := @tmp[23] - P;
end
{$ifndef ASMINTEL} // our StrUInt32 asm has less CPU cache pollution
else if Value <= high(SmallUInt32Utf8) then
begin
P := pointer(SmallUInt32Utf8[Value]);
Len := PStrLen(P - _STRLEN)^;
end
{$endif ASMINTEL} // our StrInt32 asm has less CPU cache pollution
else
begin
P := StrUInt64(@tmp[23], Value);
Len := @tmp[23] - P;
end;
MoveByOne(P, B + 1, Len);
inc(B, Len);
end;
{$endif CPU32}
procedure TTextWriter.AddCurr64(Value: PInt64);
var
tmp: array[0..31] of AnsiChar;
P: PAnsiChar;
Len: PtrInt;
begin
if BEnd - B <= 31 then
FlushToStream;
P := StrCurr64(@tmp[31], Value^);
Len := @tmp[31] - P;
if Len > 4 then
if P[Len - 1] = '0' then
if P[Len - 2] = '0' then
if P[Len - 3] = '0' then
if P[Len - 4] = '0' then
dec(Len, 5) // 'xxx.0000' -> 'xxx'
else
dec(Len, 3) // 'xxx.1000' -> 'xxx.1'
else
dec(Len, 2) // 'xxx.1200' -> 'xxx.12'
else
dec(Len); // 'xxx.1220' -> 'xxx.123'
MoveFast(P^, B[1], Len);
inc(B, Len);
end;
procedure TTextWriter.AddCurr(const Value: currency);
begin
AddCurr64(PInt64(@Value));
end;
procedure TTextWriter.AddU(Value: cardinal);
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
Len: PtrInt;
begin
if BEnd - B <= 24 then
FlushToStream;
{$ifndef ASMINTEL} // our StrUInt32 asm has less CPU cache pollution
if Value <= high(SmallUInt32Utf8) then
begin
P := pointer(SmallUInt32Utf8[Value]);
Len := PStrLen(P - _STRLEN)^;
end
else
{$endif ASMINTEL}
begin
P := StrUInt32(@tmp[23], Value);
Len := @tmp[23] - P;
end;
MoveFast(P^, B[1], Len);
inc(B, Len);
end;
procedure TTextWriter.AddUHex(Value: cardinal; QuotedChar: AnsiChar);
begin
AddBinToHexDisplayLower(@Value, SizeOf(Value), QuotedChar);
end;
procedure TTextWriter.AddQ(Value: QWord);
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
Len: PtrInt;
begin
if BEnd - B <= 32 then
FlushToStream;
{$ifndef ASMINTEL} // our StrInt32 asm has less CPU cache pollution
if Value <= high(SmallUInt32Utf8) then
begin
P := pointer(SmallUInt32Utf8[Value]);
Len := PStrLen(P - _STRLEN)^;
end
else
{$endif ASMINTEL}
begin
P := StrUInt64(@tmp[23], Value);
Len := @tmp[23] - P;
end;
MoveFast(P^, B[1], Len);
inc(B, Len);
end;
procedure TTextWriter.AddQHex(Value: Qword; QuotedChar: AnsiChar);
begin
AddBinToHexDisplayLower(@Value, SizeOf(Value), QuotedChar);
end;
procedure TTextWriter.Add(Value: Extended; precision: integer; noexp: boolean);
var
tmp: ShortString;
begin
AddShort(ExtendedToJson(@tmp, Value, precision, noexp)^);
end;
procedure TTextWriter.AddDouble(Value: double; noexp: boolean);
var
tmp: ShortString;
begin
AddShort(DoubleToJson(@tmp, Value, noexp)^);
end;
procedure TTextWriter.AddSingle(Value: single; noexp: boolean);
var
tmp: ShortString;
begin
AddShort(ExtendedToJson(@tmp, Value, SINGLE_PRECISION, noexp)^);
end;
procedure TTextWriter.Add(Value: boolean);
var
PS: PShortString;
begin
if Value then // normalize: boolean may not be in the expected [0,1] range
PS := @BOOL_STR[true]
else
PS := @BOOL_STR[false];
AddShorter(PS^);
end;
procedure TTextWriter.AddFloatStr(P: PUtf8Char);
begin
if mormot.core.base.StrLen(P) > 127 then
exit; // clearly invalid input
if BEnd - B <= 127 then
FlushToStream;
inc(B);
if P <> nil then
B := FloatStrCopy(P, B) - 1
else
B^ := '0';
end;
procedure TTextWriter.Add(Value: PGuid; QuotedChar: AnsiChar);
begin
if BEnd - B <= 38 then
FlushToStream;
inc(B);
if QuotedChar <> #0 then
begin
B^ := QuotedChar;
inc(B);
end;
B := GuidToText(B, pointer(Value));
if QuotedChar <> #0 then
B^ := QuotedChar
else
dec(B);
end;
procedure TTextWriter.AddCR;
begin
if B >= BEnd then
FlushToStream;
PCardinal(B + 1)^ := 13 + 10 shl 8; // CR + LF
inc(B, 2);
end;
procedure TTextWriter.AddCRAndIndent;
var
ntabs: cardinal;
begin
if B^ = #9 then
// we just already added an indentation level - do it once
exit;
ntabs := fHumanReadableLevel;
if ntabs >= cardinal(fTempBufSize) then
ntabs := 0; // fHumanReadableLevel=-1 after the last level of a document
if BEnd - B <= PtrInt(ntabs) then
FlushToStream;
PCardinal(B + 1)^ := 13 + 10 shl 8; // CR + LF
if ntabs > 0 then
FillCharFast(B[3], ntabs, 9); // #9=tab
inc(B, ntabs + 2);
end;
procedure TTextWriter.AddChars(aChar: AnsiChar; aCount: PtrInt);
var
n: PtrInt;
begin
while aCount > 0 do
begin
n := BEnd - B;
if n <= aCount then
begin
FlushToStream;
n := BEnd - B;
end;
if aCount < n then
n := aCount;
FillCharFast(B[1], n, ord(aChar));
inc(B, n);
dec(aCount, n);
end;
end;
procedure TTextWriter.Add2(Value: PtrUInt);
begin
if B >= BEnd then
FlushToStream;
if Value > 99 then
PCardinal(B + 1)^ := $3030 + ord(',') shl 16
else // '00,' if overflow
PCardinal(B + 1)^ := TwoDigitLookupW[Value] + ord(',') shl 16;
inc(B, 3);
end;
procedure TTextWriter.Add3(Value: cardinal);
var
V: cardinal;
begin
if B >= BEnd then
FlushToStream;
if Value > 999 then
PCardinal(B + 1)^ := $303030 // '000,' if overflow
else
begin
V := Value div 10;
PCardinal(B + 1)^ := TwoDigitLookupW[V] + (Value - V * 10 + 48) shl 16;
end;
inc(B, 4);
B^ := ',';
end;
procedure TTextWriter.Add4(Value: PtrUInt);
begin
if B >= BEnd then
FlushToStream;
if Value > 9999 then
PCardinal(B + 1)^ := $30303030 // '0000,' if overflow
else
YearToPChar(Value, B + 1);
inc(B, 5);
B^ := ',';
end;
function Value3Digits(V: cardinal; P: PUtf8Char; W: PWordArray): cardinal;
{$ifdef HASINLINE}inline;{$endif}
begin
result := V div 100;
PWord(P + 1)^ := W[V - result * 100];
V := result;
result := result div 10;
P^ := AnsiChar(V - result * 10 + 48);
end;
procedure TTextWriter.AddMicroSec(MicroSec: cardinal);
var
W: PWordArray;
begin
// in 00.000.000 TSynLog format
if B >= BEnd then
FlushToStream;
B[3] := '.';
B[7] := '.';
inc(B);
W := @TwoDigitLookupW;
MicroSec := Value3Digits(Value3Digits(MicroSec, B + 7, W), B + 3, W);
if MicroSec > 99 then
MicroSec := $3939
else
MicroSec := W[MicroSec];
PWord(B)^ := MicroSec;
inc(B, 9);
end;
procedure TTextWriter.AddCsvInteger(const Integers: array of integer);
var
i: PtrInt;
begin
if length(Integers) = 0 then
exit;
for i := 0 to high(Integers) do
begin
Add(Integers[i]);
AddComma;
end;
CancelLastComma;
end;
procedure TTextWriter.AddCsvDouble(const Doubles: array of double);
var
i: PtrInt;
begin
if length(Doubles) = 0 then
exit;
for i := 0 to high(Doubles) do
begin
AddDouble(Doubles[i]);
AddComma;
end;
CancelLastComma;
end;
procedure TTextWriter.AddNoJsonEscapeBig(P: Pointer; Len: PtrInt);
var
direct: PtrInt;
D: PUtf8Char;
comma: boolean;
begin
if (P <> nil) and
(Len > 0) then
if Len < fTempBufSize * 2 then
repeat
D := B + 1;
direct := BEnd - D; // guess biggest size available in fTempBuf at once
if direct > 0 then // 0..-15 may happen because Add up to BEnd + 16
begin
if Len < direct then
direct := Len;
// append UTF-8 bytes to fTempBuf
if direct > 0 then
begin
MoveFast(P^, D^, direct);
inc(B, direct);
end;
dec(Len, direct);
if Len = 0 then
break;
inc(PByte(P), direct);
end;
FlushToStream;
until false
else
begin
FlushFinal; // no auto-resize if content is really huge
comma := PAnsiChar(P)[Len - 1] = ',';
if comma then
dec(Len);
WriteToStream(P, Len); // no need to transit huge content into fTempBuf
if comma then
AddDirect(','); // but we need the last comma to be cancelable
end;
end;
procedure TTextWriter.AddNoJsonEscape(P: Pointer; Len: PtrInt);
begin
if (P <> nil) and
(Len > 0) then
if Len < fTempBufSize then // inlined for small chunk
begin
if BEnd - B <= Len then
FlushToStream;
MoveFast(P^, B[1], Len);
inc(B, Len);
end
else
AddNoJsonEscapeBig(P, Len); // big chunks
end;
procedure TTextWriter.AddNoJsonEscape(P: Pointer);
begin
if P <> nil then
AddNoJsonEscape(P, mormot.core.base.StrLen(PUtf8Char(P)));
end;
procedure EngineAppendUtf8(W: TTextWriter; Engine: TSynAnsiConvert;
P: PAnsiChar; Len: PtrInt);
var
tmp: TSynTempBuffer;
begin
// explicit conversion using a temporary buffer on stack
Len := Engine.AnsiBufferToUtf8(tmp.Init(Len * 3), P, Len) - PUtf8Char({%H-}tmp.buf);
W.AddNoJsonEscape(tmp.buf, Len);
tmp.Done;
end;
procedure TTextWriter.AddNoJsonEscape(P: PAnsiChar; Len: PtrInt; CodePage: cardinal);
var
B: PAnsiChar;
begin
if Len > 0 then
case CodePage of
CP_UTF8, CP_RAWBYTESTRING, CP_RAWBLOB:
AddNoJsonEscape(P, Len);
CP_UTF16:
AddNoJsonEscapeW(PWord(P), 0);
else
begin
// first handle trailing 7-bit ASCII chars, by quad
B := P;
if Len >= 4 then
repeat
if PCardinal(P)^ and $80808080 <> 0 then
break; // break on first non ASCII quad
inc(P, 4);
dec(Len, 4);
until Len < 4;
if (Len > 0) and
(P^ <= #127) then
repeat
inc(P);
dec(Len);
until (Len = 0) or
(P^ > #127);
if P <> B then
AddNoJsonEscape(B, P - B);
if Len > 0 then
// rely on explicit conversion for all remaining ASCII characters
EngineAppendUtf8(self, TSynAnsiConvert.Engine(CodePage), P, Len);
end;
end;
end;
procedure TTextWriter.AddNoJsonEscapeUtf8(const text: RawByteString);
begin
AddNoJsonEscape(pointer(text), length(text));
end;
procedure TTextWriter.AddRawJson(const json: RawJson);
begin
if json = '' then
AddNull
else
AddNoJsonEscape(pointer(json), length(json));
end;
procedure TTextWriter.AddNoJsonEscapeString(const s: string);
begin
if s <> '' then
{$ifdef UNICODE}
AddNoJsonEscapeW(pointer(s), 0);
{$else}
AddNoJsonEscape(pointer(s), length(s),
Unicode_CodePage); // =CurrentAnsiConvert.CodePage
{$endif UNICODE}
end;
procedure TTextWriter.AddNoJsonEscapeW(WideChar: PWord; WideCharCount: integer);
var
PEnd: PtrUInt;
c: cardinal;
begin
if WideChar = nil then
exit;
if WideCharCount = 0 then
repeat
if B >= BEnd then
FlushToStream;
c := WideChar^;
if c = 0 then
break
else if c <= 127 then
begin
B[1] := AnsiChar(c);
inc(WideChar);
inc(B);
end
else
inc(B, Utf16CharToUtf8(B + 1, WideChar));
until false
else
begin
PEnd := PtrUInt(WideChar) + PtrUInt(WideCharCount) * SizeOf(WideChar^);
repeat
if B >= BEnd then
FlushToStream;
c := WideChar^;
if c = 0 then
break
else if c <= 127 then
begin
B[1] := AnsiChar(c);
inc(WideChar);
inc(B);
if PtrUInt(WideChar) < PEnd then
continue
else
break;
end;
inc(B, Utf16CharToUtf8(B + 1, WideChar));
if PtrUInt(WideChar) < PEnd then
continue
else
break;
until false;
end;
end;
procedure TTextWriter.AddProp(PropName: PUtf8Char);
begin
AddProp(PropName, mormot.core.base.StrLen(PropName));
end;
procedure TTextWriter.AddProp(PropName: PUtf8Char; PropNameLen: PtrInt);
begin // not faster with a local P: PUtf8Char temp pointer instead of B
if PropNameLen <= 0 then
exit; // paranoid check
if BEnd - B <= PropNameLen then
FlushToStream;
if twoForceJsonExtended in fCustomOptions then
begin
MoveFast(PropName^, B[1], PropNameLen);
inc(B, PropNameLen + 1);
B^ := ':';
end
else
begin
B[1] := '"';
MoveFast(PropName^, B[2], PropNameLen);
inc(B, PropNameLen + 2);
PCardinal(B)^ := ord('"') + ord(':') shl 8;
inc(B);
end;
end;
procedure TTextWriter.AddPropName(const PropName: ShortString);
begin
AddProp(@PropName[1], ord(PropName[0]));
end;
procedure TTextWriter.AddPropInt64(const PropName: ShortString;
Value: Int64; WithQuote: AnsiChar);
begin
AddProp(@PropName[1], ord(PropName[0]));
if WithQuote <> #0 then
begin
B[1] := WithQuote;
inc(B);
end;
Add(Value);
inc(B);
if WithQuote <> #0 then
begin
B^ := WithQuote;
inc(B);
end;
B^ := ',';
end;
procedure TTextWriter.AddFieldName(const FieldName: RawUtf8);
begin
AddProp(Pointer(FieldName), length(FieldName));
end;
procedure TTextWriter.AddQuotedFieldName(const FieldName, VoidPlaceHolder: RawUtf8);
begin
AddQuotedFieldName(pointer(FieldName), length(FieldName), VoidPlaceHolder);
end;
procedure TTextWriter.AddQuotedFieldName(
FieldName: PUtf8Char; FieldNameLen: PtrInt; const VoidPlaceHolder: RawUtf8);
begin
if FieldNameLen = 0 then
begin
FieldName := pointer(VoidPlaceHolder);
FieldNameLen := length(VoidPlaceHolder);
end;
if BEnd - B <= FieldNameLen then
FlushToStream;
B[1] := '"';
MoveFast(FieldName^, B[2], FieldNameLen);
inc(B, FieldNameLen + 2);
B^ := '"';
end;
procedure TTextWriter.AddClassName(aClass: TClass);
begin
if aClass <> nil then
AddShort(ClassNameShort(aClass)^);
end;
procedure TTextWriter.AddInstanceName(Instance: TObject; SepChar: AnsiChar);
begin
Add('"');
if Instance = nil then
AddShorter('void')
else
AddShort(ClassNameShort(Instance)^);
AddDirect('(');
AddPointer(PtrUInt(Instance));
AddDirect(')', '"');
if SepChar <> #0 then
AddDirect(SepChar);
end;
procedure TTextWriter.AddInstancePointer(Instance: TObject; SepChar: AnsiChar;
IncludeUnitName, IncludePointer: boolean);
var
u: PShortString;
begin
if IncludeUnitName and
Assigned(ClassUnit) then
begin
u := ClassUnit(PClass(Instance)^);
if u^[0] <> #0 then
begin
AddShort(u^);
AddDirect('.');
end;
end;
AddShort(PPShortString(PPAnsiChar(Instance)^ + vmtClassName)^^);
if IncludePointer then
begin
AddDirect('(');
AddPointer(PtrUInt(Instance));
AddDirect(')');
end;
if SepChar<>#0 then
AddDirect(SepChar);
end;
procedure TTextWriter.AddShort(Text: PUtf8Char; TextLen: PtrInt);
begin
if TextLen <= 0 then
exit;
if BEnd - B <= TextLen then
FlushToStream;
MoveFast(Text^, B[1], TextLen);
inc(B, TextLen);
end;
procedure TTextWriter.AddShort(const Text: ShortString);
begin
if BEnd - B <= 255 then
FlushToStream;
MoveFast(Text[1], B[1], ord(Text[0]));
inc(B, ord(Text[0]));
end;
procedure TTextWriter.AddLine(const Text: ShortString);
var
L: PtrInt;
begin
L := ord(Text[0]);
if BEnd - B <= L then
FlushToStream;
inc(B);
if L > 0 then
begin
MoveFast(Text[1], B^, L);
inc(B, L);
end;
PCardinal(B)^ := 13 + 10 shl 8; // CR + LF
inc(B);
end;
procedure TTextWriter.AddOnSameLine(P: PUtf8Char);
var
D: PUtf8Char;
c: AnsiChar;
begin
if P = nil then
exit;
D := B + 1;
if P^ <> #0 then
repeat
if D >= BEnd then
begin
B := D - 1;
FlushToStream;
D := B + 1;
end;
c := P^;
if c < ' ' then
if c = #0 then
break
else
c := ' ';
D^ := c;
inc(P);
inc(D);
until false;
B := D - 1;
end;
procedure TTextWriter.AddOnSameLine(P: PUtf8Char; Len: PtrInt);
var
D: PUtf8Char;
c: AnsiChar;
begin
if (P = nil) or
(Len <= 0) then
exit;
D := B + 1;
repeat
if D >= BEnd then
begin
B := D - 1;
FlushToStream;
D := B + 1;
end;
c := P^;
if c < ' ' then
c := ' ';
D^ := c;
inc(D);
inc(P);
dec(Len);
until Len = 0;
B := D - 1;
end;
procedure TTextWriter.AddOnSameLineW(P: PWord; Len: PtrInt);
var
PEnd: PtrUInt;
c: cardinal;
begin
if P = nil then
exit;
if Len = 0 then
PEnd := 0
else
PEnd := PtrUInt(P) + PtrUInt(Len) * SizeOf(WideChar);
while (Len = 0) or
(PtrUInt(P) < PEnd) do
begin
if B >= BEnd then
FlushToStream;
// escape chars, so that all content will stay on the same text line
c := P^;
case c of
0:
break;
1..32:
begin
B[1] := ' ';
inc(B);
inc(P);
end;
33..127:
begin
B[1] := AnsiChar(c); // direct store 7-bit ASCII
inc(B);
inc(P);
end;
else // characters higher than #127 -> UTF-8 encode
inc(B, Utf16CharToUtf8(B + 1, P));
end;
end;
end;
procedure TTextWriter.AddOnSameLineString(const Text: string);
begin
{$ifdef UNICODE}
AddOnSameLineW(pointer(Text), length(Text));
{$else}
AddOnSameLine(pointer(Text), length(Text));
{$endif UNICODE}
end;
procedure TTextWriter.AddTrimLeftLowerCase(Text: PShortString);
var
P: PUtf8Char;
L: PtrInt;
begin
L := ord(Text^[0]);
P := @Text^[1];
while (L > 0) and
(P^ in ['a'..'z']) do
begin
inc(P);
dec(L);
end;
if L = 0 then
begin
L := ord(Text^[0]);
P := @Text^[1];
end;
AddShort(P, L);
end;
procedure TTextWriter.AddTrimSpaces(const Text: RawUtf8);
begin
AddTrimSpaces(pointer(Text));
end;
procedure TTextWriter.AddTrimSpaces(P: PUtf8Char);
var
c: AnsiChar;
begin
if P <> nil then
repeat
c := P^;
inc(P);
if c > ' ' then
Add(c);
until c = #0;
end;
procedure TTextWriter.AddReplace(Text: PUtf8Char; Orig, Replaced: AnsiChar);
begin
if Text <> nil then
while Text^ <> #0 do
begin
if Text^ = Orig then
Add(Replaced)
else
Add(Text^);
inc(Text);
end;
end;
procedure TTextWriter.AddByteToHex(Value: PtrUInt);
begin
if B >= BEnd then
FlushToStream;
PCardinal(B + 1)^ := TwoDigitsHexWB[Value];
inc(B, 2);
end;
procedure TTextWriter.AddByteToHexLower(Value: PtrUInt);
begin
if B >= BEnd then
FlushToStream;
PCardinal(B + 1)^ := TwoDigitsHexWBLower[Value];
inc(B, 2);
end;
procedure TTextWriter.AddInt18ToChars3(Value: cardinal);
begin
if B >= BEnd then
FlushToStream;
PCardinal(B + 1)^ := ((Value shr 12) and $3f) or
((Value shr 6) and $3f) shl 8 or
(Value and $3f) shl 16 + $202020;
inc(B, 3);
end;
procedure TTextWriter.AddString(const Text: RawUtf8);
var
L: PtrInt;
begin
L := PtrInt(Text);
if L <> 0 then
AddNoJsonEscape(pointer(Text), PStrLen(L - _STRLEN)^);
end;
procedure TTextWriter.AddSpaced(Text: PUtf8Char; TextLen, Width: PtrInt);
begin
if Width <= TextLen then
TextLen := Width // truncate text right
else
AddChars(' ', Width - TextLen);
AddNoJsonEscape(Text, TextLen);
end;
procedure TTextWriter.AddSpaced(const Text: RawUtf8; Width: PtrInt;
SepChar: AnsiChar);
begin
AddSpaced(pointer(Text), length(Text), Width);
if SepChar <> #0 then
Add(SepChar);
end;
procedure TTextWriter.AddSpaced(Value: QWord; Width: PtrInt; SepChar: AnsiChar);
var
tmp: array[0..23] of AnsiChar;
alt: TShort16;
p: PAnsiChar;
len: PtrInt;
begin
p := StrUInt64(@tmp[23], Value);
len := @tmp[23] - p;
if len > Width then
begin
K(Value, alt); // truncate to xxxK or xxxM
p := @alt[1];
len := ord(alt[0]);
end;
AddSpaced(p, len);
if SepChar <> #0 then
Add(SepChar);
end;
procedure TTextWriter.AddStringCopy(const Text: RawUtf8; start, len: PtrInt);
var
L: PtrInt;
begin
L := PtrInt(Text);
if (len <= 0) or
(L = 0) then
exit;
if start < 0 then
start := 0
else
dec(start);
L := PStrLen(L - _STRLEN)^;
dec(L, start);
if L > 0 then
begin
if len < L then
L := len;
AddNoJsonEscape(@PByteArray(Text)[start], L);
end;
end;
procedure TTextWriter.AddStrings(const Text: array of RawUtf8);
var
i: PtrInt;
begin
for i := 0 to high(Text) do
AddString(Text[i]);
end;
procedure TTextWriter.AddStrings(const Text: RawUtf8; count: PtrInt);
var
i, L, siz: PtrInt;
begin
L := length(Text);
siz := L * count;
if siz > 0 then
if siz > fTempBufSize then
for i := 1 to count do
AddString(Text) // would overfill our buffer -> manual append
else
begin
if BEnd - B <= siz then
FlushToStream;
for i := 1 to count do
begin
MoveFast(pointer(Text)^, B[1], L); // direct in-memory append
inc(B, L);
end;
end;
end;
procedure TTextWriter.AddBinToHexDisplay(Bin: pointer; BinBytes: PtrInt);
begin
if cardinal(BinBytes * 2 - 1) >= cardinal(fTempBufSize) then
exit;
if BEnd - B <= BinBytes * 2 then
FlushToStream;
BinToHexDisplay(Bin, PAnsiChar(B + 1), BinBytes);
inc(B, BinBytes * 2);
end;
procedure TTextWriter.AddBinToHexDisplayLower(Bin: pointer; BinBytes: PtrInt;
QuotedChar: AnsiChar);
var
max: PtrUInt;
begin
max := PtrUInt(BinBytes) * 2 + 1;
if PtrUInt(BEnd - B) <= max then
if max >= cardinal(fTempBufSize) then
exit // too big for a single call
else
FlushToStream;
inc(B);
if QuotedChar <> #0 then
begin
B^ := QuotedChar;
inc(B);
end;
BinToHexDisplayLower(Bin, pointer(B), BinBytes);
inc(B, BinBytes * 2);
if QuotedChar <> #0 then
B^ := QuotedChar
else
dec(B);
end;
procedure TTextWriter.AddBinToHexDisplayQuoted(Bin: pointer; BinBytes: PtrInt);
begin
AddBinToHexDisplayLower(Bin, BinBytes, '"');
end;
function DisplayMinChars(Bin: PByteArray; BinBytes: PtrInt): PtrInt;
{$ifdef HASINLINE}inline;{$endif}
begin
result := BinBytes;
repeat // append hexa chars up to the last non zero byte
dec(result);
until (result = 0) or
(Bin[result] <> 0);
inc(result);
end;
procedure TTextWriter.AddBinToHexDisplayMinChars(Bin: pointer; BinBytes: PtrInt;
QuotedChar: AnsiChar);
begin
if BinBytes > 0 then
AddBinToHexDisplayLower(Bin, DisplayMinChars(Bin, BinBytes), QuotedChar);
end;
procedure TTextWriter.AddPointer(P: PtrUInt; QuotedChar: AnsiChar);
begin
AddBinToHexDisplayLower(@P, DisplayMinChars(@P, SizeOf(P)), QuotedChar);
end;
procedure TTextWriter.AddBinToHex(Bin: Pointer; BinBytes: PtrInt; LowerHex: boolean);
var
chunk: PtrInt;
begin
if BinBytes <= 0 then
exit;
if B >= BEnd then
FlushToStream;
inc(B);
repeat
// guess biggest size to be added into buf^ at once
chunk := (BEnd - B) shr 1; // div 2 -> two hexa chars per byte
if BinBytes < chunk then
chunk := BinBytes;
// add hexa characters
if LowerHex then
mormot.core.text.BinToHexLower(PAnsiChar(Bin), PAnsiChar(B), chunk)
else
mormot.core.text.BinToHex(PAnsiChar(Bin), PAnsiChar(B), chunk);
inc(B, chunk * 2);
inc(PByte(Bin), chunk);
dec(BinBytes, chunk);
if BinBytes = 0 then
break;
// FlushToStream writes B-fTempBuf+1 -> special one below:
WriteToStream(fTempBuf, B - fTempBuf);
B := fTempBuf;
until false;
dec(B); // allow CancelLastChar
end;
procedure TTextWriter.AddBinToHexMinChars(Bin: Pointer; BinBytes: PtrInt;
LowerHex: boolean);
begin
if BinBytes > 0 then
AddBinToHex(Bin, DisplayMinChars(Bin, BinBytes), LowerHex);
end;
procedure TTextWriter.AddQuotedStr(Text: PUtf8Char; TextLen: PtrUInt;
Quote: AnsiChar; TextMaxLen: PtrInt);
var
q: PtrInt;
begin
Add(Quote);
if (TextMaxLen > 5) and
(TextLen > PtrUInt(TextMaxLen)) then
TextLen := TextMaxLen - 5
else
TextMaxLen := 0;
inc(TextLen, PtrUInt(Text)); // PUtf8Char(TextLen)=TextEnd
if Text <> nil then
begin
repeat
q := ByteScanIndex(pointer(Text), PUtf8Char(TextLen) - Text, byte(Quote));
if q < 0 then
begin
AddNoJsonEscape(Text, PUtf8Char(TextLen) - Text); // no double quote
break;
end;
inc(q); // include first Quote
AddNoJsonEscape(Text, q);
Add(Quote); // double Quote
inc(Text, q); // continue
until false;
if TextMaxLen <> 0 then
AddShorter('...');
end;
Add(Quote);
end;
procedure TTextWriter.AddUrlNameNormalize(U: PUtf8Char; L: PtrInt);
begin
if L <= 0 then
exit;
repeat
if B >= BEnd then
FlushToStream; // inlined Add() in the loop
inc(B);
case U^ of
#0:
begin
dec(B); // reached end of URI (should not happen if L is accurate)
break;
end;
'%':
if (L <= 2) or
not HexToChar(PAnsiChar(U + 1), B) then
B^ := '%' // browsers may not follow the RFC (e.g. encode % as % !)
else
begin
inc(U, 2); // jump %xx
dec(L, 2);
end;
'/':
if (L = 1) or
(U[1] <> '/') then
B^ := '/'
else
dec(B); // normalize URI by ignoring this first /
else
B^ := U^;
end;
inc(U);
dec(L);
until L = 0;
end;
var
HTML_ESC: array[hfAnyWhere..hfWithinAttributes] of TAnsiCharToByte;
HTML_ESCAPED: array[1..4] of string[7] = (
'&lt;', '&gt;', '&amp;', '&quot;');
procedure TTextWriter.AddHtmlEscape(Text: PUtf8Char; Fmt: TTextWriterHtmlFormat);
var
beg: PUtf8Char;
esc: PAnsiCharToByte;
begin
if Text = nil then
exit;
if Fmt <> hfNone then
begin
esc := @HTML_ESC[Fmt];
beg := Text;
repeat
while esc[Text^] = 0 do
inc(Text);
AddNoJsonEscape(beg, Text - beg);
if Text^ = #0 then
exit
else
AddShorter(HTML_ESCAPED[esc[Text^]]);
inc(Text);
beg := Text;
until Text^ = #0;
end
else
AddNoJsonEscape(Text, mormot.core.base.StrLen(Text)); // hfNone
end;
function HtmlEscape(const text: RawUtf8; fmt: TTextWriterHtmlFormat): RawUtf8;
var
temp: TTextWriterStackBuffer;
W: TTextWriter;
begin
if NeedsHtmlEscape(pointer(text), fmt) then
begin
W := TTextWriter.CreateOwnedStream(temp);
try
W.AddHtmlEscape(pointer(text), fmt);
W.SetText(result);
finally
W.Free;
end;
end
else
result := text;
end;
function HtmlEscapeString(const text: string; fmt: TTextWriterHtmlFormat): RawUtf8;
var
temp: TTextWriterStackBuffer;
W: TTextWriter;
begin
{$ifdef UNICODE}
if fmt = hfNone then
{$else}
if not NeedsHtmlEscape(pointer(text), fmt) then // work for any AnsiString
{$endif UNICODE}
begin
StringToUtf8(text, result);
exit;
end;
W := TTextWriter.CreateOwnedStream(temp);
try
W.AddHtmlEscapeString(text, fmt);
W.SetText(result);
finally
W.Free;
end;
end;
function NeedsHtmlEscape(Text: PUtf8Char; Fmt: TTextWriterHtmlFormat): boolean;
var
esc: PAnsiCharToByte;
begin
if (Text <> nil) and
(Fmt <> hfNone) then
begin
result := true;
esc := @HTML_ESC[Fmt];
repeat
if esc[Text^] <> 0 then
if Text^ = #0 then
break
else
exit;
inc(Text);
until false;
end;
result := false;
end;
procedure TTextWriter.AddHtmlEscape(Text: PUtf8Char; TextLen: PtrInt;
Fmt: TTextWriterHtmlFormat);
var
beg: PUtf8Char;
esc: PAnsiCharToByte;
begin
if (Text = nil) or
(TextLen <= 0) then
exit;
if Fmt = hfNone then
begin
AddNoJsonEscape(Text, TextLen);
exit;
end;
inc(TextLen, PtrInt(Text)); // TextLen = final PtrInt(Text)
esc := @HTML_ESC[Fmt];
repeat
beg := Text;
while (PtrUInt(Text) < PtrUInt(TextLen)) and
(esc[Text^] = 0) do
inc(Text);
AddNoJsonEscape(beg, Text - beg);
if (PtrUInt(Text) = PtrUInt(TextLen)) or
(Text^ = #0) then
exit
else
AddShorter(HTML_ESCAPED[esc[Text^]]);
inc(Text);
until false;
end;
procedure TTextWriter.AddHtmlEscapeW(Text: PWideChar;
Fmt: TTextWriterHtmlFormat);
var
tmp: TSynTempBuffer;
begin
if (Text = nil) or
(Fmt = hfNone) then
begin
AddNoJsonEscapeW(pointer(Text), 0);
exit;
end;
RawUnicodeToUtf8(Text, StrLenW(Text), tmp, [ccfNoTrailingZero]);
AddHtmlEscape(tmp.buf, tmp.Len, Fmt);
tmp.Done;
end;
procedure TTextWriter.AddHtmlEscapeString(const Text: string; Fmt: TTextWriterHtmlFormat);
var
tmp: TSynTempBuffer;
len: integer;
begin
len := StringToUtf8(Text, tmp);
AddHtmlEscape(tmp.buf, len, Fmt);
tmp.Done;
end;
procedure TTextWriter.AddHtmlEscapeUtf8(const Text: RawUtf8; Fmt: TTextWriterHtmlFormat);
begin
AddHtmlEscape(pointer(Text), length(Text), Fmt);
end;
var
XML_ESC: TAnsiCharToByte;
procedure TTextWriter.AddXmlEscape(Text: PUtf8Char);
var
i, beg: PtrInt;
esc: PAnsiCharToByte;
begin
if Text = nil then
exit;
esc := @XML_ESC;
i := 0;
repeat
if esc[Text[i]] = 0 then
begin
beg := i;
repeat // it is faster to handle all not-escaped chars at once
inc(i);
until esc[Text[i]] <> 0;
AddNoJsonEscape(Text + beg, i - beg);
end;
repeat
case Text[i] of
#0:
exit;
#1..#8, #11, #12, #14..#31:
; // ignore invalid character - see http://www.w3.org/TR/xml/#NT-Char
#9, #10, #13:
begin
// characters below ' ', #9 e.g. -> // '&#x09;'
AddShorter('&#x');
AddByteToHex(ord(Text[i]));
AddDirect(';');
end;
'<':
AddShorter('&lt;');
'>':
AddShorter('&gt;');
'&':
AddShorter('&amp;');
'"':
AddShorter('&quot;');
'''':
AddShorter('&apos;');
else
break; // should match XML_ESC[] lookup table
end;
inc(i);
until false;
until false;
end;
{ TEchoWriter }
constructor TEchoWriter.Create(Owner: TTextWriter);
begin
fWriter := Owner;
if Assigned(fWriter.OnFlushToStream) then
raise ESynException.CreateUtf8('Unexpected %.Create', [self]);
fWriter.OnFlushToStream := FlushToStream; // register
end;
destructor TEchoWriter.Destroy;
begin
if (fWriter <> nil) and
(TMethod(fWriter.OnFlushToStream).Data = self) then
fWriter.OnFlushToStream := nil; // unregister
inherited Destroy;
end;
procedure TEchoWriter.EchoPendingToBackground(aLevel: TSynLogLevel);
var
n, cap: PtrInt;
begin
fBackSafe.Lock;
try
n := fBack.Count;
if length(fBack.Level) = n then
begin
cap := NextGrow(n);
SetLength(fBack.Level, cap);
SetLength(fBack.Text, cap);
end;
fBack.Level[n] := aLevel;
fBack.Text[n] := fEchoBuf;
finally
fBackSafe.UnLock;
end;
end;
procedure TEchoWriter.AddEndOfLine(aLevel: TSynLogLevel);
var
e: PtrInt;
begin
if twoEndOfLineCRLF in fWriter.CustomOptions then
fWriter.AddCR
else
fWriter.Add(#10);
if fEchos = nil then
exit; // no redirection yet
fEchoStart := EchoFlush;
if fEchoPendingExecuteBackground then
EchoPendingToBackground(aLevel)
else
for e := length(fEchos) - 1 downto 0 do // for MultiEventRemove() below
try
fEchos[e](self, aLevel, fEchoBuf);
except // remove callback in case of exception during echoing
MultiEventRemove(fEchos, e);
end;
fEchoBuf := '';
end;
procedure TEchoWriter.EchoPendingExecute;
var
todo: TEchoWriterBack; // thread-safe per reference copy
i, e: PtrInt;
begin
if fBack.Count = 0 then
exit;
fBackSafe.Lock;
MoveFast(fBack, todo, SizeOf(fBack)); // fast copy without refcount
FillCharFast(fBack, SizeOf(fBack), 0);
fBackSafe.UnLock;
for i := 0 to todo.Count - 1 do
for e := length(fEchos) - 1 downto 0 do // for MultiEventRemove() below
try
fEchos[e](self, todo.Level[i], todo.Text[i]);
except // remove callback in case of exception during echoing in user code
MultiEventRemove(fEchos, e);
if fEchos = nil then
break;
end;
end;
procedure TEchoWriter.FlushToStream(Text: PUtf8Char; Len: PtrInt);
begin
if fEchos = nil then
exit;
EchoFlush;
fEchoStart := 0;
end;
procedure TEchoWriter.EchoAdd(const aEcho: TOnTextWriterEcho);
begin
if self <> nil then
if MultiEventAdd(fEchos, TMethod(aEcho)) then
if fEchos <> nil then
fEchoStart := fWriter.B - fWriter.fTempBuf + 1; // ignore any previous buffer
end;
procedure TEchoWriter.EchoRemove(const aEcho: TOnTextWriterEcho);
begin
if self <> nil then
MultiEventRemove(fEchos, TMethod(aEcho));
end;
function TEchoWriter.EchoFlush: PtrInt;
var
L, LI: PtrInt;
P: PUtf8Char;
begin
P := fWriter.fTempBuf;
result := fWriter.B - P + 1;
L := result - fEchoStart;
inc(P, fEchoStart);
while (L > 0) and
(P[L - 1] in [#10, #13]) do // trim right CR/LF chars
dec(L);
if L = 0 then
exit;
LI := length(fEchoBuf); // fast append to fEchoBuf
SetLength(fEchoBuf, LI + L);
MoveFast(P^, PByteArray(fEchoBuf)[LI], L);
end;
procedure TEchoWriter.EchoReset;
begin
fEchoBuf := '';
end;
function TEchoWriter.GetEndOfLineCRLF: boolean;
begin
result := twoEndOfLineCRLF in fWriter.CustomOptions;
end;
procedure TEchoWriter.SetEndOfLineCRLF(aEndOfLineCRLF: boolean);
begin
if aEndOfLineCRLF then
fWriter.CustomOptions := fWriter.CustomOptions + [twoEndOfLineCRLF]
else
fWriter.CustomOptions := fWriter.CustomOptions - [twoEndOfLineCRLF];
end;
function ObjectToJson(Value: TObject; Options: TTextWriterWriteObjectOptions): RawUtf8;
begin
ObjectToJson(Value, result, Options);
end;
procedure ObjectToJson(Value: TObject; var Result: RawUtf8;
Options: TTextWriterWriteObjectOptions);
var
temp: TTextWriterStackBuffer;
begin
if Value = nil then
Result := NULL_STR_VAR
else
with DefaultJsonWriter.CreateOwnedStream(temp) do
try
include(fCustomOptions, twoForceJsonStandard);
WriteObject(Value, Options);
SetText(Result);
finally
Free;
end;
end;
function ObjectToJsonDebug(Value: TObject;
Options: TTextWriterWriteObjectOptions): RawUtf8;
begin
// our JSON serialization detects and serialize Exception.Message
result := ObjectToJson(Value, Options);
end;
procedure ConsoleObject(Value: TObject; Options: TTextWriterWriteObjectOptions);
begin
ConsoleWrite(ObjectToJson(Value, Options));
end;
function EscapeHexBuffer(src, dest: PUtf8Char; srclen: integer;
const toescape: TSynAnsicharSet; escape: AnsiChar): PUtf8Char;
begin
result := dest;
if srclen > 0 then
repeat
if src^ in toescape then
begin
result^ := escape;
result := pointer(ByteToHex(pointer(result + 1), ord(src^)));
end
else
begin
result^ := src^;
inc(result);
end;
inc(src);
dec(srclen);
until srclen = 0;
end;
function EscapeHex(const src: RawUtf8;
const toescape: TSynAnsicharSet; escape: AnsiChar): RawUtf8;
var
l: PtrInt;
begin
l := length(src);
if l <> 0 then
begin
FastSetString(result, l * 3); // allocate maximum size
l := EscapeHexBuffer(pointer(src), pointer(result), l,
toescape, escape) - pointer(result);
end;
FakeSetLength(result, l); // return in-place with no realloc
end;
function UnescapeHexBuffer(src, dest: PUtf8Char; escape: AnsiChar): PUtf8Char;
var
c: AnsiChar;
begin
result := dest;
if src <> nil then
while src^ <> #0 do
begin
if src^ = escape then
begin
inc(src);
if src^ in [#10, #13] then // \CRLF or \LF
begin
repeat
inc(src);
until not (src^ in [#10, #13]);
continue;
end
else if HexToChar(PAnsiChar(src), @c) then // \xx
begin
result^ := c;
inc(src, 2);
inc(result);
continue;
end;
if src^ = #0 then // expect valid \c
break;
end;
result^ := src^;
inc(src);
inc(result);
end;
end;
function UnescapeHex(const src: RawUtf8; escape: AnsiChar): RawUtf8;
begin
if PosExChar(escape, src) = 0 then
result := src // no unescape needed
else
begin
FastSetString(result, length(src)); // allocate maximum size
FakeSetLength(result, UnescapeHexBuffer(
pointer(src), pointer(result), escape) - pointer(result));
end;
end;
function EscapeCharBuffer(src, dest: PUtf8Char; srclen: integer;
const toescape: TSynAnsicharSet; escape: AnsiChar): PUtf8Char;
begin
result := dest;
if srclen > 0 then
repeat
if src^ in toescape then
begin
result^ := escape;
inc(result);
end;
result^ := src^;
inc(result);
inc(src);
dec(srclen);
until srclen = 0;
end;
function EscapeChar(const src: RawUtf8;
const toescape: TSynAnsicharSet; escape: AnsiChar): RawUtf8;
var
l: PtrInt;
begin
l := length(src);
if l <> 0 then
begin
FastSetString(result, l * 2); // allocate maximum size
l := EscapeCharBuffer(pointer(src), pointer(result), l,
toescape, escape) - pointer(result);
end;
FakeSetLength(result, l); // return in-place with no realloc
end;
{ ************ Numbers (integers or floats) to Text Conversion }
procedure Int32ToUtf8(Value: PtrInt; var result: RawUtf8);
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
if PtrUInt(Value) <= high(SmallUInt32Utf8) then
result := SmallUInt32Utf8[Value]
else
begin
P := StrInt32(@tmp[23], Value);
FastSetString(result, P, @tmp[23] - P);
end;
end;
function Int32ToUtf8(Value: PtrInt): RawUtf8;
begin
Int32ToUtf8(Value, result);
end;
procedure Int64ToUtf8(Value: Int64; var result: RawUtf8);
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
{$ifdef CPU64}
if PtrUInt(Value) <= high(SmallUInt32Utf8) then
{$else} // Int64Rec gives compiler internal error C4963
if (PCardinalArray(@Value)^[0] <= high(SmallUInt32Utf8)) and
(PCardinalArray(@Value)^[1] = 0) then
{$endif CPU64}
result := SmallUInt32Utf8[Value]
else
begin
{$ifdef CPU64}
P := StrInt32(@tmp[23], Value);
{$else}
P := StrInt64(@tmp[23], Value);
{$endif CPU64}
FastSetString(result, P, @tmp[23] - P);
end;
end;
procedure UInt64ToUtf8(Value: QWord; var result: RawUtf8);
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
{$ifdef CPU64}
if Value <= high(SmallUInt32Utf8) then
{$else} // Int64Rec gives compiler internal error C4963
if (PCardinalArray(@Value)^[0] <= high(SmallUInt32Utf8)) and
(PCardinalArray(@Value)^[1] = 0) then
{$endif CPU64}
result := SmallUInt32Utf8[Value]
else
begin
{$ifdef CPU64}
P := StrUInt32(@tmp[23], Value);
{$else}
P := StrUInt64(@tmp[23], Value);
{$endif CPU64}
FastSetString(result, P, @tmp[23] - P);
end;
end;
function Int64ToUtf8(Value: Int64): RawUtf8; // faster than SysUtils.IntToStr
begin
Int64ToUtf8(Value, result);
end;
{$ifdef CPU32} // already implemented by ToUtf8(Value: PtrInt) below for CPU64
function ToUtf8(Value: Int64): RawUtf8;
begin
Int64ToUtf8(Value, result);
end;
{$endif CPU32}
function ToUtf8(Value: PtrInt): RawUtf8;
begin
Int32ToUtf8(Value, result);
end;
procedure UInt32ToUtf8(Value: PtrUInt; var result: RawUtf8);
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
if Value <= high(SmallUInt32Utf8) then
result := SmallUInt32Utf8[Value]
else
begin
P := StrUInt32(@tmp[23], Value);
FastSetString(result, P, @tmp[23] - P);
end;
end;
function UInt32ToUtf8(Value: PtrUInt): RawUtf8;
begin
UInt32ToUtf8(Value, result);
end;
function StrCurr64(P: PAnsiChar; const Value: Int64): PAnsiChar;
var
c: QWord;
d: cardinal;
begin
if Value = 0 then
begin
result := P - 1;
result^ := '0';
exit;
end;
if Value < 0 then
c := -Value
else
c := Value;
if c < 10000 then
begin
result := P - 6; // only decimals -> append '0.xxxx'
PCardinal(result)^ := ord('0') + ord('.') shl 8;
YearToPChar(c, PUtf8Char(P) - 4);
end
else
begin
result := StrUInt64(P - 1, c);
d := PCardinal(P - 5)^; // in two explit steps for CPUARM (alf)
PCardinal(P - 4)^ := d;
P[-5] := '.'; // insert '.' just before last 4 decimals
end;
if Value < 0 then
begin
dec(result);
result^ := '-';
end;
end;
procedure Curr64ToStr(const Value: Int64; var result: RawUtf8);
var
tmp: array[0..31] of AnsiChar;
P: PAnsiChar;
Decim, L: cardinal;
begin
if Value = 0 then
result := SmallUInt32Utf8[0]
else
begin
P := StrCurr64(@tmp[31], Value);
L := @tmp[31] - P;
if L > 4 then
begin
Decim := PCardinal(P + L - SizeOf(cardinal))^; // 4 last digits = 4 decimals
if Decim = ord('0') + ord('0') shl 8 + ord('0') shl 16 + ord('0') shl 24 then
dec(L, 5)
else // no decimal
if Decim and $ffff0000 = ord('0') shl 16 + ord('0') shl 24 then
dec(L, 2); // 2 decimals
end;
FastSetString(result, P, L);
end;
end;
function Curr64ToStr(const Value: Int64): RawUtf8;
begin
Curr64ToStr(Value, result);
end;
function CurrencyToStr(const Value: currency): RawUtf8;
begin
result := Curr64ToStr(PInt64(@Value)^);
end;
function Curr64ToPChar(const Value: Int64; Dest: PUtf8Char): PtrInt;
var
tmp: array[0..31] of AnsiChar;
P: PAnsiChar;
Decim: cardinal;
begin
P := StrCurr64(@tmp[31], Value);
result := @tmp[31] - P;
if result > 4 then
begin
// Decim = 4 last digits = 4 decimals
Decim := PCardinal(P + result - SizeOf(cardinal))^;
if Decim = ord('0') + ord('0') shl 8 + ord('0') shl 16 + ord('0') shl 24 then
// no decimal -> trunc trailing *.0000 chars
dec(result, 5)
else if Decim and $ffff0000 = ord('0') shl 16 + ord('0') shl 24 then
// 2 decimals -> trunc trailing *.??00 chars
dec(result, 2);
end;
MoveFast(P^, Dest^, result);
end;
function StrToCurr64(P: PUtf8Char; NoDecimal: PBoolean): Int64;
var
c: cardinal;
minus: boolean;
Dec: cardinal;
begin
result := 0;
if P = nil then
exit;
while (P^ <= ' ') and
(P^ <> #0) do
inc(P);
if P^ = '-' then
begin
minus := true;
repeat
inc(P)
until P^ <> ' ';
end
else
begin
minus := false;
if P^ = '+' then
repeat
inc(P)
until P^ <> ' ';
end;
if P^ = '.' then
begin
// '.5' -> 500
Dec := 2;
inc(P);
end
else
Dec := 0;
c := byte(P^) - 48;
if c > 9 then
exit;
PCardinal(@result)^ := c;
inc(P);
repeat
if P^ <> '.' then
begin
c := byte(P^) - 48;
if c > 9 then
break;
{$ifdef CPU32DELPHI}
result := result shl 3 + result + result;
{$else}
result := result * 10;
{$endif CPU32DELPHI}
inc(result, c);
inc(P);
if Dec <> 0 then
begin
inc(Dec);
if Dec < 5 then
continue
else
break;
end;
end
else
begin
inc(Dec);
inc(P);
end;
until false;
if NoDecimal <> nil then
if Dec = 0 then
begin
NoDecimal^ := true;
if minus then
result := -result;
exit;
end
else
NoDecimal^ := false;
if Dec <> 5 then
// Dec=5 most of the time
case Dec of
0, 1:
result := result * 10000;
{$ifdef CPU32DELPHI}
2:
result := result shl 10 - result shl 4 - result shl 3;
3:
result := result shl 6 + result shl 5 + result shl 2;
4:
result := result shl 3 + result + result;
{$else}
2:
result := result * 1000;
3:
result := result * 100;
4:
result := result * 10;
{$endif CPU32DELPHI}
end;
if minus then
result := -result;
end;
function StrToCurrency(P: PUtf8Char): currency;
begin
PInt64(@result)^ := StrToCurr64(P, nil);
end;
{$ifdef UNICODE}
function IntToString(Value: integer): string;
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
P := StrInt32(@tmp[23], Value);
Ansi7ToString(PWinAnsiChar(P), @tmp[23] - P, result);
end;
function IntToString(Value: cardinal): string;
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
P := StrUInt32(@tmp[23], Value);
Ansi7ToString(PWinAnsiChar(P), @tmp[23] - P, result);
end;
function IntToString(Value: Int64): string;
var
tmp: array[0..31] of AnsiChar;
P: PAnsiChar;
begin
P := StrInt64(@tmp[31], Value);
Ansi7ToString(PWinAnsiChar(P), @tmp[31] - P, result);
end;
function DoubleToString(Value: Double): string;
var
tmp: ShortString;
begin
if Value = 0 then
result := '0'
else
Ansi7ToString(PWinAnsiChar(@tmp[1]), DoubleToShort(@tmp, Value), result);
end;
function Curr64ToString(Value: Int64): string;
var
tmp: array[0..31] of AnsiChar;
begin
Ansi7ToString(tmp, Curr64ToPChar(Value, tmp), result);
end;
{$else UNICODE}
function IntToString(Value: integer): string;
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
if cardinal(Value) <= high(SmallUInt32Utf8) then
result := SmallUInt32Utf8[Value]
else
begin
P := StrInt32(@tmp[23], Value);
SetString(result, P, @tmp[23] - P);
end;
end;
function IntToString(Value: cardinal): string;
var
tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
if Value <= high(SmallUInt32Utf8) then
result := SmallUInt32Utf8[Value]
else
begin
P := StrUInt32(@tmp[23], Value);
SetString(result, P, @tmp[23] - P);
end;
end;
function IntToString(Value: Int64): string;
var
tmp: array[0..31] of AnsiChar;
P: PAnsiChar;
begin
if (Value >= 0) and
(Value <= high(SmallUInt32Utf8)) then
result := SmallUInt32Utf8[Value]
else
begin
P := StrInt64(@tmp[31], Value);
SetString(result, P, @tmp[31] - P);
end;
end;
function DoubleToString(Value: Double): string;
var
tmp: ShortString;
begin
if Value = 0 then
result := '0'
else
SetString(result, PAnsiChar(@tmp[1]), DoubleToShort(@tmp, Value));
end;
function Curr64ToString(Value: Int64): string;
begin
result := Curr64ToStr(Value);
end;
{$endif UNICODE}
{$ifndef EXTENDEDTOSHORT_USESTR}
var // standard FormatSettings (US)
SettingsUS: TFormatSettings;
{$endif EXTENDEDTOSHORT_USESTR}
// used ExtendedToShortNoExp / DoubleToShortNoExp from str/DoubleToAscii output
function FloatStringNoExp(S: PAnsiChar; Precision: PtrInt): PtrInt;
var
i, prec: PtrInt;
c: AnsiChar;
begin
result := ord(S[0]);
prec := result; // if no decimal
if S[1] = '-' then
dec(prec);
// test if scientific format -> return as this
for i := 2 to result do
begin
c := S[i];
if c = 'E' then // should not appear
exit
else if c = '.' then
if i >= Precision then
begin
// return huge decimal number as is
result := i - 1;
exit;
end
else
dec(prec);
end;
if (prec >= Precision) and
(prec <> result) then
begin
dec(result, prec - Precision);
if S[result + 1] > '5' then
begin
// manual rounding
prec := result;
repeat
c := S[prec];
if c <> '.' then
if c = '9' then
begin
S[prec] := '0';
if ((prec = 2) and
(S[1] = '-')) or
(prec = 1) then
begin
i := result;
inc(S, prec);
repeat
// inlined MoveFast(S[prec],S[prec+1],result);
S[i] := S[i - 1];
dec(i);
until i = 0;
S^ := '1';
dec(S, prec);
break;
end;
end
else if (c >= '0') and
(c <= '8') then
begin
inc(S[prec]);
break;
end
else
break;
dec(prec);
until prec = 0;
end; // note: this fixes http://stackoverflow.com/questions/2335162
end;
if S[result] = '0' then
repeat
// trunc any trailing 0
dec(result);
c := S[result];
if c <> '.' then
if c <> '0' then
break
else
continue
else
begin
dec(result);
if (result = 2) and
(S[1] = '-') and
(S[2] = '0') then
begin
result := 1;
S[1] := '0'; // '-0.000' -> '0'
end;
break; // if decimal are all '0' -> return only integer part
end;
until false;
end;
function ExtendedToShortNoExp(S: PShortString; Value: TSynExtended;
Precision: integer): integer;
begin
{$ifdef DOUBLETOSHORT_USEGRISU}
if Precision = DOUBLE_PRECISION then
DoubleToAscii(0, DOUBLE_PRECISION, Value, pointer(S))
else
{$endif DOUBLETOSHORT_USEGRISU}
str(Value: 0: Precision, S^); // not str(Value:0,S) -> ' 0.0E+0000'
result := FloatStringNoExp(pointer(S), Precision);
S^[0] := AnsiChar(result);
end;
const // range when to switch into scientific notation - minimal 6 digits
SINGLE_HI = 1E3;
SINGLE_LO = 1E-3;
DOUBLE_HI = 1E9;
DOUBLE_LO = 1E-9;
{$ifdef TSYNEXTENDED80}
EXT_HI = 1E12;
EXT_LO = 1E-12;
{$endif TSYNEXTENDED80}
{$ifdef EXTENDEDTOSHORT_USESTR}
function ExtendedToShort(S: PShortString; Value: TSynExtended; Precision: integer): integer;
var
scientificneeded: boolean;
valueabs: TSynExtended;
begin
{$ifdef DOUBLETOSHORT_USEGRISU}
if Precision = DOUBLE_PRECISION then
begin
result := DoubleToShort(S, Value);
exit;
end;
{$endif DOUBLETOSHORT_USEGRISU}
if Value = 0 then
begin
PCardinal(S)^ := 1 + ord('0') shl 8;
result := 1;
exit;
end;
scientificneeded := false;
valueabs := abs(Value);
if Precision <= SINGLE_PRECISION then
begin
if (valueabs > SINGLE_HI) or
(valueabs < SINGLE_LO) then
scientificneeded := true;
end
else
{$ifdef TSYNEXTENDED80}
if Precision > DOUBLE_PRECISION then
begin
if (valueabs > EXT_HI) or
(valueabs < EXT_LO) then
scientificneeded := true;
end
else
{$endif TSYNEXTENDED80}
if (valueabs > DOUBLE_HI) or
(valueabs < DOUBLE_LO) then
scientificneeded := true;
if scientificneeded then
begin
str(Value, S^);
if S^[1] = ' ' then
begin
dec(S^[0]);
MoveFast(S^[2], S^[1], ord(S^[0]));
end;
result := ord(S^[0]);
end
else
begin
str(Value: 0:Precision, S^); // not str(Value:0,S) -> ' 0.0E+0000'
result := FloatStringNoExp(pointer(S), Precision);
S^[0] := AnsiChar(result);
end;
end;
{$else not EXTENDEDTOSHORT_USESTR}
function ExtendedToShort(S: PShortString; Value: TSynExtended; Precision: integer): integer;
{$ifdef UNICODE}
var
i: PtrInt;
{$endif UNICODE}
begin
// use ffGeneral: see https://synopse.info/forum/viewtopic.php?pid=442#p442
result := FloatToText(PChar(@S^[1]), Value, fvExtended, ffGeneral, Precision, 0, SettingsUS);
{$ifdef UNICODE} // FloatToText(PWideChar) is faster than FloatToText(PAnsiChar)
for i := 1 to result do
PByteArray(S)[i] := PWordArray(PtrInt(S) - 1)[i];
{$endif UNICODE}
S^[0] := AnsiChar(result);
end;
{$endif EXTENDEDTOSHORT_USESTR}
function FloatToShortNan(const s: ShortString): TFloatNan;
begin
case PInteger(@s)^ and $ffdfdfdf of
3 + ord('N') shl 8 + ord('A') shl 16 + ord('N') shl 24:
result := fnNan;
3 + ord('I') shl 8 + ord('N') shl 16 + ord('F') shl 24,
4 + ord('+') shl 8 + ord('I') shl 16 + ord('N') shl 24:
result := fnInf;
4 + ord('-') shl 8 + ord('I') shl 16 + ord('N') shl 24:
result := fnNegInf;
else
result := fnNumber;
end;
end;
function FloatToStrNan(const s: RawUtf8): TFloatNan;
begin
case length(s) of
3:
case PInteger(s)^ and $dfdfdf of
ord('N') + ord('A') shl 8 + ord('N') shl 16:
result := fnNan;
ord('I') + ord('N') shl 8 + ord('F') shl 16:
result := fnInf;
else
result := fnNumber;
end;
4:
case PInteger(s)^ and $dfdfdfdf of
ord('+') + ord('I') shl 8 + ord('N') shl 16 + ord('F') shl 24:
result := fnInf;
ord('-') + ord('I') shl 8 + ord('N') shl 16 + ord('F') shl 24:
result := fnNegInf;
else
result := fnNumber;
end;
else
result := fnNumber;
end;
end;
function ExtendedToStr(Value: TSynExtended; Precision: integer): RawUtf8;
begin
ExtendedToStr(Value, Precision, result);
end;
procedure ExtendedToStr(Value: TSynExtended; Precision: integer; var result: RawUtf8);
var
tmp: ShortString;
begin
if Value = 0 then
result := SmallUInt32Utf8[0]
else
FastSetString(result, @tmp[1], ExtendedToShort(@tmp, Value, Precision));
end;
function FloatToJsonNan(s: PShortString): PShortString;
begin
case PInteger(s)^ and $ffdfdfdf of
3 + ord('N') shl 8 + ord('A') shl 16 + ord('N') shl 24:
result := @JSON_NAN[fnNan];
3 + ord('I') shl 8 + ord('N') shl 16 + ord('F') shl 24,
4 + ord('+') shl 8 + ord('I') shl 16 + ord('N') shl 24:
result := @JSON_NAN[fnInf];
4 + ord('-') shl 8 + ord('I') shl 16 + ord('N') shl 24:
result := @JSON_NAN[fnNegInf];
else
result := s;
end;
end;
function ExtendedToJson(tmp: PShortString; Value: TSynExtended;
Precision: integer; NoExp: boolean): PShortString;
begin
if Value = 0 then
result := @JSON_NAN[fnNumber]
else
begin
if NoExp then
ExtendedToShortNoExp(tmp, Value, Precision)
else
ExtendedToShort(tmp, Value, Precision);
result := FloatToJsonNan(tmp);
end;
end;
{$ifdef DOUBLETOSHORT_USEGRISU}
{
Implement 64-bit floating point (double) to ASCII conversion using the
GRISU-1 efficient algorithm.
Original Code in flt_core.inc flt_conv.inc flt_pack.inc from FPC RTL.
Copyright (C) 2013 by Max Nazhalov
Licenced with LGPL 2 with the linking exception.
If you don't agree with these License terms, disable this feature
by undefining DOUBLETOSHORT_USEGRISU in Synopse.inc
GRISU Original Algorithm
Copyright (c) 2009 Florian Loitsch
We extracted a double-to-ascii only cut-down version of those files,
and made a huge refactoring to reach the best performance, especially
tuning the Intel target with some dedicated asm and code rewrite.
With Delphi 10.3 on Win32:
100000 FloatToText in 38.11ms i.e. 2,623,570/s, aver. 0us, 47.5 MB/s
100000 str in 43.19ms i.e. 2,315,082/s, aver. 0us, 50.7 MB/s
100000 DoubleToShort in 45.50ms i.e. 2,197,367/s, aver. 0us, 43.8 MB/s
100000 DoubleToAscii in 42.44ms i.e. 2,356,045/s, aver. 0us, 47.8 MB/s
With Delphi 10.3 on Win64:
100000 FloatToText in 61.83ms i.e. 1,617,233/s, aver. 0us, 29.3 MB/s
100000 str in 53.20ms i.e. 1,879,663/s, aver. 0us, 41.2 MB/s
100000 DoubleToShort in 18.45ms i.e. 5,417,998/s, aver. 0us, 108 MB/s
100000 DoubleToAscii in 18.19ms i.e. 5,496,921/s, aver. 0us, 111.5 MB/s
With FPC on Win32:
100000 FloatToText in 115.62ms i.e. 864,842/s, aver. 1us, 15.6 MB/s
100000 str in 57.30ms i.e. 1,745,109/s, aver. 0us, 39.9 MB/s
100000 DoubleToShort in 23.88ms i.e. 4,187,078/s, aver. 0us, 83.5 MB/s
100000 DoubleToAscii in 23.34ms i.e. 4,284,490/s, aver. 0us, 86.9 MB/s
With FPC on Win64:
100000 FloatToText in 76.92ms i.e. 1,300,052/s, aver. 0us, 23.5 MB/s
100000 str in 27.70ms i.e. 3,609,456/s, aver. 0us, 82.6 MB/s
100000 DoubleToShort in 14.73ms i.e. 6,787,944/s, aver. 0us, 135.4 MB/s
100000 DoubleToAscii in 13.78ms i.e. 7,253,735/s, aver. 0us, 147.2 MB/s
With FPC on Linux x86_64:
100000 FloatToText in 81.48ms i.e. 1,227,249/s, aver. 0us, 22.2 MB/s
100000 str in 36.98ms i.e. 2,703,871/s, aver. 0us, 61.8 MB/s
100000 DoubleToShort in 13.11ms i.e. 7,626,601/s, aver. 0us, 152.1 MB/s
100000 DoubleToAscii in 12.59ms i.e. 7,942,180/s, aver. 0us, 161.2 MB/s
- Our rewrite is twice faster than original flt_conv.inc from FPC RTL (str)
- Delphi Win32 has trouble making 64-bit computation - no benefit since it
has good optimized i87 asm (but slower than our code with FPC/Win32)
- FPC is more efficient when compiling integer arithmetic; we avoided slow
division by calling our Div100(), but Delphi Win64 is still far behind
- Delphi Win64 has very slow FloatToText and str()
}
// Controls printing of NaN-sign.
// Undefine to print NaN sign during float->ASCII conversion.
// IEEE does not interpret the sign of a NaN, so leave it defined.
{$define GRISU1_F2A_NAN_SIGNLESS}
// Controls rounding of generated digits when formatting with narrowed
// width (either fixed or exponential notation).
// Traditionally, FPC and BP7/Delphi use "roundTiesToAway" mode.
// Undefine to use "roundTiesToEven" approach.
{$define GRISU1_F2A_HALF_ROUNDUP}
// This one is a hack against Grusu sub-optimality.
// It may be used only strictly together with GRISU1_F2A_HALF_ROUNDUP.
// It does not violate most general rules due to the fact that it is
// applicable only when formatting with narrowed width, where the fine
// view is more desirable, and the precision is already lost, so it can
// be used in general-purpose applications.
// Refer to its implementation.
{$define GRISU1_F2A_AGRESSIVE_ROUNDUP} // Defining this fixes several tests.
// Undefine to enable SNaN support.
// Note: IEEE [754-2008, page 31] requires (1) to recognize "SNaN" during
// ASCII->float, and (2) to generate the "invalid FP operation" exception
// either when SNaN is printed as "NaN", or "SNaN" is evaluated to QNaN,
// so it would be preferable to undefine these settings,
// but the FPC RTL is not ready for this right now..
{$define GRISU1_F2A_NO_SNAN}
/// If Value=0 would just store '0', whatever frac_digits is supplied.
{$define GRISU1_F2A_ZERONOFRACT}
var
/// fast lookup table for converting any decimal number from
// 0 to 99 into their byte digits (00..99) equivalence
// - used e.g. by DoubleToAscii() implementing Grisu algorithm
TwoDigitByteLookupW: packed array[0..99] of word;
const
// TFloatFormatProfile for double
nDig_mantissa = 17;
nDig_exp10 = 3;
type
// "Do-It-Yourself Floating-Point" structures
TDIY_FP = record
f: qword;
e: integer;
end;
TDIY_FP_Power_of_10 = record
c: TDIY_FP;
e10: integer;
end;
PDIY_FP_Power_of_10 = ^TDIY_FP_Power_of_10;
const
ROUNDER = $80000000;
{$ifdef CPUINTEL} // our faster version using 128-bit x86_64 multiplication
procedure d2a_diy_fp_multiply(var x, y: TDIY_FP; normalize: boolean;
out result: TDIY_FP); {$ifdef HASINLINE}inline;{$endif}
var
p: THash128Rec;
begin
mul64x64(x.f, y.f, p); // fast x86_64 / i386 asm
if (p.c1 and ROUNDER) <> 0 then
inc(p.h);
result.f := p.h;
result.e := PtrInt(x.e) + PtrInt(y.e) + 64;
if normalize then
if (PQWordRec(@result.f)^.h and ROUNDER) = 0 then
begin
result.f := result.f * 2;
dec(result.e);
end;
end;
{$else} // regular Grisu method - optimized for 32-bit CPUs
procedure d2a_diy_fp_multiply(var x, y: TDIY_FP; normalize: boolean; out result: TDIY_FP);
var
_x: TQWordRec absolute x;
_y: TQWordRec absolute y;
r: TQWordRec absolute result;
ac, bc, ad, bd, t1: TQWordRec;
begin
ac.v := qword(_x.h) * _y.h;
bc.v := qword(_x.l) * _y.h;
ad.v := qword(_x.h) * _y.l;
bd.v := qword(_x.l) * _y.l;
t1.v := qword(ROUNDER) + bd.h + bc.l + ad.l;
result.f := ac.v + ad.h + bc.h + t1.h;
result.e := x.e + y.e + 64;
if normalize then
if (r.h and ROUNDER) = 0 then
begin
inc(result.f, result.f);
dec(result.e);
end;
end;
{$endif CPUINTEL}
const
// alpha =-61; gamma = 0
// full cache: 1E-450 .. 1E+432, step = 1E+18
// sparse = 1/10
C_PWR10_DELTA = 18;
C_PWR10_COUNT = 50;
type
TDIY_FP_Cached_Power10 = record
base: array [ 0 .. 9 ] of TDIY_FP_Power_of_10;
factor_plus: array [ 0 .. 1 ] of TDIY_FP_Power_of_10;
factor_minus: array [ 0 .. 1 ] of TDIY_FP_Power_of_10;
// extra mantissa correction [ulp; signed]
corrector: array [ 0 .. C_PWR10_COUNT - 1 ] of shortint;
end;
const
CACHED_POWER10: TDIY_FP_Cached_Power10 = (
base: (
( c: ( f: qword($825ECC24C8737830); e: -362 ); e10: -90 ),
( c: ( f: qword($E2280B6C20DD5232); e: -303 ); e10: -72 ),
( c: ( f: qword($C428D05AA4751E4D); e: -243 ); e10: -54 ),
( c: ( f: qword($AA242499697392D3); e: -183 ); e10: -36 ),
( c: ( f: qword($9392EE8E921D5D07); e: -123 ); e10: -18 ),
( c: ( f: qword($8000000000000000); e: -63 ); e10: 0 ),
( c: ( f: qword($DE0B6B3A76400000); e: -4 ); e10: 18 ),
( c: ( f: qword($C097CE7BC90715B3); e: 56 ); e10: 36 ),
( c: ( f: qword($A70C3C40A64E6C52); e: 116 ); e10: 54 ),
( c: ( f: qword($90E40FBEEA1D3A4B); e: 176 ); e10: 72 )
);
factor_plus: (
( c: ( f: qword($F6C69A72A3989F5C); e: 534 ); e10: 180 ),
( c: ( f: qword($EDE24AE798EC8284); e: 1132 ); e10: 360 )
);
factor_minus: (
( c: ( f: qword($84C8D4DFD2C63F3B); e: -661 ); e10: -180 ),
( c: ( f: qword($89BF722840327F82); e: -1259 ); e10: -360 )
);
corrector: (
0, 0, 0, 0, 1, 0, 0, 0, 1, -1,
0, 1, 1, 1, -1, 0, 0, 1, 0, -1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
-1, 0, 0, -1, 0, 0, 0, 0, 0, -1,
0, 0, 0, 0, 1, 0, 0, 0, -1, 0
));
CACHED_POWER10_MIN10 = -90 -360;
// = ref.base[low(ref.base)].e10 + ref.factor_minus[high(ref.factor_minus)].e10
// return normalized correctly rounded approximation of the power of 10
// scaling factor, intended to shift a binary exponent of the original number
// into selected [ alpha .. gamma ] range
procedure d2a_diy_fp_cached_power10(exp10: integer; out factor: TDIY_FP_Power_of_10);
var
i, xmul: integer;
A, B: PDIY_FP_Power_of_10;
cx: PtrInt;
ref: ^TDIY_FP_Cached_Power10;
begin
ref := @CACHED_POWER10; // much better code generation on PIC/x86_64
// find non-sparse index
if exp10 <= CACHED_POWER10_MIN10 then
i := 0
else
begin
i := (exp10 - CACHED_POWER10_MIN10) div C_PWR10_DELTA;
if i * C_PWR10_DELTA + CACHED_POWER10_MIN10 <> exp10 then
inc(i); // round-up
if i > C_PWR10_COUNT - 1 then
i := C_PWR10_COUNT - 1;
end;
// generate result
xmul := i div length(ref.base);
A := @ref.base[i - (xmul * length(ref.base))]; // fast mod
dec(xmul, length(ref.factor_minus));
if xmul = 0 then
begin
// base
factor := A^;
exit;
end;
// surrogate
if xmul > 0 then
begin
dec(xmul);
B := @ref.factor_plus[xmul];
end
else
begin
xmul := -(xmul + 1);
B := @ref.factor_minus[xmul];
end;
factor.e10 := A.e10 + B.e10;
if A.e10 <> 0 then
begin
d2a_diy_fp_multiply(A.c, B.c, true, factor.c);
// adjust mantissa
cx := ref.corrector[i];
if cx <> 0 then
inc(int64(factor.c.f), int64(cx));
end
else
// exact
factor.c := B^.c;
end;
procedure d2a_unpack_float(const f: double; out minus: boolean;
out result: TDIY_FP); {$ifdef HASINLINE}inline;{$endif}
type
TSplitFloat = packed record
case byte of
0: (f: double);
1: (b: array[0..7] of byte);
2: (w: array[0..3] of word);
3: (d: array[0..1] of cardinal);
4: (l: qword);
end;
var
doublebits: TSplitFloat;
begin
{$ifdef FPC_DOUBLE_HILO_SWAPPED}
// high and low cardinal are swapped when using the arm fpa
doublebits.d[0] := TSplitFloat(f).d[1];
doublebits.d[1] := TSplitFloat(f).d[0];
{$else not FPC_DOUBLE_HILO_SWAPPED}
doublebits.f := f;
{$endif FPC_DOUBLE_HILO_SWAPPED}
{$ifdef endian_big}
minus := (doublebits.b[0] and $80 <> 0);
result.e := (doublebits.w[0] shr 4) and $7FF;
{$else endian_little}
minus := (doublebits.b[7] and $80 <> 0);
result.e := (doublebits.w[3] shr 4) and $7FF;
{$endif endian}
result.f := doublebits.l and $000FFFFFFFFFFFFF;
end;
const
C_FRAC2_BITS = 52;
C_EXP2_BIAS = 1023;
C_DIY_FP_Q = 64;
C_GRISU_ALPHA = -61;
C_GRISU_GAMMA = 0;
C_EXP2_SPECIAL = C_EXP2_BIAS * 2 + 1;
C_MANT2_INTEGER = qword(1) shl C_FRAC2_BITS;
type
TAsciiDigits = array[0..39] of byte;
PAsciiDigits = ^TAsciiDigits;
// convert unsigned integers into decimal digits
{$ifdef FPC_64} // leverage efficient FPC 64-bit division as mul reciprocal
function d2a_gen_digits_64(buf: PAsciiDigits; x: qword): PtrInt;
var
tab: PWordArray;
P: PAnsiChar;
c100: qword;
begin
tab := @TwoDigitByteLookupW; // 0..99 value -> two byte digits (00..99)
P := PAnsiChar(@buf[24]); // append backwards
repeat
if x >= 100 then
begin
dec(P, 2);
c100 := x div 100;
dec(x, c100 * 100);
PWord(P)^ := tab[x]; // 2 digits per loop
if c100 = 0 then
break;
x := c100;
continue;
end;
if x < 10 then
begin
dec(P);
P^ := AnsiChar(x); // 0..9
break;
end;
dec(P, 2);
PWord(P)^ := tab[x]; // 10..99
break;
until false;
PHash192(buf)^ := PHash192(P)^; // faster than MoveByOne(P,buf,result)
result := PAnsiChar(@buf[24]) - P;
end;
{$else not FPC_64} // use three 32-bit groups of digit
function d2a_gen_digits_32(buf: PAsciiDigits; x: dword; pad_9zero: boolean): PtrInt;
const
digits: array[0..9] of cardinal = (
0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000);
var
n: PtrInt;
m: cardinal;
{$ifdef FPC}
z: cardinal;
{$else}
d100: TDiv100Rec;
{$endif FPC}
tab: PWordArray;
begin
// Calculate amount of digits
if x = 0 then
n := 0 // emit nothing if padding is not required
else
begin
n := integer((BSRdword(x) + 1) * 1233) shr 12;
if x >= digits[n] then
inc(n);
end;
if pad_9zero and (n < 9) then
n := 9;
result := n;
if n = 0 then
exit;
// Emit digits
dec(PByte(buf));
tab := @TwoDigitByteLookupW;
m := x;
while (n >= 2) and (m <> 0) do
begin
dec(n);
{$ifdef FPC} // FPC will use fast mul reciprocal
z := m div 100; // compute two 0..9 digits
PWord(@buf[n])^ := tab^[m - z * 100];
m := z;
{$else}
Div100(m, d100); // our asm is faster than Delphi div operation
PWord(@buf[n])^ := tab^[d100.M];
m := d100.D;
{$endif FPC}
dec(n);
end;
if n = 0 then
exit;
if m <> 0 then
begin
if m > 9 then
m := m mod 10; // compute last 0..9 digit
buf[n] := m;
dec(n);
if n = 0 then
exit;
end;
repeat
buf[n] := 0; // padding with 0
dec(n);
until n = 0;
end;
function d2a_gen_digits_64(buf: PAsciiDigits; const x: qword): PtrInt;
var
n_digits: PtrInt;
temp: qword;
splitl, splitm, splith: cardinal;
begin
// Split X into 3 unsigned 32-bit integers; lower two should be < 10 digits long
n_digits := 0;
if x < 1000000000 then
splitl := x
else
begin
temp := x div 1000000000;
splitl := x - temp * 1000000000;
if temp < 1000000000 then
splitm := temp
else
begin
splith := temp div 1000000000;
splitm := cardinal(temp) - splith * 1000000000;
n_digits := d2a_gen_digits_32(buf, splith, false); // Generate hi digits
end;
inc(n_digits, d2a_gen_digits_32(@buf[n_digits], splitm, n_digits <> 0));
end;
// Generate digits
inc(n_digits, d2a_gen_digits_32(@buf[n_digits], splitl, n_digits <> 0));
result := n_digits;
end;
{$endif FPC_64}
// Performs digit sequence rounding, returns decimal point correction
function d2a_round_digits(var buf: TAsciiDigits; var n_current: integer;
n_max: PtrInt; half_round_to_even: boolean = true): PtrInt;
var
n: PtrInt;
dig_round, dig_sticky: byte;
{$ifdef GRISU1_F2A_AGRESSIVE_ROUNDUP}
i: PtrInt;
{$endif GRISU1_F2A_AGRESSIVE_ROUNDUP}
begin
result := 0;
n := n_current;
n_current := n_max;
// Get round digit
dig_round := buf[n_max];
{$ifdef GRISU1_F2A_AGRESSIVE_ROUNDUP}
// Detect if rounding-up the second last digit turns the "dig_round"
// into "5"; also make sure we have at least 1 digit between "dig_round"
// and the second last.
if not half_round_to_even then
if (dig_round = 4) and
(n_max < n - 3) then
if buf[n - 2] >= 8 then // somewhat arbitrary...
begin
// check for only "9" are in between
i := n - 2;
repeat
dec(i);
until (i = n_max) or
(buf[i] <> 9);
if i = n_max then
// force round-up
dig_round := 9; // any value ">=5"
end;
{$endif GRISU1_F2A_AGRESSIVE_ROUNDUP}
if dig_round < 5 then
exit;
// Handle "round half to even" case
if (dig_round = 5) and
half_round_to_even and
((n_max = 0) or
(buf[n_max - 1] and 1 = 0)) then
begin
// even and a half: check if exactly the half
dig_sticky := 0;
while (n > n_max + 1) and (dig_sticky = 0) do
begin
dec(n);
dig_sticky := buf[n];
end;
if dig_sticky = 0 then
exit; // exactly a half -> no rounding is required
end;
// Round-up
while n_max > 0 do
begin
dec(n_max);
inc(buf[n_max]);
if buf[n_max] < 10 then
begin
// no more overflow: stop now
n_current := n_max + 1;
exit;
end;
// continue rounding
end;
// Overflow out of the 1st digit, all n_max digits became 0
buf[0] := 1;
n_current := 1;
result := 1;
end;
// format the number in the fixed-point representation
procedure d2a_return_fixed(str: PAnsiChar; minus: boolean;
var digits: TAsciiDigits; n_digits_have, fixed_dot_pos, frac_digits: integer);
var
p: PAnsiChar;
d: PByte;
cut_digits_at, n_before_dot, n_before_dot_pad0, n_after_dot_pad0,
n_after_dot, n_tail_pad0: integer;
begin
// Round digits if necessary
cut_digits_at := fixed_dot_pos + frac_digits;
if cut_digits_at < 0 then
// zero
n_digits_have := 0
else if cut_digits_at < n_digits_have then
// round digits
inc(fixed_dot_pos, d2a_round_digits(digits, n_digits_have, cut_digits_at
{$ifdef GRISU1_F2A_HALF_ROUNDUP}, false {$endif} ));
// Before dot: digits, pad0
if (fixed_dot_pos <= 0) or
(n_digits_have = 0) then
begin
n_before_dot := 0;
n_before_dot_pad0 := 1;
end
else if fixed_dot_pos > n_digits_have then
begin
n_before_dot := n_digits_have;
n_before_dot_pad0 := fixed_dot_pos - n_digits_have;
end
else
begin
n_before_dot := fixed_dot_pos;
n_before_dot_pad0 := 0;
end;
// After dot: pad0, digits, pad0
if fixed_dot_pos < 0 then
n_after_dot_pad0 := -fixed_dot_pos
else
n_after_dot_pad0 := 0;
if n_after_dot_pad0 > frac_digits then
n_after_dot_pad0 := frac_digits;
n_after_dot := n_digits_have - n_before_dot;
n_tail_pad0 := frac_digits - n_after_dot - n_after_dot_pad0;
p := str + 1;
// Sign
if minus then
begin
p^ := '-';
inc(p);
end;
// integer significant digits
d := @digits;
if n_before_dot > 0 then
repeat
p^ := AnsiChar(d^ + ord('0'));
inc(p);
inc(d);
dec(n_before_dot);
until n_before_dot = 0;
// integer 0-padding
if n_before_dot_pad0 > 0 then
repeat
p^ := '0';
inc(p);
dec(n_before_dot_pad0);
until n_before_dot_pad0 = 0;
// Fractional part
if frac_digits <> 0 then
begin
// Dot
p^ := '.';
inc(p);
// Pre-fraction 0-padding
if n_after_dot_pad0 > 0 then
repeat
p^ := '0';
inc(p);
dec(n_after_dot_pad0);
until n_after_dot_pad0 = 0;
// Fraction significant digits
if n_after_dot > 0 then
repeat
p^ := AnsiChar(d^ + ord('0'));
inc(p);
inc(d);
dec(n_after_dot);
until n_after_dot = 0;
// Tail 0-padding
if n_tail_pad0 > 0 then
repeat
p^ := '0';
inc(p);
dec(n_tail_pad0);
until n_tail_pad0 = 0;
end;
// Store length
str[0] := AnsiChar(p - str - 1);
end;
// formats the number as exponential representation
procedure d2a_return_exponential(str: PAnsiChar; minus: boolean;
digits: PByte; n_digits_have, n_digits_req, d_exp: PtrInt);
var
p, exp: PAnsiChar;
begin
p := str + 1;
// Sign
if minus then
begin
p^ := '-';
inc(p);
end;
// integer part
if n_digits_have > 0 then
begin
p^ := AnsiChar(digits^ + ord('0'));
dec(n_digits_have);
end
else
p^ := '0';
inc(p);
// Dot
if n_digits_req > 1 then
begin
p^ := '.';
inc(p);
end;
// Fraction significant digits
if n_digits_req < n_digits_have then
n_digits_have := n_digits_req;
if n_digits_have > 0 then
begin
repeat
inc(digits);
p^ := AnsiChar(digits^ + ord('0'));
inc(p);
dec(n_digits_have);
until n_digits_have = 0;
while p[-1] = '0' do
dec(p); // trim #.###00000 -> #.###
if p[-1] = '.' then
dec(p); // #.0 -> #
end;
// Exponent designator
p^ := 'E';
inc(p);
// Exponent sign (+ is not stored, as in Delphi)
if d_exp < 0 then
begin
p^ := '-';
d_exp := -d_exp;
inc(p);
end;
// Exponent digits
exp := pointer(SmallUInt32Utf8[d_exp]); // 0..999 range is fine
PCardinal(p)^ := PCardinal(exp)^;
inc(p, PStrLen(exp - _STRLEN)^);
// Store length
str[0] := AnsiChar(p - str - 1);
end;
/// set one of special results with proper sign
procedure d2a_return_special(str: PAnsiChar; sign: integer;
const spec: ShortString);
begin
// Compute length
str[0] := spec[0];
if sign <> 0 then
inc(str[0]);
inc(str);
// Sign
if sign <> 0 then
begin
if sign > 0 then
str^ := '+'
else
str^ := '-';
inc(str);
end;
// Special text (3 chars)
PCardinal(str)^ := PCardinal(@spec[1])^;
end;
// Calculates the exp10 of a factor required to bring the binary exponent
// of the original number into selected [ alpha .. gamma ] range:
// result := ceiling[ ( alpha - e ) * log10(2) ]
function d2a_k_comp(e, alpha{, gamma}: integer): integer;
var
dexp: double;
const
D_LOG10_2: double = 0.301029995663981195213738894724493027; // log10(2)
var
x, n: integer;
begin
x := alpha - e;
dexp := x * D_LOG10_2;
// ceil( dexp )
n := trunc(dexp);
if x > 0 then
if dexp <> n then
inc(n); // round-up
result := n;
end;
procedure DoubleToAscii(min_width, frac_digits: integer; const v: double;
str: PAnsiChar);
var
w, D: TDIY_FP;
c_mk: TDIY_FP_Power_of_10;
n, mk, dot_pos, n_digits_need, n_digits_have: integer;
n_digits_req, n_digits_sci: integer;
minus: boolean;
fl, one_maskl: qword;
one_e: integer;
{$ifdef CPU32}
one_mask, f: cardinal; // run a 2nd loop with 32-bit range
{$endif CPU32}
buf: TAsciiDigits;
begin
// Limit parameters
if frac_digits > 216 then
frac_digits := 216; // Delphi compatible
if min_width <= C_NO_MIN_WIDTH then
min_width := -1 // no minimal width
else if min_width < 0 then
min_width := 0; // minimal width is as short as possible
// Format profile: select "n_digits_need" (and "n_digits_exp")
n_digits_req := nDig_mantissa;
// number of digits to be calculated by Grisu
n_digits_need := nDig_mantissa;
if n_digits_req < n_digits_need then
n_digits_need := n_digits_req;
// number of mantissa digits to be printed in exponential notation
if min_width < 0 then
n_digits_sci := n_digits_req
else
begin
n_digits_sci := min_width -1 {sign} -1 {dot} -1 {E} -1 {E-sign} - nDig_exp10;
if n_digits_sci < 2 then
n_digits_sci := 2; // at least 2 digits
if n_digits_sci > n_digits_req then
n_digits_sci := n_digits_req; // at most requested by real_type
end;
// Float -> DIY_FP
d2a_unpack_float(v, minus, w);
// Handle Zero
if (w.e = 0) and
(w.f = 0) then
begin
{$ifdef GRISU1_F2A_ZERONOFRACT}
PCardinal(str)^ := 1 + ord('0') shl 8; // just return '0'
{$else}
if frac_digits >= 0 then
d2a_return_fixed(str, minus, buf, 0, 1, frac_digits)
else
d2a_return_exponential(str, minus, @buf, 0, n_digits_sci, 0);
{$endif GRISU1_F2A_ZERONOFRACT}
exit;
end;
// Handle specials
if w.e = C_EXP2_SPECIAL then
begin
n := 1 - ord(minus) * 2; // default special sign [-1|+1]
if w.f = 0 then
d2a_return_special(str, n, C_STR_INF)
else
begin
// NaN [also pseudo-NaN, pseudo-Inf, non-normal for floatx80]
{$ifdef GRISU1_F2A_NAN_SIGNLESS}
n := 0;
{$endif GRISU1_F2A_NAN_SIGNLESS}
{$ifndef GRISU1_F2A_NO_SNAN}
if (w.f and (C_MANT2_INTEGER shr 1)) = 0 then
return_special(str, n, C_STR_SNAN)
else
{$endif GRISU1_F2A_NO_SNAN}
d2a_return_special(str, n, C_STR_QNAN);
end;
exit;
end;
// Handle denormals
if w.e <> 0 then
begin
// normal
w.f := w.f or C_MANT2_INTEGER;
n := C_DIY_FP_Q - C_FRAC2_BITS - 1;
end
else
begin
// denormal
n := 63 - BSRqword(w.f);
inc(w.e);
end;
// Final normalization
w.f := w.f shl n;
dec(w.e, C_EXP2_BIAS + n + C_FRAC2_BITS);
// 1. Find the normalized "c_mk = f_c * 2^e_c" such that
// "alpha <= e_c + e_w + q <= gamma"
// 2. Define "V = D * 10^k": multiply the input number by "c_mk", do not
// normalize to land into [ alpha .. gamma ]
// 3. Generate digits ( n_digits_need + "round" )
if (C_GRISU_ALPHA <= w.e) and
(w.e <= C_GRISU_GAMMA) then
begin
// no scaling required
D := w;
c_mk.e10 := 0;
end
else
begin
mk := d2a_k_comp(w.e, C_GRISU_ALPHA{, C_GRISU_GAMMA} );
d2a_diy_fp_cached_power10(mk, c_mk);
// Let "D = f_D * 2^e_D := w (*) c_mk"
if c_mk.e10 = 0 then
D := w
else
d2a_diy_fp_multiply(w, c_mk.c, false, D);
end;
// Generate digits: integer part
n_digits_have := d2a_gen_digits_64(@buf, D.f shr (-D.e));
dot_pos := n_digits_have;
// Generate digits: fractional part
{$ifdef CPU32}
f := 0; // "sticky" digit
{$endif CPU32}
if D.e < 0 then
repeat
// MOD by ONE
one_e := D.e;
one_maskl := qword(1) shl (-D.e) - 1;
fl := D.f and one_maskl;
// 64-bit loop (very efficient on x86_64, slower on i386)
while {$ifdef CPU32} (one_e < -29) and {$endif}
(n_digits_have < n_digits_need + 1) and (fl <> 0) do
begin
// f := f * 5;
inc(fl, fl shl 2);
// one := one / 2
one_maskl := one_maskl shr 1;
inc(one_e);
// DIV by one
buf[n_digits_have] := fl shr (-one_e);
// MOD by one
fl := fl and one_maskl;
// next
inc(n_digits_have);
end;
{$ifdef CPU32}
if n_digits_have >= n_digits_need + 1 then
begin
// only "sticky" digit remains
f := ord(fl <> 0);
break;
end;
one_mask := cardinal(one_maskl);
f := cardinal(fl);
// 32-bit loop
while (n_digits_have < n_digits_need + 1) and (f <> 0) do
begin
// f := f * 5;
inc(f, f shl 2);
// one := one / 2
one_mask := one_mask shr 1;
inc(one_e);
// DIV by one
buf[n_digits_have] := f shr (-one_e);
// MOD by one
f := f and one_mask;
// next
inc(n_digits_have);
end;
{$endif CPU32}
until true;
{$ifdef CPU32}
// Append "sticky" digit if any
if (f <> 0) and
(n_digits_have >= n_digits_need + 1) then
begin
// single "<>0" digit is enough
n_digits_have := n_digits_need + 2;
buf[n_digits_need + 1] := 1;
end;
{$endif CPU32}
// Round to n_digits_need using "roundTiesToEven"
if n_digits_have > n_digits_need then
inc(dot_pos, d2a_round_digits(buf, n_digits_have, n_digits_need));
// Generate output
if frac_digits >= 0 then
begin
d2a_return_fixed(str, minus, buf, n_digits_have, dot_pos - c_mk.e10,
frac_digits);
exit;
end;
if n_digits_have > n_digits_sci then
inc(dot_pos, d2a_round_digits(buf, n_digits_have, n_digits_sci
{$ifdef GRISU1_F2A_HALF_ROUNDUP}, false {$endif} ));
d2a_return_exponential(str, minus, @buf, n_digits_have, n_digits_sci,
dot_pos - c_mk.e10 - 1);
end;
function DoubleToShort(S: PShortString; const Value: double): integer;
var
valueabs: double;
begin
valueabs := abs(Value);
if (valueabs > {$ifdef FPC}double{$endif}(DOUBLE_HI)) or
(valueabs < {$ifdef FPC}double{$endif}(DOUBLE_LO)) then
// = str(Value,S) for scientific notation outside of 1E-9<Value<1E9 range
DoubleToAscii(C_NO_MIN_WIDTH, -1, Value, pointer(S))
else
begin
// inlined DoubleToShortNoExp() = str(Value:0:15,S^)
DoubleToAscii(0, DOUBLE_PRECISION, Value, pointer(S));
S^[0] := AnsiChar(FloatStringNoExp(pointer(S), DOUBLE_PRECISION));
end;
result := ord(S^[0]);
end;
function DoubleToShortNoExp(S: PShortString; const Value: double): integer;
begin
DoubleToAscii(0, DOUBLE_PRECISION, Value, pointer(S)); // = str(Value:0:15,S^)
result := FloatStringNoExp(pointer(S), DOUBLE_PRECISION);
S^[0] := AnsiChar(result);
end;
{$else} // use regular Extended version
function DoubleToShort(S: PShortString; const Value: double): integer;
begin
result := ExtendedToShort(S, Value, DOUBLE_PRECISION);
end;
function DoubleToShortNoExp(S: PShortString; const Value: double): integer;
begin
result := ExtendedToShortNoExp(S, Value, DOUBLE_PRECISION);
end;
{$endif DOUBLETOSHORT_USEGRISU}
function DoubleToJson(tmp: PShortString; Value: double;
NoExp: boolean): PShortString;
begin
if Value = 0 then
result := @JSON_NAN[fnNumber]
else
begin
if NoExp then
DoubleToShortNoExp(tmp, Value)
else
DoubleToShort(tmp, Value);
result := FloatToJsonNan(tmp);
end;
end;
function DoubleToStr(Value: Double): RawUtf8;
begin
DoubleToStr(Value, result);
end;
procedure DoubleToStr(Value: Double; var result: RawUtf8);
var
tmp: ShortString;
begin
if Value = 0 then
result := SmallUInt32Utf8[0]
else
FastSetString(result, @tmp[1], DoubleToShort(@tmp, Value));
end;
function FloatStrCopy(s, d: PUtf8Char): PUtf8Char;
var
c: AnsiChar;
begin
while s^=' ' do
inc(s);
c := s^;
if (c='+') or
(c='-') then
begin
inc(s);
d^ := c;
inc(d);
c := s^;
end;
if c = '.' then
begin
PCardinal(d)^ := ord('0')+ord('.')shl 8; // '.5' -> '0.5'
inc(d,2);
inc(s);
c := s^;
end;
if (c >= '0') and
(c <= '9') then
repeat
inc(s);
d^ := c;
inc(d);
c := s^;
if ((c >= '0') and
(c <= '9')) or
(c = '.') then
continue;
if (c <> 'e') and
(c <> 'E') then
break;
inc(s);
d^ := c; // 1.23e120 or 1.23e-45
inc(d);
c := s^;
if c = '-' then
begin
inc(s);
d^ := c;
inc(d);
c := s^;
end;
while (c >= '0') and
(c <= '9') do
begin
inc(s);
d^ := c;
inc(d);
c := s^;
end;
break;
until false;
result := d;
end;
function Char2ToByte(P: PUtf8Char; out Value: cardinal;
ConvertHexToBinTab: PByteArray): boolean;
var
B: PtrUInt;
begin
B := ConvertHexToBinTab[ord(P[0])];
if B <= 9 then
begin
Value := B;
B := ConvertHexToBinTab[ord(P[1])];
if B <= 9 then
begin
Value := Value * 10 + B;
result := false;
exit;
end;
end;
result := true; // error
end;
function Char3ToWord(P: PUtf8Char; out Value: cardinal;
ConvertHexToBinTab: PByteArray): boolean;
var
B: PtrUInt;
begin
B := ConvertHexToBinTab[ord(P[0])];
if B <= 9 then
begin
Value := B;
B := ConvertHexToBinTab[ord(P[1])];
if B <= 9 then
begin
Value := Value * 10 + B;
B := ConvertHexToBinTab[ord(P[2])];
if B <= 9 then
begin
Value := Value * 10 + B;
result := false;
exit;
end;
end;
end;
result := true; // error
end;
function Char4ToWord(P: PUtf8Char; out Value: cardinal;
ConvertHexToBinTab: PByteArray): boolean;
var
B: PtrUInt;
begin
B := ConvertHexToBinTab[ord(P[0])];
if B <= 9 then
begin
Value := B;
B := ConvertHexToBinTab[ord(P[1])];
if B <= 9 then
begin
Value := Value * 10 + B;
B := ConvertHexToBinTab[ord(P[2])];
if B <= 9 then
begin
Value := Value * 10 + B;
B := ConvertHexToBinTab[ord(P[3])];
if B <= 9 then
begin
Value := Value * 10 + B;
result := false;
exit;
end;
end;
end;
end;
result := true; // error
end;
procedure VariantToUtf8(const V: Variant; var result: RawUtf8;
var wasString: boolean);
var
tmp: TVarData;
vt: cardinal;
begin
wasString := false;
vt := TVarData(V).VType;
with TVarData(V) do
case vt of
varEmpty, varNull:
result := NULL_STR_VAR;
varSmallint:
Int32ToUtf8(VSmallInt, result);
varShortInt:
Int32ToUtf8(VShortInt, result);
varWord:
UInt32ToUtf8(VWord, result);
varLongWord:
UInt32ToUtf8(VLongWord, result);
varByte:
result := SmallUInt32Utf8[VByte];
varBoolean:
if VBoolean then
result := SmallUInt32Utf8[1]
else
result := SmallUInt32Utf8[0];
varInteger:
Int32ToUtf8(VInteger, result);
varInt64:
Int64ToUtf8(VInt64, result);
varWord64:
UInt64ToUtf8(VInt64, result);
varSingle:
ExtendedToStr(VSingle, SINGLE_PRECISION, result);
varDouble:
DoubleToStr(VDouble, result);
varCurrency:
Curr64ToStr(VInt64, result);
varDate:
begin
_VariantToUtf8DateTimeToIso8601(VDate, 'T', result, {withms=}false);
wasString := true;
end;
varString:
begin
wasString := true;
{$ifdef HASCODEPAGE}
AnyAnsiToUtf8(RawByteString(VString), result);
{$else}
result := RawUtf8(VString);
{$endif HASCODEPAGE}
end;
{$ifdef HASVARUSTRING}
varUString:
begin
wasString := true;
RawUnicodeToUtf8(VAny, length(UnicodeString(VAny)), result);
end;
{$endif HASVARUSTRING}
varOleStr:
begin
wasString := true;
RawUnicodeToUtf8(VAny, length(WideString(VAny)), result);
end;
varOlePAnsiChar: // = VT_LPSTR
begin
wasString := true;
CurrentAnsiConvert.AnsiBufferToRawUtf8(VString, StrLen(VString), result);
end;
varOlePWideChar: // = VT_LPWSTR
begin
wasString := true;
RawUnicodeToUtf8(VAny, StrLenW(VAny), result);
end;
else
if SetVariantUnRefSimpleValue(V, tmp{%H-}) then
// simple varByRef
VariantToUtf8(Variant(tmp), result, wasString)
else if vt = varVariantByRef then{%H-}
// complex varByRef
VariantToUtf8(PVariant(VPointer)^, result, wasString)
else if vt = varStringByRef then
begin
wasString := true;
{$ifdef HASCODEPAGE}
AnyAnsiToUtf8(PRawByteString(VString)^, result);
{$else}
result := PRawUtf8(VString)^;
{$endif HASCODEPAGE}
end
else if vt = varOleStrByRef then
begin
wasString := true;
RawUnicodeToUtf8(pointer(PWideString(VAny)^),
length(PWideString(VAny)^), result);
end
else
{$ifdef HASVARUSTRING}
if vt = varUStringByRef then
begin
wasString := true;
RawUnicodeToUtf8(pointer(PUnicodeString(VAny)^),
length(PUnicodeString(VAny)^), result);
end
else
{$endif HASVARUSTRING}
// not recognizable vt -> seralize as JSON to handle also custom types
_VariantSaveJson(V, twJsonEscape, result); // = mormot.core.variants.pas
end;
end;
function VariantToUtf8(const V: Variant): RawUtf8;
var
wasString: boolean;
begin
VariantToUtf8(V, result, wasString);
end;
function ToUtf8(const V: Variant): RawUtf8;
var
wasString: boolean;
begin
VariantToUtf8(V, result, wasString);
end;
function ToUtf8(const V: TVarData): RawUtf8; overload;
var
wasString: boolean;
begin
VariantToUtf8(PVariant(@V)^, result, wasString);
end;
function VariantToUtf8(const V: Variant; var Text: RawUtf8): boolean;
begin
VariantToUtf8(V, Text, result);
end;
function VariantToText(const V: Variant; var Text: RawUtf8): boolean;
begin
result := not VarIsEmptyOrNull(V) and
VariantToUtf8(V, Text);
end;
function VariantSaveJson(const Value: variant; Escape: TTextWriterKind): RawUtf8;
begin
_VariantSaveJson(Value, Escape, result);
end;
procedure VariantSaveJson(const Value: variant; Escape: TTextWriterKind;
var result: RawUtf8);
begin
_VariantSaveJson(Value, Escape, result);
end;
procedure __VariantSaveJson(const Value: variant; Escape: TTextWriterKind;
var result: RawUtf8);
begin
raise ESynException.Create('VariantSaveJson() unsupported:' +
' please include mormot.core.variants to your uses clause');
end;
procedure __VariantToUtf8DateTimeToIso8601(DT: TDateTime; FirstChar: AnsiChar;
var result: RawUtf8; WithMS: boolean);
begin
raise ESynException.Create('VariantToUtf8(varDate) unsupported:' +
' please include mormot.core.datetime to your uses clause');
end;
function VariantCompAsText(A, B: PVarData; caseInsensitive: boolean): integer;
var
au, bu: pointer;
wasString: boolean;
begin
au := nil; // no try..finally for local RawUtf8 variables
bu := nil;
VariantToUtf8(PVariant(A)^, RawUtf8(au), wasString);
VariantToUtf8(PVariant(B)^, RawUtf8(bu), wasString);
result := SortDynArrayAnsiStringByCase[caseInsensitive](au, bu);
FastAssignNew(au);
FastAssignNew(bu);
end;
function Int18ToChars3(Value: cardinal): RawUtf8;
begin
FastSetString(result, 3);
PCardinal(result)^ := ((Value shr 12) and $3f) or
((Value shr 6) and $3f) shl 8 or
(Value and $3f) shl 16 + $202020;
end;
procedure Int18ToChars3(Value: cardinal; var result: RawUtf8);
begin
FastSetString(result, 3);
PCardinal(result)^ := ((Value shr 12) and $3f) or
((Value shr 6) and $3f) shl 8 or
(Value and $3f) shl 16 + $202020;
end;
function Chars3ToInt18(P: pointer): cardinal;
begin
result := PCardinal(P)^ - $202020;
result := ((result shr 16) and $3f) or
((result shr 8) and $3f) shl 6 or
(result and $3f) shl 12;
end;
function UInt3DigitsToUtf8(Value: cardinal): RawUtf8;
begin
FastSetString(result, 3);
PWordArray(result)[0] := TwoDigitLookupW[Value div 10];
PByteArray(result)[2] := (Value mod 10) + 48;
end;
function UInt4DigitsToUtf8(Value: cardinal): RawUtf8;
begin
FastSetString(result, 4);
if Value > 9999 then
Value := 9999;
YearToPChar(Value, pointer(result));
end;
function UInt4DigitsToShort(Value: cardinal): TShort4;
begin
result[0] := #4;
if Value > 9999 then
Value := 9999;
YearToPChar(Value, @result[1]);
end;
function UInt3DigitsToShort(Value: cardinal): TShort4;
begin
if Value > 999 then
Value := 999;
YearToPChar(Value, @result[0]);
result[0] := #3; // override first digit
end;
function UInt2DigitsToShort(Value: byte): TShort4;
begin
result[0] := #2;
if Value > 99 then
Value := 99;
PCardinal(@result[1])^ := TwoDigitLookupW[Value];
end;
function UInt2DigitsToShortFast(Value: byte): TShort4;
begin
result[0] := #2;
PCardinal(@result[1])^ := TwoDigitLookupW[Value];
end;
function IPToCardinal(aIP: PUtf8Char; out aValue: cardinal): boolean;
var
i, c: cardinal;
b: array[0..3] of byte;
begin
aValue := 0;
result := false;
if (aIP = nil) or
(IdemPChar(aIP, '127.0.0.1') and
(aIP[9] = #0)) then
exit;
for i := 0 to 3 do
begin
c := GetNextItemCardinal(aIP, '.');
if (c > 255) or
((aIP = nil) and
(i < 3)) then
exit;
b[i] := c;
end;
if PCardinal(@b)^ <> $0100007f then // may be e.g. '127.000.000.001'
begin
aValue := PCardinal(@b)^;
result := true;
end;
end;
function IPToCardinal(const aIP: RawUtf8; out aValue: cardinal): boolean;
begin
result := IPToCardinal(pointer(aIP), aValue);
end;
function IPToCardinal(const aIP: RawUtf8): cardinal;
begin
IPToCardinal(pointer(aIP), result);
end;
{ ************ Text Formatting functions }
function VarRecAsChar(const V: TVarRec): integer;
begin
case V.VType of
vtChar:
result := ord(V.VChar);
vtWideChar:
result := ord(V.VWideChar);
else
result := 0;
end;
end;
function VarRecAs(const aArg: TVarRec; aClass: TClass): pointer;
begin
if (aArg.VType = vtObject) and
(aArg.VObject <> nil) and
aArg.VObject.InheritsFrom(aClass) then
result := aArg.VObject
else
result := nil;
end;
function VarRecToInt64(const V: TVarRec; out value: Int64): boolean;
begin
case V.VType of
vtInteger:
value := V.VInteger;
{$ifdef FPC} vtQWord, {$endif}
vtInt64:
value := V.VInt64^;
vtBoolean:
if V.VBoolean then
value := 1
else
value := 0; // normalize
vtVariant:
value := V.VVariant^;
else
begin
result := false;
exit;
end;
end;
result := true;
end;
function VarRecToDouble(const V: TVarRec; out value: double): boolean;
begin
case V.VType of
vtInteger:
value := V.VInteger;
vtInt64:
value := V.VInt64^;
{$ifdef FPC}
vtQWord:
value := V.VQWord^;
{$endif FPC}
vtBoolean:
if V.VBoolean then
value := 1
else
value := 0; // normalize
vtExtended:
value := V.VExtended^;
vtCurrency:
value := V.VCurrency^;
vtVariant:
value := V.VVariant^;
else
begin
result := false;
exit;
end;
end;
result := true;
end;
procedure BufToTempUtf8(Buf: PUtf8Char; var Res: TTempUtf8);
begin // Res.Len has been set by caller
if Res.Len > SizeOf(Res.Temp) then
begin
FastSetString(RawUtf8(Res.TempRawUtf8), Buf, Res.Len); // new RawUtf8
Res.Text := Res.TempRawUtf8;
end
else
begin
{$ifdef CPUX86}
MoveFast(Buf^, Res.Temp, Res.Len); // avoid slow "rep movsd" on FPC i386
{$else}
THash192(Res.Temp) := PHash192(Buf)^; // faster than MoveByOne/MoveFast
{$endif CPUX86}
Res.Text := @Res.Temp; // no RawUtf8 memory allocation
end;
end;
procedure DoubleToTempUtf8(V: double; var Res: TTempUtf8);
var
tmp: shortstring;
begin
Res.Len := DoubleToShort(@tmp, V);
BufToTempUtf8(@tmp[1], Res);
end;
procedure WideToTempUtf8(WideChar: PWideChar; WideCharCount: integer;
var Res: TTempUtf8);
var
tmp: TSynTempBuffer;
begin
if (WideChar = nil) or
(WideCharCount = 0) then
begin
Res.Text := nil;
Res.Len := 0;
end
else
begin
tmp.Init(WideCharCount * 3);
Res.Len := RawUnicodeToUtf8(tmp.buf, tmp.len + 1,
WideChar, WideCharCount, [ccfNoTrailingZero]);
BufToTempUtf8(tmp.buf, Res);
tmp.Done;
end;
end;
procedure PtrIntToTempUtf8(V: PtrInt; var Res: TTempUtf8);
{$ifdef HASINLINE} inline; {$endif}
begin
{$ifndef ASMINTEL} // our StrInt32 asm has less CPU cache pollution
if PtrUInt(V) <= high(SmallUInt32Utf8) then
begin
Res.Text := pointer(SmallUInt32Utf8[V]);
Res.Len := PStrLen(Res.Text - _STRLEN)^;
end
else
{$endif ASMINTEL}
begin
Res.Text := PUtf8Char(StrInt32(@Res.Temp[23], V));
Res.Len := @Res.Temp[23] - Res.Text;
end;
end;
procedure Int64ToTempUtf8(V: PInt64; var Res: TTempUtf8);
{$ifdef HASINLINE} inline; {$endif}
begin
{$ifdef CPU64}
PtrIntToTempUtf8(V^, Res);
{$else}
if (PCardinalArray(V)^[0] <= high(SmallUInt32Utf8)) and
(PCardinalArray(V)^[1] = 0) then
begin
Res.Text := pointer(SmallUInt32Utf8[PPtrInt(V)^]);
Res.Len := PStrLen(Res.Text - _STRLEN)^;
end
else
begin
Res.Text := PUtf8Char(StrInt64(@Res.Temp[23], V^));
Res.Len := @Res.Temp[23] - Res.Text;
end;
{$endif CPU64}
end;
procedure QWordToTempUtf8(V: PQWord; var Res: TTempUtf8);
{$ifdef HASINLINE} inline; {$endif}
begin
{$ifndef ASMINTEL} // our StrUInt64 asm has less CPU cache pollution
if V^ <= high(SmallUInt32Utf8) then
begin
Res.Text := pointer(SmallUInt32Utf8[PPtrInt(V)^]);
Res.Len := PStrLen(Res.Text - _STRLEN)^;
end
else
{$endif ASMINTEL}
begin
Res.Text := PUtf8Char(StrUInt64(@Res.Temp[23], V^));
Res.Len := @Res.Temp[23] - Res.Text;
end;
end;
procedure VariantToTempUtf8(const V: variant; var Res: TTempUtf8;
var wasString: boolean);
var
tmp: TVarData;
vt: cardinal;
begin
wasString := false;
Res.TempRawUtf8 := nil; // no allocation by default - and avoid GPF
vt := TVarData(V).VType;
with TVarData(V) do
case vt of
varEmpty,
varNull:
begin
Res.Text := pointer(NULL_STR_VAR);
Res.Len := 4;
end;
varSmallint:
PtrIntToTempUtf8(VSmallInt, Res);
varShortInt:
PtrIntToTempUtf8(VShortInt, Res);
varWord:
PtrIntToTempUtf8(VWord, Res);
varLongWord:
{$ifdef CPU32}
if VLongWord > high(SmallUInt32Utf8) then
begin
Res.Text := PUtf8Char(StrUInt32(@Res.Temp[23], VLongWord));
Res.Len := @Res.Temp[23] - Res.Text;
end
else
{$endif CPU32}
PtrIntToTempUtf8(VLongWord, Res);
varByte:
PtrIntToTempUtf8(VByte, Res);
varBoolean:
if VBoolean then
begin
Res.Text := @BOOL_STR[true][1];
Res.Len := 4;
end
else
begin
Res.Text := @BOOL_STR[false][1];
Res.Len := 5;
end;
varInteger:
PtrIntToTempUtf8(VInteger, Res);
varInt64:
Int64ToTempUtf8(@VInt64, Res);
varWord64:
QWordToTempUtf8(@VInt64, Res);
varSingle:
DoubleToTempUtf8(VSingle, Res);
varDouble:
DoubleToTempUtf8(VDouble, Res);
varCurrency:
begin
Res.Len := Curr64ToPChar(VInt64, @Res.Temp);
Res.Text := @Res.Temp;
end;
varDate:
begin
wasString := true;
_VariantToUtf8DateTimeToIso8601(VDate, 'T', RawUtf8(Res.TempRawUtf8), false);
Res.Text := pointer(Res.TempRawUtf8);
Res.Len := length(RawUtf8(Res.TempRawUtf8));
end;
varString:
begin
wasString := true;
Res.Text := VString; // assume RawUtf8
Res.Len := length(RawUtf8(VString));
end;
{$ifdef HASVARUSTRING}
varUString:
begin
wasString := true;
WideToTempUtf8(VAny, length(UnicodeString(VAny)), Res);
end;
{$endif HASVARUSTRING}
varOleStr:
begin
wasString := true;
WideToTempUtf8(VAny, length(WideString(VAny)), Res);
end;
else
if SetVariantUnRefSimpleValue(V, tmp{%H-}) then
// simple varByRef
VariantToTempUtf8(Variant(tmp), Res, wasString)
else if vt = varVariantByRef then{%H-}
// complex varByRef
VariantToTempUtf8(PVariant(VPointer)^, Res, wasString)
else if vt = varStringByRef then
begin
wasString := true;
Res.Text := PPointer(VString)^; // assume RawUtf8
Res.Len := length(PRawUtf8(VString)^);
end
else if vt = varOleStrByRef then
begin
wasString := true;
WideToTempUtf8(PPointer(VAny)^, length(PWideString(VAny)^), Res);
end
else
{$ifdef HASVARUSTRING}
if vt = varUStringByRef then
begin
wasString := true;
WideToTempUtf8(PPointer(VAny)^, length(PUnicodeString(VAny)^), Res);
end
else
{$endif HASVARUSTRING}
begin
// not recognizable vt -> seralize as JSON to handle also custom types
wasString := true;
_VariantSaveJson(V, twJsonEscape, RawUtf8(Res.TempRawUtf8));
Res.Text := pointer(Res.TempRawUtf8);
Res.Len := length(RawUtf8(Res.TempRawUtf8));
end;
end;
end;
function VarRecToTempUtf8(const V: TVarRec; var Res: TTempUtf8;
wasString: PBoolean): PtrInt;
var
isString: boolean;
begin
isString := true;
Res.TempRawUtf8 := nil; // no allocation by default - and avoid GPF
case V.VType of
vtString:
if V.VString = nil then
Res.Len := 0
else
begin
Res.Text := @V.VString^[1];
Res.Len := ord(V.VString^[0]);
end;
vtAnsiString:
begin
// expect UTF-8 content
Res.Text := pointer(V.VAnsiString);
Res.Len := length(RawUtf8(V.VAnsiString));
end;
{$ifdef HASVARUSTRING}
vtUnicodeString:
WideToTempUtf8(V.VPWideChar, length(UnicodeString(V.VUnicodeString)), Res);
{$endif HASVARUSTRING}
vtWideString:
WideToTempUtf8(V.VPWideChar, length(WideString(V.VWideString)), Res);
vtPChar:
begin
// expect UTF-8 content
Res.Text := V.VPointer;
Res.Len := mormot.core.base.StrLen(V.VPointer);
end;
vtChar:
begin
Res.Temp[0] := V.VChar; // V may be on transient stack (alf: FPC)
Res.Text := @Res.Temp;
Res.Len := 1;
end;
vtPWideChar:
WideToTempUtf8(V.VPWideChar, StrLenW(V.VPWideChar), Res);
vtWideChar:
WideToTempUtf8(@V.VWideChar, 1, Res);
vtBoolean:
begin
isString := false;
if V.VBoolean then // normalize
Res.Text := pointer(SmallUInt32Utf8[1])
else
Res.Text := pointer(SmallUInt32Utf8[0]);
Res.Len := 1;
end;
vtInteger:
begin
isString := false;
PtrIntToTempUtf8(V.VInteger, Res);
end;
vtInt64:
begin
isString := false;
Int64ToTempUtf8(V.VInt64, Res);
end;
{$ifdef FPC}
vtQWord:
begin
isString := false;
QwordToTempUtf8(V.VQWord, Res);
end;
{$endif FPC}
vtCurrency:
begin
isString := false;
Res.Text := @Res.Temp;
Res.Len := Curr64ToPChar(V.VInt64^, Res.Temp);
end;
vtExtended:
begin
isString := false;
DoubleToTempUtf8(V.VExtended^, Res);
end;
vtPointer, vtInterface:
begin
Res.Text := @Res.Temp;
Res.Len := DisplayMinChars(@V.VPointer, SizeOf(pointer)) * 2;
BinToHexDisplayLower(@V.VPointer, @Res.Temp, Res.Len shr 1);
end;
vtClass:
if V.VClass = nil then
Res.Len := 0
else
begin
Res.Text := PPUtf8Char(PtrInt(PtrUInt(V.VClass)) + vmtClassName)^ + 1;
Res.Len := ord(Res.Text[-1]);
end;
vtObject:
if V.VObject = nil then
Res.Len := 0
else
begin
Res.Text := PPUtf8Char(PPtrInt(V.VObject)^ + vmtClassName)^ + 1;
Res.Len := ord(Res.Text[-1]);
end;
vtVariant:
VariantToTempUtf8(V.VVariant^, Res, isString);
else
Res.Len := 0;
end;
result := Res.Len;
if wasString <> nil then
wasString^ := isString;
end;
procedure VarRecToUtf8(const V: TVarRec; var result: RawUtf8; wasString: PBoolean);
var
isString: boolean;
label
none;
begin
isString := false;
with V do
case V.VType of
vtString:
begin
isString := true;
if VString = nil then
goto none;
FastSetString(result, @VString^[1], ord(VString^[0]));
end;
vtAnsiString:
begin
isString := true;
result := RawUtf8(VAnsiString); // expect UTF-8 content
end;
{$ifdef HASVARUSTRING}
vtUnicodeString:
begin
isString := true;
RawUnicodeToUtf8(VUnicodeString,
length(UnicodeString(VUnicodeString)), result);
end;
{$endif HASVARUSTRING}
vtWideString:
begin
isString := true;
RawUnicodeToUtf8(VWideString, length(WideString(VWideString)), result);
end;
vtPChar:
begin
isString := true;
FastSetString(result, VPChar, mormot.core.base.StrLen(VPChar));
end;
vtChar:
begin
isString := true;
FastSetString(result, PAnsiChar(@VChar), 1);
end;
vtPWideChar:
begin
isString := true;
RawUnicodeToUtf8(VPWideChar, StrLenW(VPWideChar), result);
end;
vtWideChar:
begin
isString := true;
RawUnicodeToUtf8(@VWideChar, 1, result);
end;
vtBoolean:
if VBoolean then // normalize
result := SmallUInt32Utf8[1]
else
result := SmallUInt32Utf8[0];
vtInteger:
Int32ToUtf8(VInteger, result);
vtInt64:
Int64ToUtf8(VInt64^, result);
{$ifdef FPC}
vtQWord:
UInt64ToUtf8(VQWord^, result);
{$endif FPC}
vtCurrency:
Curr64ToStr(VInt64^, result);
vtExtended:
DoubleToStr(VExtended^,result);
vtPointer:
begin
isString := true;
PointerToHex(VPointer, result);
end;
vtClass:
begin
isString := true;
if VClass <> nil then
ClassToText(VClass, result)
else
none: result := '';
end;
vtObject:
if VObject <> nil then
ClassToText(PClass(VObject)^, result)
else
goto none;
vtInterface:
{$ifdef HASINTERFACEASTOBJECT}
if VInterface <> nil then
ClassToText((IInterface(VInterface) as TObject).ClassType, result)
else
goto none;
{$else}
PointerToHex(VInterface,result);
{$endif HASINTERFACEASTOBJECT}
vtVariant:
VariantToUtf8(VVariant^, result, isString);
else
goto none;
end;
if wasString <> nil then
wasString^ := isString;
end;
function VarRecToUtf8IsString(const V: TVarRec; var value: RawUtf8): boolean;
begin
VarRecToUtf8(V, value, @result);
end;
procedure VarRecToInlineValue(const V: TVarRec; var result: RawUtf8);
var
wasString: boolean;
tmp: RawUtf8;
begin
VarRecToUtf8(V, tmp, @wasString);
if wasString then
QuotedStr(tmp, '"', result)
else
result := tmp;
end;
function FormatUtf8(const Format: RawUtf8; const Args: array of const): RawUtf8;
begin
FormatUtf8(Format, Args, result);
end;
function FormatVariant(const Format: RawUtf8; const Args: array of const): variant;
begin
ClearVariantForString(result);
FormatUtf8(Format, Args, RawUtf8(TVarData(result).VString));
end;
type
// 3KB info on stack - only supported token is %, with any const arguments
{$ifdef USERECORDWITHMETHODS}
TFormatUtf8 = record
{$else}
TFormatUtf8 = object
{$endif USERECORDWITHMETHODS}
public
last: PTempUtf8;
L: PtrInt;
blocks: array[0..63] of TTempUtf8; // to avoid most heap allocations
procedure TooManyArgs;
procedure Parse(const Format: RawUtf8; Arg: PVarRec; ArgCount: PtrInt);
procedure Add(const SomeText: RawUtf8);
procedure DoDelim(Arg: PVarRec; ArgCount: integer; EndWithDelim: boolean;
Delim: AnsiChar);
procedure DoAdd(Arg: PVarRec; ArgCount: integer);
{$ifdef HASINLINE} inline; {$endif}
procedure DoAppendLine(var Text: RawUtf8; Arg: PVarRec; ArgCount: PtrInt;
const Separator: shortstring);
procedure DoPrepend(var Text: RawUtf8; Arg: PVarRec;
ArgCount, CodePage: PtrInt);
procedure Write(Dest: PUtf8Char);
procedure WriteString(var result: string);
function WriteMax(Dest: PUtf8Char; Max: PtrUInt): PUtf8Char;
end;
procedure TFormatUtf8.TooManyArgs;
begin
raise ESynException.Create('TFormatUtf8: too many arguments');
end;
procedure TFormatUtf8.Parse(const Format: RawUtf8; Arg: PVarRec; ArgCount: PtrInt);
var
F, FDeb: PUtf8Char;
c: PTempUtf8;
begin
if ArgCount >= length(blocks) div 2 then
TooManyArgs;
L := 0;
c := @blocks;
F := pointer(Format);
repeat
if F^ = #0 then
break
else if F^ <> '%' then
begin
FDeb := F;
repeat
inc(F);
until (F^ = '%') or
(F^ = #0);
c^.Text := FDeb;
c^.Len := F - FDeb;
inc(L, c^.Len);
c^.TempRawUtf8 := nil;
inc(c);
if F^ = #0 then
break;
end;
inc(F); // jump '%'
if ArgCount <> 0 then
begin
inc(L, VarRecToTempUtf8(Arg^, c^));
if c^.Len > 0 then
inc(c);
inc(Arg);
dec(ArgCount);
if F^ = #0 then
break;
end
else // no more available Args -> add all remaining text
if F^ = #0 then
break
else
begin
c^.Text := F;
c^.Len := length(Format) - (F - pointer(Format));
inc(L, c^.Len);
c^.TempRawUtf8 := nil;
inc(c);
break;
end;
until false;
last := c;
end;
procedure TFormatUtf8.DoDelim(Arg: PVarRec; ArgCount: integer;
EndWithDelim: boolean; Delim: AnsiChar);
var
c: PTempUtf8;
begin
L := 0;
if ArgCount > 0 then
if ArgCount >= length(blocks) div 2 then
TooManyArgs
else
begin
c := @blocks;
repeat
inc(L, VarRecToTempUtf8(Arg^, c^));
inc(Arg);
if (EndWithDelim and
(ArgCount = 1)) or
((ArgCount <> 1) and
(c^.Len <> 0) and
(c^.Text[c^.Len - 1] <> Delim)) then
begin
inc(c);
c^.Len := 1;
c^.Text := @c^.Temp;
c^.Temp[0] := Delim;
c^.TempRawUtf8 := nil;
inc(L);
end;
inc(c);
dec(ArgCount);
until ArgCount = 0;
last := c;
end;
end;
procedure TFormatUtf8.Add(const SomeText: RawUtf8);
begin
if PtrUInt(last) > PtrUInt(@blocks[high(blocks)]) then
TooManyArgs;
with last^ do
begin
Len := length(SomeText);
inc(L, Len);
Text := pointer(SomeText);
TempRawUtf8 := nil;
end;
inc(last);
end;
procedure TFormatUtf8.DoAdd(Arg: PVarRec; ArgCount: integer);
begin
L := 0;
if ArgCount <= 0 then
exit
else if ArgCount > length(blocks) then
TooManyArgs;
last := @blocks;
repeat
inc(L, VarRecToTempUtf8(Arg^, last^));
inc(Arg);
inc(last);
dec(ArgCount)
until ArgCount = 0;
end;
procedure TFormatUtf8.DoAppendLine(var Text: RawUtf8;
Arg: PVarRec; ArgCount: PtrInt; const Separator: shortstring);
var
c: PTempUtf8;
begin
if ArgCount <= 0 then
exit
else if ArgCount >= length(blocks) then
TooManyArgs;
L := length(Text);
c := @blocks;
if (Text <> '') and
(Separator[0] <> #0) then
begin
c^.Len := ord(Separator[0]);
inc(L, c^.Len);
c^.Text := @Separator[1];
c^.TempRawUtf8 := nil;
inc(c);
end;
repeat
inc(L, VarRecToTempUtf8(Arg^, c^));
inc(Arg);
inc(c);
dec(ArgCount)
until ArgCount = 0;
last := c;
ArgCount := length(Text);
SetLength(Text, L); // realloc in-place and append the new text
Write(PUtf8Char(@PByteArray(Text)[ArgCount]));
end;
procedure TFormatUtf8.DoPrepend(var Text: RawUtf8; Arg: PVarRec;
ArgCount, CodePage: PtrInt);
var
c: PTempUtf8;
new: PUtf8Char;
begin
if ArgCount <= 0 then
exit;
L := length(Text);
c := @blocks;
repeat
inc(L, VarRecToTempUtf8(Arg^, c^));
inc(Arg);
inc(c);
dec(ArgCount)
until ArgCount = 0;
last := c;
ArgCount := length(Text);
new := pointer(FastNewString(L, CodePage));
MoveFast(pointer(Text)^, new[L - ArgCount], ArgCount);
FastAssignNew(Text, new);
Write(new);
end;
procedure TFormatUtf8.Write(Dest: PUtf8Char);
var
d: PTempUtf8;
begin
if L = 0 then
exit;
d := @blocks;
repeat
MoveFast(d^.Text^, Dest^, d^.Len); // no MoveByOne() - may be huge result
inc(Dest, d^.Len);
if d^.TempRawUtf8 <> nil then
{$ifdef FPC}
FastAssignNew(d^.TempRawUtf8);
{$else}
RawUtf8(d^.TempRawUtf8) := '';
{$endif FPC}
inc(d);
until d = last;
end;
function TFormatUtf8.WriteMax(Dest: PUtf8Char; Max: PtrUInt): PUtf8Char;
var
d: PTempUtf8;
begin
if (Max > 0) and
(L <> 0) and
(Dest <> nil) then
begin
inc(Max, PtrUInt(Dest));
d := @blocks;
repeat
if PtrUInt(Dest) + PtrUInt(d^.Len) > Max then
begin
// avoid buffer overflow
MoveFast(d^.Text^, Dest^, Max - PtrUInt(Dest));
repeat
if d^.TempRawUtf8 <> nil then
{$ifdef FPC}
FastAssignNew(d^.TempRawUtf8); // release temp RawUtf8
{$else}
RawUtf8(d^.TempRawUtf8) := '';
{$endif FPC}
inc(d);
until d = last; // avoid memory leak
result := PUtf8Char(Max);
exit;
end;
MoveFast(d^.Text^, Dest^, d^.Len);
inc(Dest, d^.Len);
if d^.TempRawUtf8 <> nil then
{$ifdef FPC}
FastAssignNew(d^.TempRawUtf8);
{$else}
RawUtf8(d^.TempRawUtf8) := '';
{$endif FPC}
inc(d);
until d = last;
end;
result := Dest;
end;
procedure TFormatUtf8.WriteString(var result: string);
var
temp: TSynTempBuffer; // will avoid most memory allocations
begin
result := '';
if L = 0 then
exit;
{$ifndef UNICODE}
if Unicode_CodePage = CP_UTF8 then // e.g. on POSIX or Windows + Lazarus
begin
FastSetString(RawUtf8(result), L);
Write(pointer(result)); // here string=UTF8String=RawUtf8
exit;
end;
{$endif UNICODE}
temp.Init(L);
Write(temp.buf);
Utf8DecodeToString(temp.buf, L, result);
temp.Done;
end;
procedure FormatUtf8(const Format: RawUtf8; const Args: array of const;
out result: RawUtf8);
var
f: TFormatUtf8;
begin
if (Format = '') or
(high(Args) < 0) then // no formatting needed
result := Format
else if PWord(Format)^ = ord('%') then // optimize raw conversion
VarRecToUtf8(Args[0], result)
else
begin
f.Parse(Format, @Args[0], length(Args));
FastSetString(result, f.L);
f.Write(pointer(result));
end;
end;
procedure FormatShort(const Format: RawUtf8; const Args: array of const;
var result: ShortString);
var
f: TFormatUtf8;
begin
if (Format = '') or
(high(Args) < 0) then // no formatting needed
SetString(result, PAnsiChar(pointer(Format)), length(Format))
else
begin
f.Parse(Format, @Args[0], length(Args));
result[0] := AnsiChar(f.WriteMax(@result[1], 255) - @result[1]);
end;
end;
function FormatBuffer(const Format: RawUtf8; const Args: array of const;
Dest: pointer; DestLen: PtrInt): PtrInt;
var
f: TFormatUtf8;
begin
if (Dest = nil) or
(DestLen <= 0) then
begin
result := 0;
exit; // avoid buffer overflow
end;
f.Parse(Format, @Args[0], length(Args));
result := PtrUInt(f.WriteMax(Dest, DestLen)) - PtrUInt(Dest);
end;
function FormatToShort(const Format: RawUtf8;
const Args: array of const): ShortString;
var
f: TFormatUtf8;
begin
f.Parse(Format, @Args[0], length(Args));
result[0] := AnsiChar(f.WriteMax(@result[1], 255) - @result[1]);
end;
procedure FormatShort16(const Format: RawUtf8; const Args: array of const;
var result: TShort16);
var
f: TFormatUtf8;
begin
if (Format = '') or
(high(Args) < 0) then // no formatting needed
SetString(result, PAnsiChar(pointer(Format)), length(Format))
else
begin
f.Parse(Format, @Args[0], length(Args));
result[0] := AnsiChar(f.WriteMax(@result[1], 16) - @result[1]);
end;
end;
procedure FormatString(const Format: RawUtf8; const Args: array of const;
out result: string);
var
f: TFormatUtf8;
begin
if (Format = '') or
(high(Args) < 0) then
// no formatting needed
Utf8ToStringVar(Format, result)
else
begin
f.Parse(Format, @Args[0], length(Args));
f.WriteString(result);
end;
end;
function FormatString(const Format: RawUtf8; const Args: array of const): string;
begin
FormatString(Format, Args, result);
end;
procedure AppendLine(var Text: RawUtf8; const Args: array of const;
const Separator: shortstring);
var
f: TFormatUtf8;
begin
{%H-}f.DoAppendLine(Text, @Args[0], length(Args), Separator);
end;
procedure Append(var Text: RawUtf8; const Args: array of const);
var
f: TFormatUtf8;
begin
{%H-}f.DoAppendLine(Text, @Args[0], length(Args), '');
end;
procedure Append(var Text: RawByteString; const Args: array of const);
var
f: TFormatUtf8;
begin
{%H-}f.DoAppendLine(RawUtf8(Text), @Args[0], length(Args), '');
if Text <> '' then
FakeCodePage(Text, CP_RAWBYTESTRING);
end;
procedure Append(var Text: RawUtf8; const Added: RawByteString);
begin
if Added <> '' then
Append(Text, pointer(Added), PStrLen(PtrUInt(Added) - _STRLEN)^);
end;
procedure Append(var Text: RawUtf8; const Added1, Added2: RawByteString);
var
l, a1, a2: PtrInt;
begin
l := length(Text);
a1 := length(Added1);
a2 := length(Added2);
SetLength(Text, l + a1 + a2);
MoveFast(pointer(Added1)^, PByteArray(Text)[l], a1);
MoveFast(pointer(Added2)^, PByteArray(Text)[l + a1], a2);
end;
procedure Append(var Text: RawUtf8; Added: AnsiChar);
begin
Append(Text, @Added, 1);
end;
procedure Append(var Text: RawUtf8; Added: pointer; AddedLen: PtrInt);
var
t: PtrInt;
begin
if (Added = nil) or (AddedLen <= 0) then
exit;
t := length(Text);
SetLength(Text, t + AddedLen);
MoveFast(pointer(Added)^, PByteArray(Text)[t], AddedLen);
end;
procedure Append(var Text: RawByteString; const Added: RawByteString);
begin
if Added <> '' then
Append(Text, pointer(Added), PStrLen(PtrUInt(Added) - _STRLEN)^);
end;
procedure Append(var Text: RawByteString; Added: pointer; AddedLen: PtrInt);
var
t: PtrInt;
begin
if (Added = nil) or
(AddedLen <= 0) then
exit;
t := length(Text);
SetLength(Text, t + AddedLen);
MoveFast(Added^, PByteArray(Text)^[t], AddedLen);
if Text <> '' then
FakeCodePage(Text, CP_RAWBYTESTRING);
end;
procedure Prepend(var Text: RawUtf8; const Args: array of const);
var
f: TFormatUtf8;
begin
{%H-}f.DoPrepend(Text, @Args[0], length(Args), CP_UTF8);
end;
procedure Prepend(var Text: RawByteString; const Added: RawByteString);
var
t, a: PtrInt;
new: PAnsiChar;
begin
t := length(Text);
a := length(Added);
if a <> 0 then
if t = 0 then
Text := Added
else
begin
new := FastNewString(t + a, CP_RAWBYTESTRING);
MoveFast(PByteArray(Text)[0], new[a], t);
MoveFast(PByteArray(Added)[0], new[0], a);
FastAssignNew(Text, new);
end;
end;
procedure Prepend(var Text: RawByteString; Added: AnsiChar);
var
t: PtrInt;
begin
t := length(Text);
SetLength(Text, t + 1); // is likely to avoid any reallocmem
MoveFast(PByteArray(Text)[0], PByteArray(Text)[1], t);
PByteArray(Text)[0] := ord(Added);
end;
procedure Prepend(var Text: RawByteString; const Args: array of const);
var
f: TFormatUtf8;
begin
{%H-}f.DoPrepend(RawUtf8(Text), @Args[0], length(Args), CP_RAWBYTESTRING);
end;
function Make(const Args: array of const): RawUtf8;
var
f: TFormatUtf8;
begin
{%H-}f.DoAdd(@Args[0], length(Args));
FastSetString(result, f.L);
f.Write(pointer(result));
end;
procedure Make(const Args: array of const; var Result: RawUtf8);
var
f: TFormatUtf8;
begin
{%H-}f.DoAdd(@Args[0], length(Args));
FastSetString(result, f.L);
f.Write(pointer(result));
end;
function MakeString(const Args: array of const): string;
var
f: TFormatUtf8;
begin
{%H-}f.DoAdd(@Args[0], length(Args));
f.WriteString(result);
end;
function MakePath(const Part: array of const; EndWithDelim: boolean;
Delim: AnsiChar): TFileName;
var
f: TFormatUtf8;
begin
{%H-}f.DoDelim(@Part[0], length(Part), EndWithDelim, Delim);
f.WriteString(string(result));
end;
function MakeFileName(const Part: array of const; LastIsExt: boolean): TFileName;
var
f: TFormatUtf8;
ext: RawUtf8;
hipart: integer;
begin
hipart := High(Part);
if LastIsExt then
if (hipart > 0) and
VarRecToUtf8IsString(Part[hipart], ext) then
dec(hipart)
else
LastIsExt := false;
f.DoDelim(@Part[0], hipart + 1, false, PathDelim);
if LastIsExt and
(ext <> '') then
begin
if ext[1] <> '.' then
f.Add('.');
f.Add(ext);
end;
f.WriteString(string(result));
end;
function MakeCsv(const Value: array of const; EndWithComma: boolean;
Comma: AnsiChar): RawUtf8;
var
f: TFormatUtf8;
begin
f.DoDelim(@Value[0], length(Value), EndWithComma, Comma);
FastSetString(result, f.L);
f.Write(pointer(result));
end;
function StringToConsole(const S: string): RawByteString;
begin
result := Utf8ToConsole(StringToUtf8(S));
end;
procedure ConsoleWrite(const Fmt: RawUtf8; const Args: array of const;
Color: TConsoleColor; NoLineFeed: boolean);
var
tmp: RawUtf8;
begin
FormatUtf8(Fmt, Args, tmp);
ConsoleWrite(tmp, Color, NoLineFeed);
end;
procedure ConsoleWrite(const Args: array of const;
Color: TConsoleColor; NoLineFeed: boolean);
var
tmp: RawUtf8;
begin
Append(tmp, Args);
ConsoleWrite(tmp, Color, NoLineFeed);
end;
procedure ConsoleShowFatalException(E: Exception; WaitForEnterKey: boolean);
begin
ConsoleWrite(#13#10'Fatal exception ', ccLightRed, true);
ConsoleWrite('%', [E.ClassType], ccWhite, true);
ConsoleWrite(' raised with message ', ccLightRed);
ConsoleWrite(' %', [E.Message], ccLightMagenta);
TextColor(ccLightGray);
if WaitForEnterKey then
begin
ConsoleWrite(#13#10'Program will now abort');
{$ifndef OSPOSIX}
ConsoleWrite('Press [Enter] to quit');
ConsoleWaitForEnterKey;
{$endif OSPOSIX}
end;
end;
{ ************ Resource and Time Functions }
procedure KB(bytes: Int64; out result: TShort16; nospace: boolean);
type
TUnits = (kb, mb, gb, tb, pb, eb, b);
const
TXT: array[{nospace:}boolean, TUnits] of RawUtf8 = (
(' KB', ' MB', ' GB', ' TB', ' PB', ' EB', '% B'),
( 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', '%B'));
var
hi, rem: cardinal;
u: TUnits;
begin
if bytes < 1 shl 10 - (1 shl 10) div 10 then
begin
FormatShort16(TXT[nospace, b], [integer(bytes)], result);
exit;
end;
if bytes < 1 shl 20 - (1 shl 20) div 10 then
begin
u := kb;
rem := bytes;
hi := bytes shr 10;
end
else if bytes < 1 shl 30 - (1 shl 30) div 10 then
begin
u := mb;
rem := bytes shr 10;
hi := bytes shr 20;
end
else if bytes < Int64(1) shl 40 - (Int64(1) shl 40) div 10 then
begin
u := gb;
rem := bytes shr 20;
hi := bytes shr 30;
end
else if bytes < Int64(1) shl 50 - (Int64(1) shl 50) div 10 then
begin
u := tb;
rem := bytes shr 30;
hi := bytes shr 40;
end
else if bytes < Int64(1) shl 60 - (Int64(1) shl 60) div 10 then
begin
u := pb;
rem := bytes shr 40;
hi := bytes shr 50;
end
else
begin
u := eb;
rem := bytes shr 50;
hi := bytes shr 60;
end;
rem := rem and 1023;
if rem <> 0 then
rem := rem div 102;
if rem = 10 then
begin
rem := 0;
inc(hi); // round up as expected by (most) human beings
end;
if rem <> 0 then
FormatShort16('%.%%', [hi, rem, TXT[nospace, u]], result)
else
FormatShort16('%%', [hi, TXT[nospace, u]], result);
end;
function KB(bytes: Int64): TShort16;
begin
KB(bytes, result, {nospace=}false);
end;
function KBNoSpace(bytes: Int64): TShort16;
begin
KB(bytes, result, {nospace=}true);
end;
function KB(bytes: Int64; nospace: boolean): TShort16;
begin
KB(bytes, result, nospace);
end;
function KB(const buffer: RawByteString): TShort16;
begin
KB(length(buffer), result, {nospace=}false);
end;
procedure KBU(bytes: Int64; var result: RawUtf8);
var
tmp: TShort16;
begin
KB(bytes, tmp, {nospace=}false);
FastSetString(result, @tmp[1], ord(tmp[0]));
end;
procedure K(value: Int64; out result: TShort16);
begin
KB(Value, result, {nospace=}true);
if result[0] <> #0 then
dec(result[0]); // just trim last 'B' ;)
end;
function K(value: Int64): TShort16;
begin
K(Value, result);
end;
function IntToThousandString(Value: integer;
const ThousandSep: TShort4): ShortString;
var
i, L, Len: cardinal;
begin
str(Value, result);
L := length(result);
Len := L + 1;
if Value < 0 then
// ignore '-' sign
dec(L, 2)
else
dec(L);
for i := 1 to L div 3 do
insert(ThousandSep, result, Len - i * 3);
end;
function SecToString(S: QWord): TShort16;
begin
MicroSecToString(S * 1000000, result);
end;
function MilliSecToString(MS: QWord): TShort16;
begin
MicroSecToString(MS * 1000, result);
end;
function MicroSecToString(Micro: QWord): TShort16;
begin
MicroSecToString(Micro, result);
end;
function MicroSecFrom(Start: QWord): TShort16;
var
stop: Int64;
begin
QueryPerformanceMicroSeconds(stop);
MicroSecToString(stop - Int64(Start), result);
end;
procedure By100ToTwoDigitString(value: cardinal; const valueunit: ShortString;
var result: TShort16);
var
d100: TDiv100Rec;
begin
if value < 100 then
FormatShort16('0.%%', [UInt2DigitsToShortFast(value), valueunit], result)
else
begin
Div100(value, d100{%H-});
if d100.m = 0 then
FormatShort16('%%', [d100.d, valueunit], result)
else
FormatShort16('%.%%', [d100.d, UInt2DigitsToShortFast(d100.m), valueunit], result);
end;
end;
procedure _TimeToString(value: cardinal; const u: ShortString;
var result: TShort16);
var
d: cardinal;
begin
d := value div 60;
FormatShort16('%%%',
[d, u, UInt2DigitsToShortFast(value - (d * 60))], result);
end;
procedure MicroSecToString(Micro: QWord; out result: TShort16);
begin
if Int64(Micro) <= 0 then
PCardinal(@result)^ := 3 + ord('0') shl 8 + ord('u') shl 16 + ord('s') shl 24
else if Micro < 1000 then
FormatShort16('%us', [Micro], result)
else if Micro < 1000000 then
By100ToTwoDigitString(
{$ifdef CPU32} PCardinal(@Micro)^ {$else} Micro {$endif} div 10, 'ms', result)
else if Micro < 60000000 then
By100ToTwoDigitString(
{$ifdef CPU32} PCardinal(@Micro)^ {$else} Micro {$endif} div 10000, 's', result)
else if Micro < QWord(3600000000) then
_TimeToString(
{$ifdef CPU32} PCardinal(@Micro)^ {$else} Micro {$endif} div 1000000, 'm', result)
else if Micro < QWord(86400000000 * 2) then
_TimeToString(Micro div 60000000, 'h', result)
else
FormatShort16('%d', [Micro div QWord(86400000000)], result)
end;
procedure NanoSecToString(Nano: QWord; out result: TShort16);
begin
if Int64(Nano) <= 0 then
PCardinal(@result)^ := 3 + ord('0') shl 8 + ord('n') shl 16 + ord('s') shl 24
else if Nano > 9900 then
MicroSecToString(Nano div 1000, result)
else if Nano >= 1000 then
By100ToTwoDigitString(
{$ifdef CPU32} PCardinal(@Nano)^ {$else} Nano {$endif} div 10, 'us', result)
else
By100ToTwoDigitString(
{$ifdef CPU32} PCardinal(@Nano)^ {$else} Nano {$endif} * 100, 'ns', result);
end;
{ ************ ESynException class }
{ ESynException }
procedure ESynException.CreateAfterSetMessageUtf8;
begin
inherited Create(Utf8ToString(fMessageUtf8));
end;
constructor ESynException.CreateUtf8(const Format: RawUtf8;
const Args: array of const);
begin
FormatUtf8(Format, Args, fMessageUtf8);
CreateAfterSetMessageUtf8;
end;
constructor ESynException.CreateU(const Msg: RawUtf8);
begin
fMessageUtf8 := Msg;
CreateAfterSetMessageUtf8;
end;
constructor ESynException.CreateLastOSError(const Format: RawUtf8;
const Args: array of const; const Trailer: ShortString);
var
error: integer;
fmt: RawUtf8;
begin
error := GetLastError;
FormatUtf8('% 0x% [%] %', [Trailer, CardinalToHexShort(error),
StringReplaceAll(GetErrorText(error), '%', '#'), Format], fmt);
CreateUtf8(fmt, Args);
end;
{$ifndef NOEXCEPTIONINTERCEPT}
function DefaultSynLogExceptionToStr(WR: TTextWriter;
const Context: TSynLogExceptionContext): boolean;
var
extcode: cardinal;
extnames: TPUtf8CharDynArray;
i: PtrInt;
begin
WR.AddClassName(Context.EClass);
if (Context.ELevel = sllException) and
(Context.EInstance <> nil) and
(Context.EClass <> EExternalException) then
begin
extcode := Context.AdditionalInfo(extnames);
if extcode <> 0 then
begin
WR.AddShorter(' 0x');
WR.AddBinToHexDisplayLower(@extcode, SizeOf(extcode));
for i := 0 to high(extnames) do
begin
{$ifdef OSWINDOWS}
WR.AddShort(' [.NET/CLR unhandled ');
{$else}
WR.AddShort(' [unhandled ');
{$endif OSWINDOWS}
WR.AddNoJsonEscape(extnames[i]);
WR.AddShort('Exception]');
end;
end;
WR.AddDirect(' ');
if WR.ClassType = TTextWriter then
{$ifdef UNICODE}
WR.AddOnSameLineW(pointer(Context.EInstance.Message), 0)
{$else}
WR.AddOnSameLine(pointer(Context.EInstance.Message))
{$endif UNICODE}
else
WR.WriteObject(Context.EInstance); // use RTTI for JSON serialization
end
else if Context.ECode <> 0 then
begin
WR.AddDirect(' ', '(');
WR.AddPointer(Context.ECode);
WR.AddDirect(')');
end;
result := false; // caller should append "at EAddr" and the stack trace
end;
function ESynException.CustomLog(WR: TTextWriter;
const Context: TSynLogExceptionContext): boolean;
begin
if Assigned(TSynLogExceptionToStrCustom) then
result := TSynLogExceptionToStrCustom(WR, Context)
else
result := DefaultSynLogExceptionToStr(WR, Context);
end;
{$endif NOEXCEPTIONINTERCEPT}
function StatusCodeToErrorMsg(Code: integer): RawUtf8;
begin
FormatUtf8('HTTP Error % - %', [Code, StatusCodeToText(Code)^], result);
end;
{ **************** Hexadecimal Text And Binary Conversion }
procedure BinToHex(Bin, Hex: PAnsiChar; BinBytes: PtrInt);
var
{$ifdef CPUX86NOTPIC}
tab: TAnsiCharToWord absolute TwoDigitsHexW;
{$else}
tab: PAnsiCharToWord; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
begin
{$ifndef CPUX86NOTPIC}
tab := @TwoDigitsHexW;
{$endif CPUX86NOTPIC}
if BinBytes > 0 then
repeat
PWord(Hex)^ := tab[Bin^];
inc(Bin);
inc(Hex, 2);
dec(BinBytes);
until BinBytes = 0;
end;
function BinToHex(const Bin: RawByteString): RawUtf8;
var
L: integer;
begin
L := length(Bin);
FastSetString(result, L * 2);
mormot.core.text.BinToHex(pointer(Bin), pointer(result), L);
end;
function BinToHex(Bin: PAnsiChar; BinBytes: PtrInt): RawUtf8;
begin
FastSetString(result, BinBytes * 2);
mormot.core.text.BinToHex(Bin, pointer(result), BinBytes);
end;
function HexToBin(Hex: PAnsiChar; HexLen: PtrInt;
var Bin: RawByteString): boolean;
begin
Bin := '';
if HexLen and 1 <> 0 then
begin
result := false;
exit; // hexadecimal should be in char pairs
end;
HexLen := HexLen shr 1;
pointer(Bin) := FastNewString(HexLen, CP_RAWBYTESTRING);
result := mormot.core.text.HexToBin(Hex, pointer(Bin), HexLen);
if not result then
Bin := '';
end;
function HexToBin(const Hex: RawUtf8): RawByteString;
begin
HexToBin(pointer(Hex), length(Hex), result);
end;
function HexaToByte(P: PUtf8Char; var Dest: byte; tab: PByteArray): boolean;
{$ifdef HASINLINE}inline;{$endif}
var
b, c: byte;
begin
b := tab[Ord(P[0]) + 256]; // + 256 for shl 4
if b <> 255 then
begin
c := tab[Ord(P[1])];
if c <> 255 then
begin
inc(b, c);
Dest := b;
result := true;
exit;
end;
end;
result := false; // mark error
end;
function HumanHexToBin(const hex: RawUtf8; var Bin: RawByteString): boolean;
var
len: PtrInt;
h, p: PAnsiChar;
tab: PByteArray;
begin
Bin := '';
result := false;
len := length(hex);
if len = 0 then
exit;
p := FastNewString(len shr 1, CP_RAWBYTESTRING); // shr 1 = maximum length
pointer(Bin) := p;
h := pointer(hex);
tab := @ConvertHexToBin;
repeat
while h^ = ' ' do
inc(h);
if not HexaToByte(pointer(h), PByte(p)^, tab) then
break; // invalid 'xx' pair - may be len < 2
inc(p);
inc(h, 2);
dec(len, 2);
if len = 0 then
begin
result := true; // properly ended with 'xx' last hexa byte
break;
end;
while h^ = ' ' do
inc(h);
if h^ <> ':' then
continue;
dec(len);
if len = 0 then
break; // should not end with ':'
inc(h);
until false;
if result then
FakeLength(Bin, p - pointer(Bin))
else
Bin := '';
end;
function HumanHexCompare(a, b: PUtf8Char): integer;
var
ca, cb: byte;
tab: PByteArray;
begin
result := 0;
if a <> b then
if a <> nil then
if b <> nil then
begin
tab := @ConvertHexToBin;
repeat
while a^ = ' ' do
inc(a);
while b^ = ' ' do
inc(b);
if not HexaToByte(pointer(a), ca{%H-}, tab) or
not HexaToByte(pointer(b), cb{%H-}, tab) then
begin
result := ComparePointer(a, b); // consistent but not zero
break;
end;
result := ca - cb;
if result <> 0 then
break;
inc(a, 2);
inc(b, 2);
while a^ = ' ' do
inc(a);
while b^ = ' ' do
inc(b);
case a^ of
#0:
begin
if b^ <> #0 then
dec(result);
break;
end;
':':
inc(a);
end;
case b^ of
#0:
begin
inc(result); // we know a^<>#0
break;
end;
':':
inc(b);
end;
until false;
end
else
inc(result)
else
dec(result);
end;
function HumanHexCompare(const a, b: RawUtf8): integer;
begin
result := HumanHexCompare(pointer(a), pointer(b));
end;
function HumanHexToBin(const hex: RawUtf8): RawByteString;
begin
HumanHexToBin(hex, result);
end;
function ByteToHex(P: PAnsiChar; Value: byte): PAnsiChar;
begin
PWord(P)^ := TwoDigitsHexWB[Value];
result := P + 2;
end;
procedure BinToHexDisplay(Bin, Hex: PAnsiChar; BinBytes: PtrInt);
var
{$ifdef CPUX86NOTPIC}
tab: TAnsiCharToWord absolute TwoDigitsHexW;
{$else}
tab: PAnsiCharToWord; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
begin
{$ifndef CPUX86NOTPIC}
tab := @TwoDigitsHexW;
{$endif CPUX86NOTPIC}
inc(Hex, BinBytes * 2);
if BinBytes > 0 then
repeat
dec(Hex, 2);
PWord(Hex)^ := tab[Bin^];
inc(Bin);
dec(BinBytes);
until BinBytes = 0;
end;
function BinToHexDisplay(Bin: PAnsiChar; BinBytes: PtrInt): RawUtf8;
begin
FastSetString(result, BinBytes * 2);
BinToHexDisplay(Bin, pointer(result), BinBytes);
end;
procedure BinToHexLower(Bin, Hex: PAnsiChar; BinBytes: PtrInt);
var
{$ifdef CPUX86NOTPIC}
tab: TAnsiCharToWord absolute TwoDigitsHexWLower;
{$else}
tab: PAnsiCharToWord; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
begin
{$ifndef CPUX86NOTPIC}
tab := @TwoDigitsHexWLower;
{$endif CPUX86NOTPIC}
if BinBytes > 0 then
repeat
PWord(Hex)^ := tab[Bin^];
inc(Bin);
inc(Hex, 2);
dec(BinBytes);
until BinBytes = 0;
end;
function BinToHexLower(const Bin: RawByteString): RawUtf8;
begin
BinToHexLower(pointer(Bin), length(Bin), result);
end;
procedure BinToHexLower(Bin: PAnsiChar; BinBytes: PtrInt; var result: RawUtf8);
begin
FastSetString(result, BinBytes * 2);
BinToHexLower(Bin, pointer(result), BinBytes);
end;
function BinToHexLower(Bin: PAnsiChar; BinBytes: PtrInt): RawUtf8;
begin
BinToHexLower(Bin, BinBytes, result);
end;
procedure BinToHexDisplayLower(Bin, Hex: PAnsiChar; BinBytes: PtrInt);
var
{$ifdef CPUX86NOTPIC}
tab: TAnsiCharToWord absolute TwoDigitsHexWLower;
{$else}
tab: PAnsiCharToWord; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
begin
if (Bin = nil) or
(Hex = nil) or
(BinBytes <= 0) then
exit;
{$ifndef CPUX86NOTPIC}
tab := @TwoDigitsHexWLower;
{$endif CPUX86NOTPIC}
inc(Hex, BinBytes * 2);
repeat
dec(Hex, 2);
PWord(Hex)^ := tab[Bin^];
inc(Bin);
dec(BinBytes);
until BinBytes = 0;
end;
function BinToHexDisplayLower(Bin: PAnsiChar; BinBytes: PtrInt): RawUtf8;
begin
FastSetString(result, BinBytes * 2);
BinToHexDisplayLower(Bin, pointer(result), BinBytes);
end;
function BinToHexDisplayLowerShort(Bin: PAnsiChar; BinBytes: PtrInt): ShortString;
begin
if BinBytes > 127 then
BinBytes := 127;
result[0] := AnsiChar(BinBytes * 2);
BinToHexDisplayLower(Bin, @result[1], BinBytes);
end;
function {%H-}BinToHexDisplayLowerShort16(Bin: Int64; BinBytes: PtrInt): TShort16;
begin
if BinBytes > 8 then
BinBytes := 8;
result[0] := AnsiChar(BinBytes * 2);
BinToHexDisplayLower(@Bin, @result[1], BinBytes);
end;
procedure BinBitsToHexDisplayLowerShort16(Bin: Int64; BinBits: PtrInt;
var Result: TShort16);
begin
Result[0] := AnsiChar(BitsToBytes(BinBits) * 2);
if Result[0] > #16 then
Result[0] := #16;
BinToHexDisplayLower(@Bin, @Result[1], ord(Result[0]) shr 1);
end;
{$ifdef UNICODE}
function BinToHexDisplayFile(Bin: PAnsiChar; BinBytes: PtrInt): TFileName;
var
temp: TSynTempBuffer;
begin
temp.Init(BinBytes * 2);
BinToHexDisplayLower(Bin, temp.Buf, BinBytes);
Ansi7ToString(PWinAnsiChar(temp.buf), BinBytes * 2, string(result));
temp.Done;
end;
{$else}
function BinToHexDisplayFile(Bin: PAnsiChar; BinBytes: PtrInt): TFileName;
begin
SetString(result, nil, BinBytes * 2);
BinToHexDisplayLower(Bin, pointer(result), BinBytes);
end;
{$endif UNICODE}
procedure PointerToHex(aPointer: Pointer; var result: RawUtf8);
begin
FastSetString(result, SizeOf(Pointer) * 2);
BinToHexDisplay(@aPointer, pointer(result), SizeOf(Pointer));
end;
function PointerToHex(aPointer: Pointer): RawUtf8;
begin
PointerToHex(aPointer, result);
end;
function CardinalToHex(aCardinal: cardinal): RawUtf8;
begin
FastSetString(result, SizeOf(aCardinal) * 2);
BinToHexDisplay(@aCardinal, pointer(result), SizeOf(aCardinal));
end;
function CardinalToHexLower(aCardinal: cardinal): RawUtf8;
begin
FastSetString(result, SizeOf(aCardinal) * 2);
BinToHexDisplayLower(@aCardinal, pointer(result), SizeOf(aCardinal));
end;
function Int64ToHex(aInt64: Int64): RawUtf8;
begin
FastSetString(result, SizeOf(Int64) * 2);
BinToHexDisplay(@aInt64, pointer(result), SizeOf(Int64));
end;
procedure Int64ToHex(aInt64: Int64; var result: RawUtf8);
begin
FastSetString(result, SizeOf(Int64) * 2);
BinToHexDisplay(@aInt64, pointer(result), SizeOf(Int64));
end;
function PointerToHexShort(aPointer: Pointer): TShort16;
begin
result[0] := AnsiChar(DisplayMinChars(@aPointer, SizeOf(aPointer)) * 2);
BinToHexDisplayLower(@aPointer, @result[1], ord(result[0]) shr 1);
end;
function CardinalToHexShort(aCardinal: cardinal): TShort16;
begin
result[0] := AnsiChar(SizeOf(aCardinal) * 2);
BinToHexDisplay(@aCardinal, @result[1], SizeOf(aCardinal));
end;
function crc32cUtf8ToHex(const str: RawUtf8): RawUtf8;
begin
result := CardinalToHex(crc32c(0, pointer(str), length(str)));
end;
function Int64ToHexShort(aInt64: Int64): TShort16;
begin
result[0] := AnsiChar(SizeOf(aInt64) * 2);
BinToHexDisplay(@aInt64, @result[1], SizeOf(aInt64));
end;
function ToHexShort(P: pointer; Len: PtrInt): TShort64;
begin
if Len = 0 then
begin
result[0] := AnsiChar(Len);
exit;
end;
if Len > 32 then
Len := 32;
Len := DisplayMinChars(P, Len);
result[0] := AnsiChar(Len * 2);
BinToHexDisplay(P, @result[1], Len);
end;
function Int64ToHexLower(aInt64: Int64): RawUtf8;
var
L: PtrInt;
begin
L := DisplayMinChars(@aInt64, SizeOf(Int64));
FastSetString(result, L * 2);
BinToHexDisplay(@aInt64, pointer(result), L);
end;
procedure Int64ToHexShort(aInt64: Int64; out result: TShort16);
begin
result[0] := AnsiChar(SizeOf(aInt64) * 2);
BinToHexDisplay(@aInt64, @result[1], SizeOf(aInt64));
end;
function Int64ToHexString(aInt64: Int64): string;
var
temp: TShort16;
begin
Int64ToHexShort(aInt64, temp);
Ansi7ToString(@temp[1], ord(temp[0]), result);
end;
function HexDisplayToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: PtrInt): boolean;
var
b, c: byte;
{$ifdef CPUX86NOTPIC}
tab: THexToDualByte absolute ConvertHexToBin;
{$else}
tab: PByteArray; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
begin
result := false; // return false if any invalid char
if (Hex = nil) or
(Bin = nil) then
exit;
{$ifndef CPUX86NOTPIC}
tab := @ConvertHexToBin;
{$endif CPUX86NOTPIC}
if BinBytes > 0 then
begin
inc(Bin, BinBytes - 1); // display = reverse order
repeat
b := tab[Ord(Hex[0]) + 256]; // + 256 for shl 4
if b = 255 then
exit;
c := tab[Ord(Hex[1])];
if c = 255 then
exit;
Bin^ := b or c;
dec(Bin);
inc(Hex, 2);
dec(BinBytes);
until BinBytes = 0;
end;
result := true; // correct content in Hex
end;
function HexDisplayToCardinal(Hex: PAnsiChar; out aValue: cardinal): boolean;
begin
result := HexDisplayToBin(Hex, @aValue, SizeOf(aValue));
if not result then
aValue := 0;
end;
function HexDisplayToInt64(Hex: PAnsiChar; out aValue: Int64): boolean;
begin
result := HexDisplayToBin(Hex, @aValue, SizeOf(aValue));
if not result then
aValue := 0;
end;
function HexDisplayToInt64(const Hex: RawByteString): Int64;
begin
if not HexDisplayToBin(pointer(Hex), @result, SizeOf(result)) then
result := 0;
end;
function HexToBin(Hex: PAnsiChar; Bin: PByte; BinBytes: PtrInt): boolean;
var
b, c: byte;
{$ifdef CPUX86NOTPIC}
tab: THexToDualByte absolute ConvertHexToBin;
{$else}
tab: PByteArray; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
begin
result := false; // return false if any invalid char
if Hex = nil then
exit;
{$ifndef CPUX86NOTPIC}
tab := @ConvertHexToBin;
{$endif CPUX86NOTPIC}
if BinBytes > 0 then
if Bin <> nil then
repeat
b := tab[Ord(Hex[0]) + 256]; // + 256 for shl 4
if b = 255 then
exit;
c := tab[Ord(Hex[1])];
if c = 255 then
exit;
inc(Hex, 2);
Bin^ := b or c;
inc(Bin);
dec(BinBytes);
until BinBytes = 0
else
repeat // Bin=nil -> validate Hex^ input
if (tab[Ord(Hex[0])] > 15) or
(tab[Ord(Hex[1])] > 15) then
exit;
inc(Hex, 2);
dec(BinBytes);
until BinBytes = 0;
result := true; // conversion OK
end;
procedure HexToBinFast(Hex: PAnsiChar; Bin: PByte; BinBytes: PtrInt);
var
{$ifdef CPUX86NOTPIC}
tab: THexToDualByte absolute ConvertHexToBin;
{$else}
tab: PByteArray; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
c: byte;
begin
{$ifndef CPUX86NOTPIC}
tab := @ConvertHexToBin;
{$endif CPUX86NOTPIC}
if BinBytes > 0 then
repeat
c := tab[ord(Hex[0]) + 256]; // + 256 for shl 4
c := tab[ord(Hex[1])] or c;
Bin^ := c;
inc(Hex, 2);
inc(Bin);
dec(BinBytes);
until BinBytes = 0;
end;
function IsHex(const Hex: RawByteString; BinBytes: PtrInt): boolean;
begin
result := (length(Hex) = BinBytes * 2) and
mormot.core.text.HexToBin(pointer(Hex), nil, BinBytes);
end;
function HexToCharValid(Hex: PAnsiChar): boolean;
begin
result := (ConvertHexToBin[Ord(Hex[0])] <= 15) and
(ConvertHexToBin[Ord(Hex[1])] <= 15);
end;
function HexToCharValid(Hex: PAnsiChar; HexToBin: PByteArray): boolean;
begin
result := (HexToBin[Ord(Hex[0])] <= 15) and
(HexToBin[Ord(Hex[1])] <= 15);
end;
function HexToChar(Hex: PAnsiChar; Bin: PUtf8Char; HexToBin: PByteArray): boolean;
var
b, c: byte;
begin
if Hex <> nil then
begin
b := HexToBin[ord(Hex[0]) + 256]; // + 256 for shl 4
c := HexToBin[ord(Hex[1])];
if (b <> 255) and
(c <> 255) then
begin
if Bin <> nil then
begin
inc(c, b);
Bin^ := AnsiChar(c);
end;
result := true;
exit;
end;
end;
result := false; // return false if any invalid char
end;
function HexToWideChar(Hex: PUtf8Char): cardinal;
var
B: cardinal;
{$ifdef CPUX86NOTPIC}
tab: THexToDualByte absolute ConvertHexToBin;
{$else}
tab: PByteArray; // faster on PIC, ARM and x86_64
{$endif CPUX86NOTPIC}
begin
{$ifndef CPUX86NOTPIC}
tab := @ConvertHexToBin;
{$endif CPUX86NOTPIC}
result := tab[ord(Hex[0])];
if result <= 15 then
begin
result := result shl 12;
B := tab[ord(Hex[1])];
if B <= 15 then
begin
B := B shl 8;
inc(result, B);
B := tab[ord(Hex[2])];
if B <= 15 then
begin
B := B shl 4;
inc(result, B);
B := tab[ord(Hex[3])];
if B <= 15 then
begin
inc(result, B);
exit;
end;
end;
end;
end;
result := 0;
end;
function OctToBin(Oct: PAnsiChar; Bin: PByte): PtrInt;
var
c, v: byte;
label
_nxt;
begin
result := PtrInt(Bin);
if Oct <> nil then
repeat
c := ord(Oct^);
inc(Oct);
if c <> ord('\') then
begin
if c = 0 then
break;
_nxt: Bin^ := c;
inc(Bin);
continue;
end;
c := ord(Oct^);
inc(Oct);
if c = ord('\') then
goto _nxt;
dec(c, ord('0'));
if c > 3 then
// stop at malformated input (includes #0)
break;
c := c shl 6;
v := c;
c := ord(Oct[0]);
dec(c, ord('0'));
if c > 7 then
break;
c := c shl 3;
v := v or c;
c := ord(Oct[1]);
dec(c, ord('0'));
if c > 7 then
break;
c := c or v;
Bin^ := c;
inc(Bin);
inc(Oct, 2);
until false;
result := PAnsiChar(Bin) - PAnsiChar(result);
end;
function OctToBin(const Oct: RawUtf8): RawByteString;
var
tmp: TSynTempBuffer;
L: integer;
begin
tmp.Init(length(Oct));
try
L := OctToBin(pointer(Oct), tmp.buf);
FastSetRawByteString(result, tmp.buf, L);
finally
tmp.Done;
end;
end;
function GuidToText(P: PUtf8Char; guid: PByteArray): PUtf8Char;
var
i: PtrInt;
tab: PWordArray;
begin
// encode as '3F2504E0-4F89-11D3-9A0C-0305E82C3301'
tab := @TwoDigitsHexWB;
for i := 3 downto 0 do
begin
PWord(P)^ := tab[guid[i]];
inc(P, 2);
end;
inc(PByte(guid), 4);
for i := 1 to 2 do
begin
P[0] := '-';
PWord(P + 1)^ := tab[guid[1]];
PWord(P + 3)^ := tab[guid[0]];
inc(PByte(guid), 2);
inc(P, 5);
end;
P[0] := '-';
PWord(P + 1)^ := tab[guid[0]];
PWord(P + 3)^ := tab[guid[1]];
P[5] := '-';
inc(PByte(guid), 2);
inc(P, 6);
for i := 0 to 5 do
begin
PWord(P)^ := tab[guid[i]];
inc(P, 2);
end;
result := P;
end;
function GuidToRawUtf8(const guid: TGuid): RawUtf8;
var
P: PUtf8Char;
begin
FastSetString(result, 38);
P := pointer(result);
P^ := '{';
GuidToText(P + 1, @guid)^ := '}';
end;
function ToUtf8(const guid: TGuid): RawUtf8;
begin
FastSetString(result, 36);
GuidToText(pointer(result), @Guid);
end;
function GuidToShort(const guid: TGuid): TGuidShortString;
begin
GuidToShort(Guid, result);
end;
procedure GuidToShort(const guid: TGuid; out dest: TGuidShortString);
begin
dest[0] := #38;
dest[1] := '{';
GuidToText(@dest[2], @guid)^ := '}';
end;
{$ifdef UNICODE}
function GuidToString(const guid: TGuid): string;
var
tmp: TGuidShortString;
begin
GuidToShort(guid, tmp);
Ansi7ToString(@tmp[1], 38, result);
end;
{$else}
function GuidToString(const guid: TGuid): string;
begin
result := GuidToRawUtf8(guid);
end;
{$endif UNICODE}
function TextToGuid(P: PUtf8Char; guid: PByteArray): PUtf8Char;
var
i: PtrInt;
tab: PByteArray;
begin
// decode from '3F2504E0-4F89-11D3-9A0C-0305E82C3301'
result := nil;
tab := @ConvertHexToBin;
for i := 3 downto 0 do
begin
if not HexaToByte(P, guid[i], tab) then
exit;
inc(P, 2);
end;
inc(PByte(guid), 4);
for i := 1 to 2 do
begin
if P^ = '-' then // '-' separators are optional
inc(P);
if not HexaToByte(P, guid[1], tab) or
not HexaToByte(P + 2, guid[0], tab) then
exit;
inc(P, 4);
inc(PByte(guid), 2);
end;
if P^ = '-' then
inc(P);
if not HexaToByte(P, guid[0], tab) or // in reverse order than the previous loop
not HexaToByte(P + 2, guid[1], tab) then
exit;
inc(P, 4);
inc(PByte(guid), 2);
if P^ = '-' then
inc(P);
for i := 0 to 5 do
if HexaToByte(P, guid[i], tab) then
inc(P, 2)
else
exit;
result := P;
end;
function StringToGuid(const text: string): TGuid;
{$ifdef UNICODE}
var
tmp: array[0..35] of byte;
i: integer;
{$endif UNICODE}
begin
if (length(text) = 38) and
(text[1] = '{') and
(text[38] = '}') then
begin
{$ifdef UNICODE}
for i := 0 to 35 do
tmp[i] := PWordArray(text)[i + 1];
if TextToGuid(@tmp, @result) <> nil then
{$else}
if TextToGuid(@text[2], @result) <> nil then
{$endif UNICODE}
exit; // conversion OK
end;
FillZero(PHash128(@result)^);
end;
function RawUtf8ToGuid(const text: RawByteString): TGuid;
begin
if not RawUtf8ToGuid(text, result) then
FillZero(PHash128(@result)^);
end;
function RawUtf8ToGuid(const text: RawByteString; out guid: TGuid): boolean;
begin
result := true;
case length(text) of
32, // '3F2504E04F8911D39A0C0305E82C3301' TextToGuid() order, not HexToBin()
36: // '3F2504E0-4F89-11D3-9A0C-0305E82C3301' JSON compatible layout
if TextToGuid(pointer(text), @guid) <> nil then
exit;
38: // '{3F2504E0-4F89-11D3-9A0C-0305E82C3301}' regular layout
if (text[1] <> '{') or
(text[38] <> '}') or
(TextToGuid(@text[2], @guid) <> nil) then
exit;
end;
result := false;
end;
function TrimGuid(var text: RawUtf8): boolean;
var
s, d: PUtf8Char;
L: PtrInt;
c: AnsiChar;
begin
s := UniqueRawUtf8(text);
if s = nil then
begin
result := false;
exit;
end;
result := true;
d := s;
repeat
c := s^;
inc(s);
case c of
#0:
break;
#1..' ', '-', '{', '}': // trim spaces and GUID/UUID separators
continue;
'A'..'F':
inc(c, 32); // convert to lower-case
'a'..'f', '0'..'9':
; // valid hexadecimal char
else
result := false; // not a true hexadecimal content
end;
d^ := c;
inc(d);
until false;
L := d - pointer(text);
if L = 0 then
begin
FastAssignNew(text);
result := false;
end
else
begin
FakeLength(text, L);
result := result and (L = 32);
end;
end;
function StreamToRawByteString(aStream: TStream; aSize: Int64;
aCodePage: integer): RawByteString;
var
current: Int64;
begin
result := '';
if aStream = nil then
exit;
current := aStream.Position;
if (current = 0) and
aStream.InheritsFrom(TRawByteStringStream) and
((aSize < 0) or
(aSize = length(TRawByteStringStream(aStream).DataString))) then
begin
result := TRawByteStringStream(aStream).DataString; // fast COW
exit;
end;
if aSize < 0 then
aSize := aStream.Size - current;
if (aSize = 0) or
(aSize > maxInt) then
exit;
pointer(result) := FastNewString(aSize, aCodePage);
aStream.ReadBuffer(pointer(result)^, aSize);
aStream.Position := current;
end;
function StreamChangeToRawByteString(aStream: TStream; var aPosition: Int64): RawByteString;
var
current, size: Int64;
begin
result := '';
if aStream = nil then
exit;
size := aStream.Size - aPosition;
if size <= 0 then
exit; // nothing new
pointer(result) := FastNewString(size, CP_RAWBYTESTRING);
current := aStream.Position;
aStream.Position := aPosition;
aStream.ReadBuffer(pointer(result)^, size);
aStream.Position := current;
aPosition := current;
end;
function RawByteStringToStream(const aString: RawByteString): TStream;
begin
result := TRawByteStringStream.Create(aString);
end;
function ReadStringFromStream(S: TStream; MaxAllowedSize: integer): RawUtf8;
var
L: integer;
begin
result := '';
L := 0;
if (S.Read(L, 4) <> 4) or
(L <= 0) or
(L > MaxAllowedSize) then
exit;
FastSetString(result, L);
if S.Read(pointer(result)^, L) <> L then
result := '';
end;
function WriteStringToStream(S: TStream; const Text: RawUtf8): boolean;
var
L: integer;
begin
L := length(Text);
if L = 0 then
result := S.Write(L, 4) = 4
else
{$ifdef FPC}
result := (S.Write(L, 4) = 4) and
(S.Write(pointer(Text)^, L) = L);
{$else}
result := S.Write(pointer(PtrInt(Text) - SizeOf(integer))^, L + 4) = L + 4;
{$endif FPC}
end;
const // should be local for better code generation
HexChars: array[0..15] of AnsiChar = '0123456789ABCDEF';
HexCharsLower: array[0..15] of AnsiChar = '0123456789abcdef';
procedure InitializeUnit;
var
i: PtrInt;
v: byte;
c: AnsiChar;
P: PAnsiChar;
B: PByteArray;
tmp: array[0..15] of AnsiChar;
begin
// initialize internal lookup tables for various text conversions
for i := 0 to 255 do
begin
TwoDigitsHex[i][1] := HexChars[i shr 4];
TwoDigitsHex[i][2] := HexChars[i and $f];
TwoDigitsHexLower[i][1] := HexCharsLower[i shr 4];
TwoDigitsHexLower[i][2] := HexCharsLower[i and $f];
end;
{$ifndef EXTENDEDTOSHORT_USESTR}
{$ifdef ISDELPHIXE}
SettingsUS := TFormatSettings.Create(ENGLISH_LANGID);
{$else}
GetLocaleFormatSettings(ENGLISH_LANGID, SettingsUS);
{$endif ISDELPHIXE}
SettingsUS.DecimalSeparator := '.'; // value may have been overriden :(
{$endif EXTENDEDTOSHORT_USESTR}
{$ifdef DOUBLETOSHORT_USEGRISU}
MoveFast(TwoDigitLookup[0], TwoDigitByteLookupW[0], SizeOf(TwoDigitLookup));
for i := 0 to 199 do
dec(PByteArray(@TwoDigitByteLookupW)[i], ord('0')); // '0'..'9' -> 0..9
{$endif DOUBLETOSHORT_USEGRISU}
FillcharFast(ConvertHexToBin[0], SizeOf(ConvertHexToBin), 255); // all to 255
B := @ConvertHexToBin;
v := 0;
for i := ord('0') to ord('9') do
begin
B[i] := v;
B[i + 256] := v shl 4;
inc(v);
end;
for i := ord('A') to ord('F') do
begin
B[i] := v;
B[i + 256] := v shl 4;
B[i + (ord('a') - ord('A'))] := v;
B[i + (ord('a') - ord('A') + 256)] := v shl 4;
inc(v);
end;
for i := 0 to high(SmallUInt32Utf8) do
begin
P := StrUInt32(@tmp[15], i);
FastSetString(SmallUInt32Utf8[i], P, @tmp[15] - P);
end;
for c := #0 to #127 do
begin
XML_ESC[c] := ord(c in [#0..#31, '<', '>', '&', '"', '''']);
case c of // HTML_ESCAPED: array[1..4] = '&lt;', '&gt;', '&amp;', '&quot;'
#0,
'<':
v := 1;
'>':
v := 2;
'&':
v := 3;
'"':
v := 4;
else
v := 0;
end;
HTML_ESC[hfAnyWhere, c] := v;
if c in [#0, '&', '<', '>'] then
HTML_ESC[hfOutsideAttributes, c] := v;
if c in [#0, '&', '"'] then
HTML_ESC[hfWithinAttributes, c] := v;
end;
_VariantToUtf8DateTimeToIso8601 := __VariantToUtf8DateTimeToIso8601;
_VariantSaveJson := __VariantSaveJson;
TextWriterSharedStream := TRawByteStringStream.Create;
end;
initialization
InitializeUnit;
finalization
TextWriterSharedStream.Free;
end.