10012 lines
307 KiB
ObjectPascal
10012 lines
307 KiB
ObjectPascal
{ unit NativeXml
|
|
|
|
This is a small-footprint implementation to read and write XML documents
|
|
natively from Delpi Object Pascal code. NativeXml has very fast parsing speeds.
|
|
|
|
You can use this code to read XML documents from files, streams or strings.
|
|
The load routine generates events that can be used to display load progress
|
|
on the fly.
|
|
|
|
Note #1: this unit is a completely redesigned implementation of legacy NativeXml.
|
|
|
|
Note #2: any external encoding (ANSI, UTF16, etc) is converted to an internal
|
|
encoding that is always UTF8. NativeXml uses Utf8String as string type internally,
|
|
and converts from strings with external encoding in the parsing process.
|
|
When writing, Utf8String strings are converted to the external encoding strings,
|
|
if the encoding was set beforehand, or defaults to UTF8 if no encoding was set.
|
|
|
|
Note #3: the character data is always normalized inside the document (just a $0A
|
|
instead of $0D$0A in Windows for end-of-lines). If EolStyle = esCRLF, the
|
|
data is un-normalized before it gets consumed. If you need no un-normalisation
|
|
(and after all it is non-optimal) you can use EolStyle = esLF (default).
|
|
|
|
Note #4: Binary XML: Since NativeXml v4.00, you can use the binary file format of
|
|
NativeXml, named BXM.
|
|
BXM has these advantages:
|
|
- No need to parse the plain text-based XML file.
|
|
- The binary format avoids repeated instances of duplicate strings and
|
|
thus allows a compact representation of all the XML element types.
|
|
Furthermore, the stringtable is sorted by frequency before storing
|
|
BXM to file, which compacts the format even more (smaller indices for
|
|
more frequent strings, so less space in the file).
|
|
- BXM allows options "none" (no compression), "zlib" (zlib compression,
|
|
aka "deflate"), and other compression schemes based on event handlers.
|
|
This allows for ~50% of the total conventional xml size for "none" and
|
|
~15% of the size for "zlib". So a huge size reduction.
|
|
- In (near) future, BXM will allow other data formats besides "string",
|
|
e.g. data formats Date, DateTime, Base64Binary, HexBinary and Decimal.
|
|
This will reduce the binary size even more, esp for xml files that use
|
|
Base64 for binary content.
|
|
- My aim is to keep the BXM file format backwards compatible, so you can
|
|
always open BXM files based on earlier versions.
|
|
- BXM allows external encryption/compression thru event handlers. f.i. AES
|
|
encryption is handled in functions TNativeXml.AeszEncode / AeszDecode.
|
|
|
|
Author: Nils Haeck M.Sc.
|
|
Creation Date: 01apr2003
|
|
Major Rewrite: 10nov2010
|
|
|
|
Contributor(s):
|
|
Marius Z: devised and helped with the LINQ-like stackable NodeNewXYZ
|
|
functions in TNativeXml
|
|
Stefan Glienke: TDateTime methods use GetTimeZoneInformation
|
|
Hans-Dieter Karl (hdk): added additional Ansi/Wide/Int64/DateTime functions, some fixes
|
|
|
|
It is NOT allowed under ANY circumstances to publish, alter or copy this code
|
|
without accepting the license conditions in accompanying LICENSE.txt
|
|
first!
|
|
|
|
This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
|
|
ANY KIND, either express or implied.
|
|
|
|
Please visit http://www.simdesign.nl/xml.html for more information.
|
|
|
|
Copyright (c) 2003 - 2011 Simdesign B.V. (www.simdesign.nl)
|
|
}
|
|
unit NativeXml;
|
|
|
|
interface
|
|
|
|
{$i simdesign.inc}
|
|
|
|
// define if you want to include the Graphics unit, and graphics-related properties.
|
|
{.$define USEGRAPHICS}
|
|
|
|
// define if you want to include zlib (=deflate) compression in binary xml.
|
|
{.$define USEZLIB}
|
|
|
|
// define if you want to include AES encryption in binary xml. AES functionality is
|
|
// in 3rd-party file ElAES.pas (Eldos AES). Please observe license details.
|
|
{.$define USEAES}
|
|
|
|
// define if you want to use tags.
|
|
// A Tag is an additional pointer field of TXmlNode that can be used by the application,
|
|
// but it is not stored in the xml text.
|
|
{.$define USETAGS}
|
|
|
|
// define if you want an additional int64 field FSourcePos in each TXmlNode
|
|
{.$define SOURCEPOS}
|
|
|
|
uses
|
|
{$ifdef USEGRAPHICS}
|
|
Graphics,
|
|
{$endif USEGRAPHICS}
|
|
{$ifdef USEZLIB}
|
|
ZLib,
|
|
{$endif USEZLIB}
|
|
{$ifdef USEAES}
|
|
// Eldos AES
|
|
ElAES,
|
|
{$endif USEAES}
|
|
|
|
Classes, Contnrs, SysUtils,
|
|
|
|
{$ifdef D5UP}
|
|
// D5 does not define MSWINDOWS
|
|
{$define MSWINDOWS}
|
|
{$endif D5UP}
|
|
|
|
{$ifdef MSWINDOWS}
|
|
// unit Windows defines MultiByteToWideChar and GetTimeZoneInformation
|
|
Windows,
|
|
// unit WinInet for method LoadFromURL
|
|
WinInet,
|
|
{$else MSWINDOWS}
|
|
// linux: win32-compatible functions
|
|
NativeXmlWin32Compat,
|
|
{$endif MSWINDOWS}
|
|
|
|
// units from simlib.general
|
|
sdStreams, sdStringTable, sdDebug, System.Types;
|
|
|
|
const
|
|
|
|
// Current version of the NativeXml unit
|
|
cNativeXmlVersion = 'v4.07';
|
|
|
|
type
|
|
// An event that is used to indicate load or save progress.
|
|
TXmlProgressEvent = procedure(Sender: TObject; Position: int64) of object;
|
|
|
|
// event used for encoding/decoding binary xml
|
|
TXmlCoderEvent = function(StreamIn, StreamOut: TStream; CodecSize: int64): UTF8String of object;
|
|
|
|
// TsdElementType enumerates the different kinds of elements that can be found
|
|
// in the XML document.
|
|
TsdElementType = (
|
|
xeElement, // 0 normal element <name {attr}>[value][sub-elements]</name>
|
|
xeAttribute, // 1 attribute ( name='value' or name="value")
|
|
xeCharData, // 2 character data in a node
|
|
xeComment, // 3 comment <!--{comment}-->
|
|
xeCData, // 4 literal data <![CDATA[{data}]]>
|
|
xeCondSection, // 5 conditional section <![ IGNORE / INCLUDE [ markup ]]>
|
|
xeDeclaration, // 6 xml declaration <?xml{declaration}?>
|
|
xeStylesheet, // 7 stylesheet <?xml-stylesheet{stylesheet}?>
|
|
xeDocType, // 8 doctype dtd declaration <!DOCTYPE{spec}>
|
|
xeDtdElement, // 9 dtd element <!ELEMENT >
|
|
xeDtdAttList, // 10 dtd attlist <!ATTLIST >
|
|
xeDtdEntity, // 11 dtd entity <!ENTITY >
|
|
xeDtdNotation, // 12 dtd notation <!NOTATION >
|
|
xeInstruction, // 13 processing instruction <?...?>
|
|
xeWhiteSpace, // 14 chardata with only whitespace
|
|
xeQuotedText, // 15 quoted text: "bla" or 'bla'
|
|
xeEndTag, // 16 </...> and signal function in binary xml
|
|
xeError // 17 some error or unknown
|
|
);
|
|
TsdElementTypes = set of TsdElementType;
|
|
|
|
// Definition of different methods of string encoding.
|
|
TsdStringEncoding = (
|
|
seAnsi, // Ansi encoding, e.g. "Windows-1252" or other codepage (1 byte per character)
|
|
seUTF8, // utf-8 (1, 2, 3 or 4 bytes per character)
|
|
seUTF16BE, // utf-16 Big Endian (2 or 4 bytes per character)
|
|
seUTF16LE, // utf-16 Little Endian (2 or 4 bytes per character)
|
|
seUTF32BE, // ucs-4 Big Endian (4 bytes per character)
|
|
seUTF32LE, // ucs-4 Little Endian (4 bytes per character)
|
|
seUCS4_2143, // ucs-4 unusual octet order - 2143 (4 bytes per character)
|
|
seUCS4_3412, // ucs-4 unusual octet order - 3412 (4 bytes per character)
|
|
seEBCDIC // Extended Binary Coded Decimal Interchange Code (1 byte per character)
|
|
);
|
|
|
|
// Choose what kind of binary encoding will be used when calling
|
|
// TXmlNode BufferRead and BufferWrite.
|
|
TsdBinaryEncoding = (
|
|
xbeBase64, { With this encoding, each group of 3 bytes are stored as 4
|
|
characters, requiring 64 different characters. - DEFAULT}
|
|
xbeBinHex { With this encoding, each byte is stored as a hexadecimal
|
|
number, e.g. 0 = 00 and 255 = FF. }
|
|
);
|
|
|
|
// Node closing style:
|
|
// ncDefault defaults to what is parsed per element
|
|
// ncFull looks like <node a="bla"></node> and
|
|
// ncClose looks like <node a="bla"/>
|
|
TsdNodeClosingStyle = (
|
|
ncDefault,
|
|
ncFull,
|
|
ncClose
|
|
);
|
|
|
|
// End-Of-Line style
|
|
TsdEolStyle = (
|
|
esLF, // write End-Of-Line as just LF (#$0A) like normalised xml
|
|
esCRLF, // write End-Of-Line as CR + LF (#$0D + #$0A), for the windows platform
|
|
esCR // write End-Of-Line as CR (#$0D), for the mac platform
|
|
);
|
|
|
|
// Note on TNativeXml.XmlFormat:
|
|
// - xfCompact (default) to save the xml fully compliant and at smallest size
|
|
// - xfReadable writes additional nonsignificant whitespace so the client can
|
|
// easily read the xml file with a standard editor.
|
|
// - xfPreserve aims to preserve whitespace data just as it is parsed
|
|
TXmlFormatType = (
|
|
xfCompact, // Save without any control chars except LF after declaration
|
|
xfReadable, // Save in readable format with indents and end-of-lines
|
|
xfPreserve // Preserve whitespace whenever possible
|
|
);
|
|
|
|
// record with info from a Byte order Mark (BOM)
|
|
TsdBomInfo = packed record
|
|
BOM: array[0..3] of byte; // 4 bytes possibly containing the BOM
|
|
Len: integer; // byte length of the BOM
|
|
Encoding: TsdStringEncoding; // which string encoding does the file have?
|
|
HasBOM: boolean; // does a file have a BOM?
|
|
end;
|
|
|
|
// quote char style in TsdQuotedText
|
|
TsdQuoteCharStyle = (
|
|
qsQuote,
|
|
qsApos
|
|
);
|
|
|
|
TXmlCompareOption = (
|
|
xcNodeName,
|
|
xcNodeType,
|
|
xcNodeValue,
|
|
xcAttribCount,
|
|
xcAttribNames,
|
|
xcAttribValues,
|
|
xcChildCount,
|
|
xcChildNames,
|
|
xcChildValues,
|
|
xcRecursive
|
|
);
|
|
TXmlCompareOptions = set of TXmlCompareOption;
|
|
|
|
TsdXmlBinaryMethod = (
|
|
bmDefault, // no compression: 'none'
|
|
bmZlib, // zlib compression: 'zlib'
|
|
bmAesz // AES + zlib compression: 'aesz'
|
|
);
|
|
|
|
// codepage information (name and codepage record)
|
|
TCodepageInfo = packed record
|
|
Name: Utf8String;
|
|
Codepage: integer;
|
|
end;
|
|
|
|
const
|
|
|
|
// Codepages defined in Windows
|
|
cCodepageInfoCount = 143;
|
|
cCodePageInfo: array[0..cCodepageInfoCount - 1] of TCodepageInfo =
|
|
( (Name: 'IBM037'; Codepage: 37), //1
|
|
(Name: 'IBM437'; Codepage: 437),
|
|
(Name: 'IBM500'; Codepage: 500),
|
|
(Name: 'ASMO-708'; Codepage: 708),
|
|
(Name: 'ASMO-449+'; Codepage: 709), //5
|
|
(Name: 'BCON V4'; Codepage: 709),
|
|
(Name: 'Arabic'; Codepage: 710),
|
|
(Name: 'DOS-720'; Codepage: 720),
|
|
(Name: 'ibm737'; Codepage: 737),
|
|
(Name: 'ibm775'; Codepage: 775), //10
|
|
(Name: 'ibm850'; Codepage: 850),
|
|
(Name: 'ibm852'; Codepage: 852),
|
|
(Name: 'IBM855'; Codepage: 855),
|
|
(Name: 'ibm857'; Codepage: 857),
|
|
(Name: 'IBM00858'; Codepage: 858),
|
|
(Name: 'IBM860'; Codepage: 860),
|
|
(Name: 'ibm861'; Codepage: 861),
|
|
(Name: 'DOS-862'; Codepage: 862),
|
|
(Name: 'IBM863'; Codepage: 863),
|
|
(Name: 'IBM864'; Codepage: 864), //20
|
|
(Name: 'IBM865'; Codepage: 865),
|
|
(Name: 'cp866'; Codepage: 866),
|
|
(Name: 'ibm869'; Codepage: 869),
|
|
(Name: 'IBM870'; Codepage: 870),
|
|
(Name: 'windows-874'; Codepage: 874),
|
|
(Name: 'cp875'; Codepage: 875),
|
|
(Name: 'shift_jis'; Codepage: 932),
|
|
(Name: 'gb2312'; Codepage: 936),
|
|
(Name: 'ks_c_5601-1987'; Codepage: 949),
|
|
(Name: 'big5'; Codepage: 950), //30
|
|
(Name: 'IBM1026'; Codepage: 1026),
|
|
(Name: 'IBM01047'; Codepage: 1047),
|
|
(Name: 'IBM01140'; Codepage: 1140),
|
|
(Name: 'IBM01141'; Codepage: 1141),
|
|
(Name: 'IBM01142'; Codepage: 1142),
|
|
(Name: 'IBM01143'; Codepage: 1143),
|
|
(Name: 'IBM01144'; Codepage: 1144),
|
|
(Name: 'IBM01145'; Codepage: 1145),
|
|
(Name: 'IBM01146'; Codepage: 1146),
|
|
(Name: 'IBM01147'; Codepage: 1147), //40
|
|
(Name: 'IBM01148'; Codepage: 1148),
|
|
(Name: 'IBM01149'; Codepage: 1149),
|
|
(Name: 'utf-16'; Codepage: 1200),
|
|
(Name: 'unicodeFFFE'; Codepage: 1201),
|
|
(Name: 'windows-1250'; Codepage: 1250),
|
|
(Name: 'windows-1251'; Codepage: 1251),
|
|
(Name: 'windows-1252'; Codepage: 1252),
|
|
(Name: 'windows-1253'; Codepage: 1253),
|
|
(Name: 'windows-1254'; Codepage: 1254),
|
|
(Name: 'windows-1255'; Codepage: 1255), //50
|
|
(Name: 'windows-1256'; Codepage: 1256),
|
|
(Name: 'windows-1257'; Codepage: 1257),
|
|
(Name: 'windows-1258'; Codepage: 1258),
|
|
(Name: 'Johab'; Codepage: 1361),
|
|
(Name: 'macintosh'; Codepage: 10000),
|
|
(Name: 'x-mac-japanese'; Codepage: 10001),
|
|
(Name: 'x-mac-chinesetrad'; Codepage: 10002),
|
|
(Name: 'x-mac-korean'; Codepage: 10003),
|
|
(Name: 'x-mac-arabic'; Codepage: 10004),
|
|
(Name: 'x-mac-hebrew'; Codepage: 10005), //60
|
|
(Name: 'x-mac-greek'; Codepage: 10006),
|
|
(Name: 'x-mac-cyrillic'; Codepage: 10007),
|
|
(Name: 'x-mac-chinesesimp'; Codepage: 10008),
|
|
(Name: 'x-mac-romanian'; Codepage: 10010),
|
|
(Name: 'x-mac-ukrainian'; Codepage: 10017),
|
|
(Name: 'x-mac-thai'; Codepage: 10021),
|
|
(Name: 'x-mac-ce'; Codepage: 10029),
|
|
(Name: 'x-mac-icelandic'; Codepage: 10079),
|
|
(Name: 'x-mac-turkish'; Codepage: 10081),
|
|
(Name: 'x-mac-croatian'; Codepage: 10082), //70
|
|
(Name: 'utf-32'; Codepage: 12000),
|
|
(Name: 'utf-32BE'; Codepage: 12001),
|
|
(Name: 'x-Chinese_CNS'; Codepage: 20000),
|
|
(Name: 'x-cp20001'; Codepage: 20001),
|
|
(Name: 'x_Chinese-Eten'; Codepage: 20002),
|
|
(Name: 'x-cp20003'; Codepage: 20003),
|
|
(Name: 'x-cp20004'; Codepage: 20004),
|
|
(Name: 'x-cp20005'; Codepage: 20005),
|
|
(Name: 'x-IA5'; Codepage: 20105),
|
|
(Name: 'x-IA5-German'; Codepage: 20106), //80
|
|
(Name: 'x-IA5-Swedish'; Codepage: 20107),
|
|
(Name: 'x-IA5-Norwegian'; Codepage: 20108),
|
|
(Name: 'us-ascii'; Codepage: 20127),
|
|
(Name: 'x-cp20261'; Codepage: 20261),
|
|
(Name: 'x-cp20269'; Codepage: 20269),
|
|
(Name: 'IBM273'; Codepage: 20273),
|
|
(Name: 'IBM277'; Codepage: 20277),
|
|
(Name: 'IBM278'; Codepage: 20278),
|
|
(Name: 'IBM280'; Codepage: 20280),
|
|
(Name: 'IBM284'; Codepage: 20284), //90
|
|
(Name: 'IBM285'; Codepage: 20285),
|
|
(Name: 'IBM290'; Codepage: 20290),
|
|
(Name: 'IBM297'; Codepage: 20297),
|
|
(Name: 'IBM420'; Codepage: 20420),
|
|
(Name: 'IBM423'; Codepage: 20423),
|
|
(Name: 'IBM424'; Codepage: 20424),
|
|
(Name: 'x-EBCDIC-KoreanExtended'; Codepage: 20833),
|
|
(Name: 'IBM-Thai'; Codepage: 20838),
|
|
(Name: 'koi8-r'; Codepage: 20866),
|
|
(Name: 'IBM871'; Codepage: 20871), //100
|
|
(Name: 'IBM880'; Codepage: 20880),
|
|
(Name: 'IBM905'; Codepage: 20905),
|
|
(Name: 'IBM00924'; Codepage: 20924),
|
|
(Name: 'EUC-JP'; Codepage: 20932),
|
|
(Name: 'x-cp20936'; Codepage: 20936),
|
|
(Name: 'x-cp20949'; Codepage: 20949),
|
|
(Name: 'cp1025'; Codepage: 21025),
|
|
(Name: 'koi8-u'; Codepage: 21866),
|
|
(Name: 'iso-8859-1'; Codepage: 28591),
|
|
(Name: 'iso-8859-2'; Codepage: 28592), //110
|
|
(Name: 'iso-8859-3'; Codepage: 28593),
|
|
(Name: 'iso-8859-4'; Codepage: 28594),
|
|
(Name: 'iso-8859-5'; Codepage: 28595),
|
|
(Name: 'iso-8859-6'; Codepage: 28596),
|
|
(Name: 'iso-8859-7'; Codepage: 28597),
|
|
(Name: 'iso-8859-8'; Codepage: 28598),
|
|
(Name: 'iso-8859-9'; Codepage: 28599),
|
|
(Name: 'iso-8859-13'; Codepage: 28603),
|
|
(Name: 'iso-8859-15'; Codepage: 28605),
|
|
(Name: 'x-Europa'; Codepage: 29001), //120
|
|
(Name: 'iso-8859-8-i'; Codepage: 38598),
|
|
(Name: 'iso-2022-jp'; Codepage: 50220),
|
|
(Name: 'csISO2022JP'; Codepage: 50221),
|
|
(Name: 'iso-2022-jp'; Codepage: 50222),
|
|
(Name: 'iso-2022-kr'; Codepage: 50225),
|
|
(Name: 'x-cp50227'; Codepage: 50227),
|
|
(Name: 'euc-jp'; Codepage: 51932),
|
|
(Name: 'EUC-CN'; Codepage: 51936),
|
|
(Name: 'euc-kr'; Codepage: 51949),
|
|
(Name: 'hz-gb-2312'; Codepage: 52936), //130
|
|
(Name: 'GB18030'; Codepage: 54936),
|
|
(Name: 'x-iscii-de'; Codepage: 57002),
|
|
(Name: 'x-iscii-be'; Codepage: 57003),
|
|
(Name: 'x-iscii-ta'; Codepage: 57004),
|
|
(Name: 'x-iscii-te'; Codepage: 57005),
|
|
(Name: 'x-iscii-as'; Codepage: 57006),
|
|
(Name: 'x-iscii-or'; Codepage: 57007),
|
|
(Name: 'x-iscii-ka'; Codepage: 57008),
|
|
(Name: 'x-iscii-ma'; Codepage: 57009),
|
|
(Name: 'x-iscii-gu'; Codepage: 57010), //140
|
|
(Name: 'x-iscii-pa'; Codepage: 57011),
|
|
(Name: 'utf-7'; Codepage: 65000),
|
|
(Name: 'utf-8'; Codepage: 65001));//143
|
|
|
|
// default charset names for TsdStringEncoding
|
|
cStringEncodingCharsetNames: array[TsdStringEncoding] of Utf8String =
|
|
('ansi',
|
|
'utf-8',
|
|
'unicodeFFFE',
|
|
'utf-16',
|
|
'utf-32BE',
|
|
'utf-32',
|
|
'ucs4_2143',
|
|
'ucs4_3412',
|
|
'ebcdic');
|
|
|
|
// default codecs for TsdStringEncoding if no codepage is given
|
|
cStringEncodingCodePages: array[TsdStringEncoding] of integer =
|
|
( 0 {ansi can be any codepage},
|
|
65001 {utf-8},
|
|
1201 {unicodeFFFE},
|
|
1200 {utf-16},
|
|
12001 {utf-32BE},
|
|
12000 {utf-32},
|
|
0 {no codepage for UCS4_2143},
|
|
0 {no codepage for UCS4_3412},
|
|
0 {ebcdic can be any codepage});
|
|
|
|
// all xml compare options
|
|
xcAll: TXmlCompareOptions = [xcNodeName, xcNodeType, xcNodeValue, xcAttribCount,
|
|
xcAttribNames, xcAttribValues, xcChildCount, xcChildNames, xcChildValues,
|
|
xcRecursive];
|
|
|
|
// "signature" that defines the binary XML file/stream
|
|
cBinaryXmlCookie: array[0..3] of AnsiChar = '$BXM';
|
|
|
|
{ TsdXmlParser }
|
|
|
|
type
|
|
|
|
// XML buffered parser. It buffers the source stream into
|
|
// a memory buffer of limited size and reads from the stream chunk-wise.
|
|
// This way, it can do string comparisons in memory, directly on the buffer.
|
|
TsdXmlParser = class(TDebugPersistent)
|
|
protected
|
|
FBomInfo: TsdBomInfo;
|
|
FSource: TStream;
|
|
FChunkSize: integer;
|
|
FRawBuffer: array of byte; // raw data buffer
|
|
FUtf8Buffer: array of AnsiChar; // utf8 data buffer
|
|
FEncoding: TsdStringEncoding;
|
|
FCodePage: integer;
|
|
FRawFirstIdx: integer;
|
|
FRawLastIdx: integer;
|
|
FUtf8FirstIdx: integer;
|
|
FUtf8CurrentIdx: integer;
|
|
FUtf8LastIdx: integer;
|
|
FUtf8BasePosition: int64;
|
|
FBaseLineNumber: int64;
|
|
FEndOfStream: boolean;
|
|
FLastChar0D: boolean;
|
|
FOnDebugOut: TsdDebugEvent;
|
|
function LoCase(Ch: AnsiChar): AnsiChar;
|
|
procedure IncCurrentIdxCheck(var BytesAvail: integer);
|
|
function ReadString(AIndex, ACount: integer): Utf8String;
|
|
function ReadNextChunk: integer;
|
|
procedure EncodeChunk;
|
|
function GetPosition: int64;
|
|
function GetLineNumber: int64;
|
|
procedure SetCodePage(const Value: integer);
|
|
public
|
|
constructor Create(ASource: TStream; AChunkSize: integer); virtual;
|
|
destructor Destroy; override;
|
|
property OnDebugOut: TsdDebugEvent read FOnDebugOut write FOnDebugOut;
|
|
property Owner: TDebugComponent read FOwner write FOwner;
|
|
// Call flush once in a while, to check if data can be flushed out. Flushing
|
|
// means that the part before the current pointer is removed and the bytes
|
|
// following are moved to position 0. It is only actually done when enough
|
|
// chunks are read, and the flushing happens chunk-wise.
|
|
procedure Flush;
|
|
// Is the stream from binary xml?
|
|
function IsBinaryXml: boolean;
|
|
// Make at least one byte available from current position
|
|
function MakeDataAvailable: integer;
|
|
// Get the next character from the stream
|
|
function NextChar: AnsiChar;
|
|
// Check if the stream at this position contains string S. If so, the stream
|
|
// will be positioned after, if not, it will remain where it is.
|
|
function CheckString(const S: Utf8String): boolean;
|
|
// Move one position back in the stream
|
|
procedure MoveBack;
|
|
// Read a string from the stream until Terminator is found. The string returned
|
|
// will be the part before Terminator, the stream is positioned after Terminator
|
|
function ReadStringUntil(const Terminator: Utf8String): Utf8String;
|
|
// Read a quoted string from the stream, return the unquoted string
|
|
function ReadQuotedString(AQuote: AnsiChar): Utf8String;
|
|
// Read a string from the stream until character AChar is encountered.
|
|
// var EOS will be True if the stream reached the end.
|
|
function ReadStringUntilChar(AChar: AnsiChar): Utf8String;
|
|
// The encoding detected in the source stream (valid after ReadBOM or after
|
|
// the declaration).
|
|
property Encoding: TsdStringEncoding read FEncoding write FEncoding;
|
|
// CodePage used in text processing
|
|
property CodePage: integer read FCodePage write SetCodePage;
|
|
// Position in the stream in bytes from the start.
|
|
property Position: int64 read GetPosition;
|
|
// Line number in the stream. Lines are detected by analysing the stream
|
|
// for occurances of #13 (CR). The line number is *calculated* when this
|
|
// property is read, so it should not be read very regularly.
|
|
property LineNumber: int64 read GetLineNumber;
|
|
// Is the end of the stream detected?
|
|
property EndOfStream: boolean read FEndOfStream;
|
|
|
|
// Special parser procedures to parse XML content.
|
|
|
|
// Read the next character, skip any blanks inbetween. Blanks are:
|
|
// #$09, #$0A, #$0D, #$20
|
|
function NextCharSkipBlanks(var Blanks: Utf8String): AnsiChar;
|
|
// Read BOM (Byte Order Mark) from the start of the file in order to detect which
|
|
// encoding is used.
|
|
procedure ReadBOM;
|
|
// Read an new tag from the stream (from the position afer "<")
|
|
function ReadOpenTag: TsdElementType;
|
|
// Read a string from the stream until a blank char, or a "/" or a ">" is
|
|
// encountered.
|
|
function ReadStringUntilBlankOrEndTag: Utf8String;
|
|
// Info from Byte Order Mark (BOM)
|
|
property BomInfo: TsdBomInfo read FBomInfo;
|
|
end;
|
|
|
|
// specialized buffered writer that obeys encoding and codepage
|
|
TsdXmlWriter = class(TsdBufferWriter)
|
|
private
|
|
FOwner: TDebugComponent;
|
|
FRawBuffer: array of byte;
|
|
FRawBufferSize: integer;
|
|
procedure DoDebugOut(Sender: TObject; WarnStyle: TsdWarnStyle; const AMessage: Utf8String);
|
|
public
|
|
FEncoding: TsdStringEncoding;
|
|
FCodePage: integer;
|
|
constructor Create(AOwner: TDebugComponent; ASource: TStream; AChunkSize: integer);
|
|
destructor Destroy; override;
|
|
// overridden Write for all supported encodings (ansi, utf8, utf16le, utf16be)
|
|
function Write(const Buffer; Count: Longint): Longint; override;
|
|
end;
|
|
|
|
{
|
|
NativeXmlNodes:
|
|
Parsing and writing methods for XML nodes:
|
|
Attribute, CDATA, CharData, Comment, Declaration, DocType,
|
|
DTD-AttList, DTD-Element, DTD-Entity, DTD-Notation, Element,
|
|
(processing) Instruction, QuotedText, StyleSheet and ancestors:
|
|
|
|
TXmlNode
|
|
TsdContainerNode
|
|
TsdElement
|
|
TsdDtdElement
|
|
TsdDtdAttList
|
|
TsdDtdEntity
|
|
TsdDtdNotation
|
|
TsdDocType
|
|
TsdDeclaration
|
|
TsdStyleSheet
|
|
TsdCharData
|
|
TsdQuotedText
|
|
TsdProcessingInstruction
|
|
TsdComment
|
|
TsdCData
|
|
TsdConditionalSection
|
|
TsdAttribute
|
|
}
|
|
|
|
// Forward declaration TsdAttribute (needed by TXmlNode)
|
|
TsdAttribute = class;
|
|
|
|
// Forward declaration TsdElement (needed by TXmlNode)
|
|
TsdElement = class;
|
|
|
|
// TXmlNode metaclass
|
|
TsdNodeClass = class of TXmlNode;
|
|
|
|
// Forward declaration of TNativeXml
|
|
TNativeXml = class;
|
|
|
|
// Forward declaration of TXmlNode
|
|
TXmlNode = class;
|
|
|
|
// Pass a function of this kind to TXmlNode.SortChildNodes. The function should
|
|
// return -1 if Node1 < Node2, 0 if Node1 = Node2 and 1 if Node1 > Node2.
|
|
TXmlNodeCompareFunction = function(Node1, Node2: TXmlNode): integer;
|
|
|
|
// TXmlNode is the ancestor for all nodes in the xml document. See TsdElement
|
|
// for the elements, TsdAttribute for the attributes.
|
|
TXmlNode = class(TDebugPersistent)
|
|
private
|
|
// inherited from TDebugPersistent: FOwner: TDebugComponent
|
|
function GetAttributeByName(const AName: Utf8String): TsdAttribute;
|
|
function GetAttributeValueByName(const AName: Utf8String): Utf8String;
|
|
function GetAttributeValueByNameWide(const AName: Utf8String): UnicodeString;
|
|
procedure SetAttributeValueByName(const AName, Value: Utf8String);
|
|
procedure SetAttributeValueByNameWide(const AName: Utf8String; const Value: UnicodeString);
|
|
function GetBinaryString: RawByteString;
|
|
procedure SetBinaryString(const Value: RawByteString);
|
|
function GetNameUnicode: UnicodeString;
|
|
procedure SetNameUnicode(const Value: UnicodeString);
|
|
function GetValueUnicode: UnicodeString;
|
|
procedure SetValueUnicode(const Value: UnicodeString);
|
|
function GetAttributes(Index: integer): TsdAttribute;
|
|
function GetAttributeName(Index: integer): Utf8String;
|
|
function GetAttributeValue(Index: integer): Utf8String;
|
|
procedure SetAttributeName(Index: integer; const Value: Utf8String);
|
|
procedure SetAttributeValue(Index: integer; const Value: Utf8String);
|
|
function GetAttributeValueAsInteger(Index: integer): integer;
|
|
procedure SetAttributeValueAsInteger(Index: integer; const Value: integer);
|
|
function GetWriteOnDefault: boolean;
|
|
procedure SetWriteOnDefault(const Value: boolean);
|
|
function GetName: Utf8String; virtual;
|
|
function GetValue: Utf8String; virtual;
|
|
procedure SetName(const Value: Utf8String); virtual;
|
|
procedure SetValue(const Value: Utf8String); virtual;
|
|
procedure DoProgress(Position: int64);
|
|
function GetParentNode(ADepth: integer): TXmlNode;
|
|
function GetEolStyle: TsdEolStyle;
|
|
function GetPreserveWhiteSpace: boolean;
|
|
function GetXmlFormat: TXmlFormatType;
|
|
procedure DoNodeNew(ANode: TXmlNode);
|
|
procedure DoNodeLoaded(ANode: TXmlNode);
|
|
function GetContent: Utf8String; virtual;
|
|
function GetDirectNodeCount: integer; virtual;
|
|
function GetContainerCount: integer; virtual;
|
|
function GetContainers(Index: integer): TXmlNode; virtual;
|
|
function GetElementCount: integer; virtual;
|
|
function GetElements(Index: integer): TsdElement; virtual;
|
|
function GetDocument: TNativeXml;
|
|
function GetSourcePos: int64;
|
|
protected
|
|
FParent: TXmlNode;
|
|
{$ifdef USETAGS}
|
|
FTag: pointer;
|
|
{$endif USETAGS}
|
|
{$ifdef SOURCEPOS}
|
|
FSourcePos: int64;
|
|
{$endif SOURCEPOS}
|
|
// string table lookup methods
|
|
function GetString(AID: integer): Utf8String;
|
|
function AddString(const S: Utf8String): integer;
|
|
function GetNodeCount: integer; virtual;
|
|
function GetAttributeCount: integer; virtual;
|
|
function GetNodes(Index: integer): TXmlNode; virtual;
|
|
class function EscapeString(const S: Utf8String): Utf8String;
|
|
class function ReplaceString(const S: Utf8String): Utf8String;
|
|
function GetIndent: Utf8String; virtual;
|
|
function GetEndOfLine: Utf8String; virtual;
|
|
function GetSeparator: Utf8String; virtual;
|
|
function NodeIndexByName(const AName: Utf8String): integer; virtual;
|
|
procedure WriteValue(const AName, AValue: Utf8String); virtual;
|
|
procedure WriteContent(S: TStream); virtual;
|
|
function CompareNodeName(const NodeName: Utf8String): integer;
|
|
function GetFullPath: Utf8String;
|
|
property WriteOnDefault: boolean read GetWriteOnDefault write SetWriteOnDefault;
|
|
function GetParentNodeName(ADepth: integer): Utf8String;
|
|
// Create a new node object. AOwner must be the TNativeXml that is
|
|
// going to hold this new node. Make sure to use the correct class when
|
|
// creating, e.g. TsdElement.Create(Owner) for an element.
|
|
constructor Create(AOwner: TComponent); virtual;
|
|
// Create a new TXmlNode with name AName. AOwner must be the TNativeXml
|
|
// that is going to hold this new node.
|
|
constructor CreateName(AOwner: TNativeXml; const AName: Utf8String); virtual;
|
|
// Create a new TXmlNode with name AName and UTF8String value AValue. AOwner
|
|
// must be the TNativeXml that is going to hold this new node.
|
|
constructor CreateNameValue(AOwner: TNativeXml; const AName, AValue: Utf8String); virtual;
|
|
public
|
|
// copy the data and subnodes from ANode; this node is cleared first
|
|
procedure CopyFrom(ANode: TObject); virtual;
|
|
// Create a node as a child of AParent. AOwner must be TNativeXml or descendant,
|
|
// AParent is TXmlNode
|
|
constructor CreateParent(AOwner: TComponent; AParent: TXmlNode); virtual;
|
|
constructor CreateParentNear(AOwner: TComponent; AParent, ANode: TXmlNode; IsBefore: boolean);
|
|
// for compat: assign to source XmlNode
|
|
procedure Assign(Source: TPersistent); override;
|
|
// Convert the Utf8String S to a UnicodeString
|
|
class function Utf8ToWide(const S: Utf8String): UnicodeString;
|
|
// Convert the UnicodeString W to an Utf8String
|
|
class function WideToUtf8(const W: UnicodeString): Utf8String;
|
|
// parse this node with parser P, result is the endnode and should be identical
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; virtual;
|
|
// write this node to stream S
|
|
procedure WriteStream(S: TStream); virtual;
|
|
// The element type
|
|
function ElementType: TsdElementType; virtual;
|
|
// name of the element type
|
|
function ElementTypeName: Utf8String;
|
|
// write the node to a Utf8String
|
|
function WriteToString: Utf8String;
|
|
// Pointer to the owner document NativeXml
|
|
property Document: TNativeXml read GetDocument;
|
|
{$ifdef USETAGS}
|
|
// Tag is a pointer value the developer can use at will. Tag does not get
|
|
// saved to the XML. Tag is often used to point to a GUI element. Tag can
|
|
// cast to anything other than 'pointer' but please be reminded that the size
|
|
// of a pointer can be platform-dependent (ie 32bit vs 64bit) and writing
|
|
// code depending on that is thus also platform-dependent.
|
|
property Tag: pointer read FTag write FTag;
|
|
{$endif USETAGS}
|
|
// SourcePos (int64) points to the position in the source file where the
|
|
// nodes text begins.
|
|
property SourcePos: int64 read GetSourcePos;
|
|
// Parent points to the parent node of the current XML node.
|
|
property Parent: TXmlNode read FParent;
|
|
// This function returns True if the node has no subnodes and no attributes,
|
|
// and if the node Name and value are empty.
|
|
function IsClear: boolean;
|
|
// clear the node
|
|
procedure Clear; virtual;
|
|
// recursively delete empty nodes
|
|
procedure DeleteEmptyNodes; virtual;
|
|
// Call Delete to delete this node completely from the parent node list. This
|
|
// call only succeeds if the node has a parent. It has no effect when called for
|
|
// the root node.
|
|
procedure Delete; virtual;
|
|
// This function returns True if the node has no subnodes and no attributes,
|
|
// and if the node value is empty.
|
|
function IsEmpty: boolean;
|
|
// Test whether ANode is equal to another node based on compare options. If
|
|
// MismatchNodes is provided, a list of mismatching subnodes is filled.
|
|
function IsEqualTo(ANode: TXmlNode; Options: TXmlCompareOptions;
|
|
MismatchNodes: TList = nil): boolean;
|
|
// Use this method to add an attribute with name AName and string value AValue
|
|
// to the node. AName and AValue must be UTF8 encoded.
|
|
procedure AttributeAdd(const AName, AValue: Utf8String); overload;
|
|
// Use this method to add the attribute AAttribute. AAttribute must be owned by
|
|
// the xml document beforehand.
|
|
procedure AttributeAdd(AAttribute: TsdAttribute); overload; virtual;
|
|
// Add an open array of TsdAttribute objects. Attributes must be owned by
|
|
// the xml document beforehand.
|
|
procedure AttributesAdd(Attributes: array of TsdAttribute);
|
|
// Clear all attributes from the current node.
|
|
procedure AttributesClear; virtual;
|
|
// Use this method to delete the attribute at Index in the list. Index must be
|
|
// equal or greater than 0, and smaller than AttributeCount. Using an index
|
|
// outside of that range has no effect.
|
|
procedure AttributeDelete(Index: integer);
|
|
// Clear all elements from the current node.
|
|
procedure ElementsClear; virtual;
|
|
// Use this method to delete the element at Index in the list. Index must be
|
|
// equal or greater than 0, and smaller than ElementCount. Using an index
|
|
// outside of that range has no effect.
|
|
procedure ElementDelete(Index: integer);
|
|
// Use this method to find the index of an attribute with name AName.
|
|
function AttributeIndexByName(const AName: Utf8String): integer; virtual;
|
|
// Add the node ANode to the nodelist. It will be added at the end, unless
|
|
// it is an attribute, in that case it will be added at the end of the current
|
|
// list of attributes. NodeAdd will set the parent of ANode to itself.
|
|
function NodeAdd(ANode: TXmlNode): integer; virtual;
|
|
// This function returns a pointer to the first subnode that has an attribute with
|
|
// name AttribName and value AttribValue. If ShouldRecurse = True (default), the
|
|
// function works recursively, using the depthfirst method.
|
|
function NodeByAttributeValue(const NodeName, AttribName, AttribValue: Utf8String;
|
|
ShouldRecurse: boolean = True): TXmlNode; overload;
|
|
{$ifdef D7UP}
|
|
function NodeByAttributeValue(const NodeName, AttribName: Utf8String;
|
|
const AttribValue: UnicodeString; ShouldRecurse: boolean = True): TXmlNode; overload;
|
|
{$endif D7UP}
|
|
// Return a reference to the first subnode in the nodelist that has name AName.
|
|
// If no subnodes with AName are found, the function returns nil.
|
|
function NodeByName(const AName: Utf8String): TXmlNode;
|
|
// Use this procedure to retrieve all nodes that have name AName. Pointers to
|
|
// these nodes are added to the list in AList. AList must be initialized
|
|
// before calling this procedure. If you use a TsdNodeList you don't need
|
|
// to cast the list items to TXmlNode.
|
|
procedure NodesByName(const AName: Utf8String; const AList: TList);
|
|
// Add an open array of TXmlNode objects. Nodes must be owned by the xml document
|
|
// beforehand.
|
|
procedure NodesAdd(Nodes: array of TXmlNode);
|
|
// Delete the subnode at Index. The node will also be freed, so do not free the
|
|
// node in the application.
|
|
procedure NodeDelete(Index: integer); virtual;
|
|
// Extract the subnode at Index. The node will not be freed.
|
|
function NodeExtract(ANode: TXmlNode): TXmlNode; virtual;
|
|
// Remove the subnode. The node will also be freed, so do not free the
|
|
// node in the application.
|
|
procedure NodeRemove(ANode: TXmlNode); virtual;
|
|
// Remove the line of the xml file that has the subnode ANode in it.
|
|
// The subnode and accompanying character data will be freed.
|
|
procedure NodeRemoveEx(ANode: TXmlNode); virtual;
|
|
// Call NodeIndexOf to get the index for ANode in the Nodes list. The first
|
|
// node in the list has index 0, the second item has index 1, and so on. If
|
|
// a node is not in the list, NodeIndexOf returns -1.
|
|
function NodeIndexOf(ANode: TXmlNode): integer; virtual;
|
|
// Insert the node ANode at location Index in the list. Make sure to honour
|
|
// the fact that attributes are also nodes, and should always be first in
|
|
// the list. You can find the number of attributes with AttributeCount.
|
|
procedure NodeInsert(Index: integer; ANode: TXmlNode); virtual;
|
|
// Switch position of the nodes at Index1 and Index2.
|
|
procedure NodeExchange(Index1, Index2: integer); virtual;
|
|
// This function returns a pointer to the first node with AName. If this node
|
|
// is not found, then it creates a new node with AName and returns its pointer.
|
|
function NodeFindOrCreate(const AName: Utf8String): TXmlNode; virtual;
|
|
// Create a new node with AName, add it to the subnode list, and return a
|
|
// pointer to it.
|
|
function NodeNew(const AName: Utf8String): TXmlNode; virtual;
|
|
// Create a new node with AName, and insert it into the subnode list at location
|
|
// Index, and return a pointer to it.
|
|
function NodeNewAtIndex(Index: integer; const AName: Utf8String): TXmlNode; virtual;
|
|
// Clear (and free) the complete list of subnodes.
|
|
procedure NodesClear; virtual;
|
|
// Find the first node which has name NodeName. Contrary to the NodeByName
|
|
// function, this function will search the whole subnode tree, using the
|
|
// DepthFirst method. It is possible to search for a full path too, e.g.
|
|
// FoundNode := MyNode.FindNode('/Root/SubNode1/SubNode2/ThisNode');
|
|
function FindNode(const NodeName: Utf8String): TXmlNode; virtual;
|
|
// Find all nodes which have name NodeName. Contrary to the NodesByName
|
|
// function, this function will search the whole subnode tree. If you use
|
|
// a TsdNodeList for the AList parameter, you don't need to cast the list
|
|
// items to TXmlNode.
|
|
procedure FindNodes(const NodeName: Utf8String; const AList: TList); virtual;
|
|
// Iterates the next sibling of Node
|
|
function NextSibling(ANode: TXmlNode): TXmlNode; virtual;
|
|
// Return the first subnode with AType, or nil if none
|
|
function FirstNodeByType(AType: TsdElementType): TXmlNode; virtual;
|
|
// Read TreeDepth to find out many nested levels there are for the current XML
|
|
// node. Root has a TreeDepth of zero.
|
|
function TreeDepth: integer;
|
|
// The name of the node. For elements this is the element name. The string
|
|
// is encoded as UTF8.
|
|
property Name: Utf8String read GetName write SetName;
|
|
// The name of the node. For elements this is the element name. The string
|
|
// is encoded as UTF8.
|
|
property NameUnicode: UnicodeString read GetNameUnicode write SetNameUnicode;
|
|
// The value of the node. For elements this is the element value (based on
|
|
// first chardata fragment), for attributes this is the attribute value. The
|
|
// string is encoded as UTF8. Use ToWide(Node.Value) or Node.ValueUnicode
|
|
// to get a UnicodeString compatible with "unicode" windows methods.
|
|
property Value: Utf8String read GetValue write SetValue;
|
|
// ValueUnicode returns the value of the node as a UnicodeString.
|
|
property ValueUnicode: UnicodeString read GetValueUnicode write SetValueUnicode;
|
|
// List of attributes present in this element. Use AttributeCount to iterate.
|
|
property Attributes[Index: integer]: TsdAttribute read GetAttributes;
|
|
// Get the number of attributes in this node
|
|
property AttributeCount: integer read GetAttributeCount;
|
|
// Get or set the name of the attribute at Index (as UTF8).
|
|
property AttributeName[Index: integer]: Utf8String read GetAttributeName write SetAttributeName;
|
|
// Get or set the value of the attribute at Index (as UTF8).
|
|
property AttributeValue[Index: integer]: Utf8String read GetAttributeValue write SetAttributeValue;
|
|
// Read this property to get the integer value of the attribute at index Index.
|
|
// If the value cannot be converted, 0 will be returned. Write to it to set the
|
|
// integer value.
|
|
property AttributeValueAsInteger[Index: integer]: integer read GetAttributeValueAsInteger write SetAttributeValueAsInteger;
|
|
// Get a reference to an attribute node by its name. If there is no attribute
|
|
// with that name, nil will be returned.
|
|
property AttributeByName[const AName: Utf8String]: TsdAttribute read GetAttributeByName;
|
|
// Get the value of an attribute with name AName. If no attribute is present,
|
|
// an empty string is returned. When setting this value, an attribute is
|
|
// created if it does not yet exist.
|
|
property AttributeValueByName[const AName: Utf8String]: Utf8String read
|
|
GetAttributeValueByName write SetAttributeValueByName;
|
|
property AttributeValueByNameWide[const AName: Utf8String]: UnicodeString read
|
|
GetAttributeValueByNameWide write SetAttributeValueByNameWide;
|
|
// Use HasAttribute to determine if the node has an attribute with name AName.
|
|
function HasAttribute(const AName: Utf8String): boolean; virtual;
|
|
// List of subnodes, by index. Iterate through the list using NodeCount
|
|
// and this property. The attributes are listed first, then followed by
|
|
// all other node types, in the order as found in the XML document.
|
|
property Nodes[Index: integer]: TXmlNode read GetNodes; default;
|
|
// Get number of sub-elements present in this node.
|
|
property ElementCount: integer read GetElementCount;
|
|
// List of sub-elements, by index.
|
|
property Elements[Index: integer]: TsdElement read GetElements;
|
|
// Get number of subnodes present in this node (this includes attributes,
|
|
// cdata, char-data, sub-elements, etcetera).
|
|
property NodeCount: integer read GetNodeCount;
|
|
// content of the node (raw source without the pre- and post matter)
|
|
property Content: Utf8String read GetContent;
|
|
// Fullpath will return the complete path of the node from the root, e.g.
|
|
// /Root/SubNode1/SubNode2/ThisNode
|
|
property FullPath: Utf8String read GetFullPath;
|
|
// direct node count (aka the attributes and optional whitespace inbetween)
|
|
property DirectNodeCount: integer read GetDirectNodeCount;
|
|
// (child) container count
|
|
property Containers[Index: integer]: TXmlNode read GetContainers;
|
|
property ContainerCount: integer read GetContainerCount;
|
|
|
|
// Get/Set ValueAsXYZ functions
|
|
|
|
// Convert the node's value to boolean and return the result. If this conversion
|
|
// fails, or no value is found, then the function returns ADefault.
|
|
function GetValueAsBoolDef(ADefault: boolean): boolean; virtual;
|
|
// Convert the node's value to a double and return the result. If this conversion
|
|
// fails, or no value is found, then the function returns ADefault.
|
|
function GetValueAsFloatDef(ADefault: double): double; virtual;
|
|
// Convert the node's value to a TDateTime and return the result. If this conversion
|
|
// fails, or no value is found, then the function returns ADefault.
|
|
function GetValueAsDateTimeDef(ADefault: TDateTime): TDateTime; virtual;
|
|
// Convert the node's value to integer and return the result. If this conversion
|
|
// fails, or no value is found, then the function returns ADefault.
|
|
function GetValueAsIntegerDef(ADefault: integer): integer; virtual;
|
|
// Convert the node's value to int64 and return the result. If this conversion
|
|
// fails, or no value is found, then the function returns ADefault.
|
|
function GetValueAsInt64Def(ADefault: int64): int64; virtual;
|
|
// Convert the node's value to boolean and return the result.
|
|
function GetValueAsBool: boolean; virtual;
|
|
// Convert the node's value to a double and return the result.
|
|
function GetValueAsFloat: double; virtual;
|
|
// Convert the node's value to a TDateTime and return the result.
|
|
function GetValueAsDateTime: TDateTime; virtual;
|
|
// Convert the node's value to integer and return the result.
|
|
function GetValueAsInteger: integer; virtual;
|
|
// Convert the node's value to int64 and return the result.
|
|
function GetValueAsInt64: int64; virtual;
|
|
// Store AValue as boolean
|
|
procedure SetValueAsBool(const AValue: boolean); virtual;
|
|
// Store AValue as float
|
|
procedure SetValueAsFloat(const AValue: double); virtual;
|
|
// Store AValue as Date
|
|
procedure SetValueAsDate(const AValue: TDateTime); virtual;
|
|
// Store AValue as Time
|
|
procedure SetValueAsTime(const AValue: TDateTime); virtual;
|
|
// Store AValue as DateTime
|
|
procedure SetValueAsDateTime(const AValue: TDateTime); virtual;
|
|
// Store AValue as Integer
|
|
procedure SetValueAsInteger(const AValue: integer); virtual;
|
|
// Store AValue as Int64
|
|
procedure SetValueAsInt64(const AValue: int64); virtual;
|
|
|
|
// ValueAsXYZ properties
|
|
|
|
// Read and store existent value as boolean
|
|
property ValueAsBool: boolean read GetValueAsBool write SetValueAsBool;
|
|
// Read and store existent value as float
|
|
property ValueAsFloat: double read GetValueAsFloat write SetValueAsFloat;
|
|
// Store existent value as Date
|
|
property ValueAsDate: TDateTime write SetValueAsDate;
|
|
// Store existent value as Time
|
|
property ValueAsTime: TDateTime write SetValueAsTime;
|
|
// Read and store existent value as DateTime
|
|
property ValueAsDateTime: TDateTime read GetValueAsDateTime write SetValueAsDateTime;
|
|
// Read and store existent value as Integer
|
|
property ValueAsInteger: integer read GetValueAsInteger write SetValueAsInteger;
|
|
// Read and store existent value as Int64
|
|
property ValueAsInt64: int64 read GetValueAsInt64 write SetValueAsInt64;
|
|
|
|
// ReadXYZ functions
|
|
|
|
// Find the attribute with AName, and convert its value to a boolean. If the
|
|
// attribute is not found, or cannot be converted, the default ADefault will
|
|
// be returned.
|
|
function ReadAttributeBool(const AName: Utf8String; ADefault: boolean = False): boolean; virtual;
|
|
// Find the attribute with AName, and convert its value to an integer. If the
|
|
// attribute is not found, or cannot be converted, the default ADefault will
|
|
// be returned.
|
|
function ReadAttributeInteger(const AName: Utf8String; ADefault: integer = 0): integer; virtual;
|
|
function ReadAttributeInt64(const AName: Utf8String; ADefault: int64 = 0): int64; virtual; // added by hdk
|
|
// Find the attribute with AName, and convert its value to a float. If the
|
|
// attribute is not found, or cannot be converted, the default ADefault will
|
|
// be returned.
|
|
function ReadAttributeFloat(const AName: Utf8String; ADefault: double = 0): double; virtual;
|
|
// Find the attribute with AName. If the attribute is not found, ADefault will
|
|
// be returned.
|
|
function ReadAttributeString(const AName: Utf8String; ADefault: Utf8String = ''): Utf8String; virtual;
|
|
function ReadAttributeUnicodeString(const AName: Utf8String; ADefault: UnicodeString = ''): UnicodeString; virtual; // added by hdk
|
|
function ReadAttributeAnsiString(const AName: Utf8String; ADefault: AnsiString = ''): AnsiString; virtual; // added by hdk
|
|
// Read the subnode with AName and convert it to a boolean value. If the
|
|
// subnode is not found, or cannot be converted, the boolean ADefault will
|
|
// be returned.
|
|
function ReadAttributeDateTime(const AName: Utf8String; ADefault: TDateTime = 0): TDateTime; virtual; // added by hdk
|
|
|
|
function ReadBool(const AName: Utf8String; ADefault: boolean = False): boolean; virtual;
|
|
{$ifdef USEGRAPHICS}
|
|
// Read the properties Color, Mode, Style and Width for the TPen object APen
|
|
// from the subnode with AName.
|
|
procedure ReadPen(const AName: Utf8String; APen: TPen); virtual;
|
|
// Read the properties Color and Style for the TBrush object ABrush from the
|
|
// subnode with AName.
|
|
procedure ReadBrush(const AName: Utf8String; ABrush: TBrush); virtual;
|
|
// Read the subnode with AName and convert its value to TColor. If the
|
|
// subnode is not found, or cannot be converted, ADefault will be returned.
|
|
function ReadColor(const AName: Utf8String; ADefault: TColor = clBlack): TColor; virtual;
|
|
{$endif USEGRAPHICS}
|
|
// Read the subnode with AName and convert its value to TDateTime. If the
|
|
// subnode is not found, or cannot be converted, ADefault will be returned.
|
|
function ReadDateTime(const AName: Utf8String; ADefault: TDateTime = 0): TDateTime; virtual;
|
|
// Read the subnode with AName and convert its value to a double. If the
|
|
// subnode is not found, or cannot be converted, ADefault will be returned.
|
|
function ReadFloat(const AName: Utf8String; ADefault: double = 0.0): double; virtual;
|
|
// Read the subnode with AName and convert its value to an integer. If the
|
|
// subnode is not found, or cannot be converted, ADefault will be returned.
|
|
function ReadInteger(const AName: Utf8String; ADefault: integer = 0): integer; virtual;
|
|
function ReadInt64(const AName: Utf8String; ADefault: int64 = 0): int64; virtual; // added by hdk
|
|
// Read the subnode with AName and return its UTF8String value. If the subnode is
|
|
// not found, ADefault will be returned.
|
|
function ReadString(const AName: Utf8String; const ADefault: Utf8String = ''): Utf8String; virtual;
|
|
// Read the subnode with AName and return its UnicodeString value. If the subnode is
|
|
// not found, ADefault will be returned.
|
|
function ReadUnicodeString(const AName: Utf8String; const ADefault: UnicodeString = ''): UnicodeString; virtual;
|
|
function ReadAnsiString(const AName: Utf8String; const ADefault: AnsiString = ''): AnsiString; virtual; // added by hdk
|
|
|
|
// WriteXYZ functions
|
|
|
|
// If the attribute with name AName exists, then set its value to the integer
|
|
// AValue. If it does not exist, then create a new attribute AName with the
|
|
// integer value converted to a quoted string. If ADefault = AValue, and
|
|
// WriteOnDefault = False, no attribute will be added.
|
|
procedure WriteAttributeInteger(const AName: Utf8String; AValue: integer; ADefault: integer = 0); virtual;
|
|
procedure WriteAttributeInt64(const AName: UTF8String; AValue: int64; ADefault: int64 = 0); virtual; // added by hdk
|
|
// If the attribute with name AName exists, then set its value to the float
|
|
// AValue. If it does not exist, then create a new attribute AName with the
|
|
// float value converted to a quoted string. If ADefault = AValue, and
|
|
// WriteOnDefault = False, no attribute will be added.
|
|
procedure WriteAttributeFloat(const AName: Utf8String; AValue: double; ADefault: double = 0); virtual;
|
|
// If the attribute with name AName exists, then set its value to the string
|
|
// AValue. If it does not exist, then create a new attribute AName with the
|
|
// string value with quotes. If ADefault = AValue, and
|
|
// WriteOnDefault = False, no attribute will be added.
|
|
procedure WriteAttributeString(const AName: Utf8String; AValue: Utf8String; ADefault: Utf8String = ''); virtual;
|
|
procedure WriteAttributeUnicodeString(const AName: Utf8String; const AValue: UnicodeString; const ADefault: UnicodeString = ''); virtual;
|
|
procedure WriteAttributeAnsiString(const AName: Utf8String; const AValue: AnsiString; const ADefault: AnsiString = ''); virtual; // added by hdk
|
|
// If the attribute with name AName exists, then set its value to the TDateTime
|
|
// AValue. If it does not exist, then create a new attribute AName with the
|
|
// TDateTime value converted to a quoted string. If ADefault = AValue, and
|
|
// WriteOnDefault = False, no attribute will be added.
|
|
procedure WriteAttributeDateTime(const AName: Utf8String; AValue: TDateTime; ADefault: TDateTime = 0); virtual; // changed by hdk
|
|
// If the attribute with name AName exists, then set its value to the boolean
|
|
// AValue. If it does not exist, then create a new attribute AName with the
|
|
// boolean value converted to a quoted string. If ADefault = AValue, and
|
|
// WriteOnDefault = False, no attribute will be added.
|
|
procedure WriteAttributeBool(const AName: Utf8String; AValue: boolean; ADefault: boolean = False); virtual;
|
|
// Add or replace the subnode with AName and set its value to represent the boolean
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
procedure WriteBool(const AName: Utf8String; AValue: boolean; ADefault: boolean = False); virtual;
|
|
{$ifdef USEGRAPHICS}
|
|
// Write properties Color, Mode, Style and Width of the TPen object APen to
|
|
// the subnode with AName. If AName does not exist, it will be created.
|
|
procedure WritePen(const AName: Utf8String; APen: TPen); virtual;
|
|
// Write properties Color and Style of the TBrush object ABrush to the subnode
|
|
// with AName. If AName does not exist, it will be created.
|
|
procedure WriteBrush(const AName: Utf8String; ABrush: TBrush); virtual;
|
|
// Add or replace the subnode with AName and set its value to represent the TColor
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
procedure WriteColor(const AName: Utf8String; AValue: TColor; ADefault: TColor = clBlack); virtual;
|
|
{$endif USEGRAPHICS}
|
|
// Add or replace the subnode with AName and set its value to represent the TDateTime
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
// The XML format used is compliant with W3C's specification of date and time.
|
|
procedure WriteDateTime(const AName: Utf8String; AValue: TDateTime; ADefault: TDateTime = 0); virtual;
|
|
// Add or replace the subnode with AName and set its value to represent the double
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
procedure WriteFloat(const AName: Utf8String; AValue: double; ADefault: double = 0.0); virtual;
|
|
// Add or replace the subnode with AName and set its value to represent the hexadecimal representation of
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
procedure WriteHex(const AName: Utf8String; AValue, Digits: integer; ADefault: integer = 0); virtual;
|
|
// Add or replace the subnode with AName and set its value to represent the integer
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
procedure WriteInteger(const AName: Utf8String; AValue: integer; ADefault: integer = 0); virtual;
|
|
// Add or replace the subnode with AName and set its value to represent the int64
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
procedure WriteInt64(const AName: Utf8String; AValue: int64; ADefault: int64 = 0); virtual;
|
|
// Add or replace the subnode with AName and set its value to represent the UTF8String
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
procedure WriteString(const AName, AValue: Utf8String; const ADefault: Utf8String = ''); virtual;
|
|
// Add or replace the subnode with AName and set its value to represent the UnicodeString
|
|
// AValue. If AValue = ADefault, and WriteOnDefault = False, no subnode will be added.
|
|
procedure WriteUnicodeString(const AName: Utf8String; const AValue: UnicodeString; const ADefault: UnicodeString = ''); virtual;
|
|
procedure WriteAnsiString(const AName: Utf8String; const AValue: AnsiString; const ADefault: AnsiString = ''); virtual; // added by hdk
|
|
|
|
// Returns the length of the data in the buffer, once it would be decoded by
|
|
// the Base64 method. The length of the unencoded data is determined from the
|
|
// length of the encoded data. Base64 must use the padding characters.
|
|
function BufferLength: integer; virtual;
|
|
// Use this method to read binary data from the node into Buffer with a length of Count.
|
|
procedure BufferRead(var Buffer; Count: Integer; BinaryEncoding: TsdBinaryEncoding = xbeBase64); virtual;
|
|
// Use this method to write binary data in Buffer with a length of Count to the
|
|
// current node. The data will appear as text using Base64 method in the final XML document.
|
|
procedure BufferWrite(const Buffer; Count: Integer); virtual;
|
|
// Use BinaryString to add/extract binary data in an easy way to/from the node. Internally the
|
|
// data gets stored as Base64-encoded data. Do not use this method for normal textual
|
|
// information, it is better to use ValueAsString in that case (adds less overhead).
|
|
property BinaryString: RawByteString read GetBinaryString write SetBinaryString;
|
|
// return the index of the node in its parent
|
|
function IndexInParent: integer;
|
|
// sort the child nodes based on a compare function. If Compare = nil, just
|
|
// alphabetical compare is used.
|
|
procedure SortChildNodes(Compare: TXmlNodeCompareFunction);
|
|
end;
|
|
|
|
// List of nodes
|
|
TsdNodeList = class(TObjectList)
|
|
private
|
|
function GetItems(Index: integer): TXmlNode;
|
|
function GetNextSiblingOf(ANode: TXmlNode): TXmlNode;
|
|
function GetLastSiblingOf(ANode: TXmlNode): TXmlNode;
|
|
public
|
|
// TsdNodeList has a different default than TObjectList
|
|
// since 'AOwnsObjects' should usually be false in client code
|
|
constructor Create(AOwnsObjects: boolean = false); virtual;
|
|
// ByType returns the first item in the list that has element type AType.
|
|
// If no item is found, the function returns nil.
|
|
function ByType(AType: TsdElementType): TXmlNode;
|
|
function FindFirst: TXmlNode;
|
|
function FindNext(ANode: TXmlNode): TXmlNode;
|
|
property Items[Index: integer]: TXmlNode read GetItems; default;
|
|
end;
|
|
|
|
TsdXmlNodeEvent = procedure(Sender: TObject; ANode: TXmlNode) of object;
|
|
|
|
// Node representing a xml char-data fragment
|
|
TsdCharData = class(TXmlNode)
|
|
private
|
|
function GetName: Utf8String; override;
|
|
function GetValue: Utf8String; override;
|
|
procedure SetName(const Value: Utf8String); override;
|
|
procedure SetValue(const Value: Utf8String); override;
|
|
protected
|
|
FValueID: integer; // core value ID
|
|
// the core value is the escaped, eol-normalized value
|
|
function GetCoreValue: Utf8String; virtual;
|
|
// PlatformValue is unnormalized CoreValue
|
|
function GetPlatformValue: Utf8String; virtual;
|
|
procedure SetCoreValue(const Value: Utf8String); virtual;
|
|
public
|
|
procedure CopyFrom(ANode: TObject); override;
|
|
destructor Destroy; override;
|
|
function IsWhiteSpace: boolean; virtual;
|
|
function GetValueUsingReferences(Nodes: array of TXmlNode): Utf8String;
|
|
function ElementType: TsdElementType; override;
|
|
function HasNonStandardReferences: boolean;
|
|
procedure WriteStream(S: TStream); override;
|
|
end;
|
|
|
|
// Node representing whitespace chardata
|
|
TsdWhiteSpace = class(TsdCharData)
|
|
public
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// Node representing quoted text ('bla' or "bla")
|
|
TsdQuotedText = class(TsdCharData)
|
|
private
|
|
FQuoteStyle: TsdQuoteCharStyle;
|
|
function GetName: Utf8String; override;
|
|
protected
|
|
public
|
|
procedure CopyFrom(ANode: TObject); override;
|
|
constructor Create(AOwner: TComponent); override;
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// Node representing an xml attribute.
|
|
TsdAttribute = class(TXmlNode)
|
|
private
|
|
FCoreValue: TsdQuotedText;
|
|
protected
|
|
FNameID: integer;
|
|
function GetName: Utf8String; override;
|
|
procedure SetName(const Value: Utf8String); override;
|
|
function GetValue: Utf8String; override;
|
|
procedure SetValue(const Value: Utf8String); override;
|
|
constructor Create(AOwner: TComponent); override;
|
|
public
|
|
procedure CopyFrom(ANode: TObject); override;
|
|
destructor Destroy; override;
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// TsdContainerNode is the base class for all element types that can have
|
|
// sub-nodes.
|
|
TsdContainerNode = class(TXmlNode)
|
|
private
|
|
FDirectNodeCount: integer;
|
|
FValueIndex: integer;
|
|
FNodeClosingStyle: TsdNodeClosingStyle;
|
|
protected
|
|
// list of subnodes: direct nodes first, then subelements
|
|
FNodes: TsdNodeList;
|
|
function ParseAttributeList(P: TsdXmlParser): AnsiChar; virtual;
|
|
function ParseFixStructuralErrors(const AEndTagName: Utf8String): TXmlNode;
|
|
// parse the element list; the result (endtag) should be this element
|
|
function ParseElementList(P: TsdXmlParser; const SupportedTags: TsdElementTypes): TXmlNode; virtual;
|
|
// parses the value in descendants TsdElement and TsdDocType
|
|
procedure ParseIntermediateData(P: TsdXmlParser); virtual;
|
|
function ParseQuotedTextList(P: TsdXmlParser): AnsiChar; virtual;
|
|
procedure WriteAttributeList(S: TStream; Count: integer); virtual;
|
|
function GetNodeCount: integer; override;
|
|
function GetNodes(Index: integer): TXmlNode; override;
|
|
function HasSubContainers: boolean; virtual;
|
|
property NodeList: TsdNodeList read FNodes;
|
|
// count of the attributes
|
|
function GetDirectNodeCount: integer; override;
|
|
function GetContainers(Index: integer): TXmlNode; override;
|
|
function GetContainerCount: integer; override;
|
|
function GetElements(Index: integer): TsdElement; override;
|
|
function GetElementCount: integer; override;
|
|
function GetNodeClosingStyle: TsdNodeClosingStyle; virtual;
|
|
constructor Create(AOwner: TComponent); override;
|
|
public
|
|
procedure CopyFrom(ANode: TObject); override;
|
|
destructor Destroy; override;
|
|
procedure Clear; override;
|
|
// Use this method to add the attribute AAttribute. AAttribute must be owned by
|
|
// the xml document beforehand.
|
|
procedure AttributeAdd(AAttribute: TsdAttribute); override;
|
|
// default node addition in a container node
|
|
function NodeAdd(ANode: TXmlNode): integer; override;
|
|
procedure NodeDelete(Index: integer); override;
|
|
function NodeExtract(ANode: TXmlNode): TXmlNode; override;
|
|
function NodeIndexOf(ANode: TXmlNode): integer; override;
|
|
procedure NodeInsert(Index: integer; ANode: TXmlNode); override;
|
|
procedure NodeInsertNear(ANode, AOther: TXmlNode; IsBefore: boolean);
|
|
procedure NodeExchange(Index1, Index2: integer); override;
|
|
procedure NodesClear; override;
|
|
function FirstNodeByType(AType: TsdElementType): TXmlNode; override;
|
|
function NextSibling(ANode: TXmlNode): TXmlNode; override;
|
|
property NodeClosingStyle: TsdNodeClosingStyle read GetNodeClosingStyle write FNodeClosingStyle;
|
|
end;
|
|
|
|
// Node representing an xml element.
|
|
TsdElement = class(TsdContainerNode)
|
|
private
|
|
FNameID: integer;
|
|
protected
|
|
function GetName: Utf8String; override;
|
|
function GetValue: Utf8String; override;
|
|
procedure SetName(const Value: Utf8String); override;
|
|
procedure SetValue(const Value: Utf8String); override;
|
|
procedure ParseIntermediateData(P: TsdXmlParser); override;
|
|
public
|
|
procedure CopyFrom(ANode: TObject); override;
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// Node representing an xml declaration, e.g. <?xml version="1.0"?>
|
|
TsdDeclaration = class(TsdContainerNode)
|
|
private
|
|
function GetEncoding: Utf8String;
|
|
function GetVersion: Utf8String;
|
|
procedure SetEncoding(const Value: Utf8String);
|
|
procedure SetVersion(const Value: Utf8String);
|
|
protected
|
|
function GetName: Utf8String; override;
|
|
public
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
function ElementType: TsdElementType; override;
|
|
property Version: Utf8String read GetVersion write SetVersion;
|
|
// encoding aka charset
|
|
property Encoding: Utf8String read GetEncoding write SetEncoding;
|
|
end;
|
|
|
|
// Node representing an xml comment. Get/set Value for the comment.
|
|
TsdComment = class(TsdCharData)
|
|
protected
|
|
function GetName: Utf8String; override;
|
|
public
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// Node representing a CData element. Get/Set value for the data in CDATA.
|
|
TsdCData = class(TsdComment)
|
|
protected
|
|
function GetName: Utf8String; override;
|
|
function GetValue: Utf8String; override;
|
|
procedure SetValue(const Value: Utf8String); override;
|
|
public
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// Conditional Section (todo)
|
|
TsdConditionalSection = class(TsdComment)
|
|
end;
|
|
|
|
// DocType declaration element. It can have sub-nodes with dtd elements,
|
|
// entities, notations, etc.
|
|
TsdDocType = class(TsdContainerNode)
|
|
private
|
|
FNameID: integer;
|
|
FExternalID: TsdCharData;
|
|
FSystemLiteral: TsdQuotedText;
|
|
FPubIDLiteral: TsdQuotedText;
|
|
protected
|
|
constructor Create(AOwner: TComponent); override;
|
|
function GetName: Utf8String; override;
|
|
procedure SetName(const Value: Utf8String); override;
|
|
procedure ParseIntermediateData(P: TsdXmlParser); override;
|
|
public
|
|
procedure CopyFrom(ANode: TObject); override;
|
|
destructor Destroy; override;
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
function ElementType: TsdElementType; override;
|
|
// External ID: either SYSTEM or PUBLIC
|
|
property ExternalID: TsdCharData read FExternalID;
|
|
// The system literal without quotes
|
|
property SystemLiteral: TsdQuotedText read FSystemLiteral;
|
|
// The PubID literal without quotes
|
|
property PubIDLiteral: TsdQuotedText read FPubIDLiteral;
|
|
end;
|
|
|
|
// DTD Element declaration
|
|
TsdDtdElement = class(TsdElement)
|
|
protected
|
|
function GetValue: Utf8String; override;
|
|
procedure ParseContent(P: TsdXmlParser); virtual;
|
|
procedure WriteContent(S: TStream); override;
|
|
public
|
|
function ElementType: TsdElementType; override;
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
end;
|
|
|
|
// DTD AttList declaration
|
|
TsdDtdAttList = class(TsdDtdElement)
|
|
public
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// DTD Entity declaration
|
|
TsdDtdEntity = class(TsdDtdElement)
|
|
protected
|
|
procedure ParseContent(P: TsdXmlParser); override;
|
|
public
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// DTD Notation declaration
|
|
TsdDtdNotation = class(TsdDtdElement)
|
|
public
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// (processing) instruction
|
|
TsdInstruction = class(TsdCharData)
|
|
protected
|
|
function GetName: Utf8String; override;
|
|
public
|
|
function ElementType: TsdElementType; override;
|
|
function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
end;
|
|
|
|
// TsdStyleSheet
|
|
TsdStyleSheet = class(TsdDeclaration)
|
|
protected
|
|
function GetName: Utf8String; override;
|
|
public
|
|
//function ParseStream(P: TsdXmlParser): TXmlNode; override;
|
|
procedure WriteStream(S: TStream); override;
|
|
function ElementType: TsdElementType; override;
|
|
end;
|
|
|
|
// Forward declaration of TsdBinaryXml
|
|
TsdBinaryXml = class;
|
|
|
|
// TNativeXml is a fast XML parser (parsing on typical hardware storage
|
|
// 15 Mb per second), because it loads external data in chunks and buffers it in
|
|
// memory. Use Create to create a new instance, use LoadFromFile/LoadFromStream to
|
|
// load the XML document from a file or stream, and use SaveToFile and SaveToStream to
|
|
// save the XML document.
|
|
TNativeXml = class(TDebugComponent)
|
|
private
|
|
// inherited from TDebugComponent: FOnDebugOut: TsdDebugEvent;
|
|
function GetOrCreateDeclarationNode: TXmlNode; virtual;
|
|
function GetCharset: Utf8String;
|
|
function GetPreserveWhitespace: boolean;
|
|
procedure SetCharset(const Value: Utf8String);
|
|
procedure SetBinaryDocument(ABinaryXml: TsdBinaryXml);
|
|
procedure SetBinaryMethod(const Value: TsdXmlBinaryMethod);
|
|
procedure SetPreserveWhiteSpace(const Value: boolean);
|
|
procedure SetExternalEncoding(const Value: TsdStringEncoding);
|
|
procedure SetExternalCodepage(const Value: integer);
|
|
procedure SetXmlFormat(const Value: TXmlFormatType);
|
|
{$ifdef USEZLIB}
|
|
function ZlibEncode(SIn, SOut: TStream; CodecSize: int64): Utf8String;
|
|
function ZlibDecode(SIn, SOut: TStream; PlainSize: int64): Utf8String;
|
|
{$ifdef USEAES}
|
|
function AeszEncode(SIn, SOut: TStream; CodecSize: int64): Utf8String;
|
|
function AeszDecode(SIn, SOut: TStream; PlainSize: int64): Utf8String;
|
|
{$endif USEAES}
|
|
{$endif USEZLIB}
|
|
protected
|
|
FRootNodes: TsdNodeList;
|
|
FSymbolTable: TsdSymbolTable;
|
|
|
|
// options
|
|
FAbortParsing: boolean;
|
|
FDirectCloseTag: Utf8String;
|
|
FDropCommentsOnParse: boolean;
|
|
FEolStyle: TsdEolStyle;
|
|
FFloatAllowScientific: boolean;
|
|
FFloatSignificantDigits: integer;
|
|
FExternalBomInfo: TsdBomInfo;
|
|
FExternalCodePage: integer;
|
|
FExternalEncoding: TsdStringEncoding;
|
|
FFixStructuralErrors: boolean;
|
|
FHasDeclaration: boolean;
|
|
FHasDocType: boolean;
|
|
FHasRoot: boolean;
|
|
FIndentString: Utf8String;
|
|
FNodeClosingStyle: TsdNodeClosingStyle;
|
|
FParserWarnings: boolean;
|
|
FRootName: Utf8String;
|
|
FSplitSecondDigits: integer;
|
|
FXmlFormat: TXmlFormatType;
|
|
FUseLocalBias: boolean;
|
|
FWriteOnDefault: boolean;
|
|
FBinaryMethod: TsdXmlBinaryMethod;
|
|
FAesKeyHex: Utf8String; // optional encryption key string in hex
|
|
FSingleTagNames: TStringList; // used in FixStructuralErrors
|
|
|
|
// events
|
|
FOnNodeNew: TsdXmlNodeEvent;
|
|
FOnNodeLoaded: TsdXmlNodeEvent;
|
|
FOnProgress: TXmlProgressEvent;
|
|
procedure ClearData(AHasDeclaration, AHasDocType, AHasRoot: boolean);
|
|
procedure DoNodeNew(ANode: TXmlNode);
|
|
procedure DoNodeLoaded(ANode: TXmlNode);
|
|
|
|
// GetParserPosition gives the parser's current position in the stream when
|
|
// loading.
|
|
function GetParserPosition(P: TsdXmlParser): int64;
|
|
function GetCommentString: Utf8String;
|
|
procedure SetCommentString(const Value: Utf8String);
|
|
function GetStyleSheet: TsdStyleSheet;
|
|
function GetDeclaration: TsdDeclaration;
|
|
function GetDocType: TsdDocType;
|
|
function GetRoot: TsdElement;
|
|
function GetRootNodeCount: integer;
|
|
function GetRootNodeClass: TsdNodeClass; virtual;
|
|
function GetRootContainers(Index: integer): TsdContainerNode; virtual;
|
|
function GetRootContainerCount: integer; virtual;
|
|
function GetVersionString: Utf8String;
|
|
procedure SetVersionString(const Value: Utf8String);
|
|
|
|
// GetParserLineNumber gives the parser's current line number in the stream
|
|
// when loading.
|
|
function GetParserLineNumber(P: TsdXmlParser): int64;
|
|
procedure MoveSubNodes(AList: TsdNodeList; FromNode, ToNode: TXmlNode);
|
|
procedure DoProgress(Position: int64);
|
|
function LineFeed: Utf8String;
|
|
|
|
// ParseStream is called from any of the XmlNode descendants
|
|
// and is the core method to get the xml data from external data to
|
|
// the document object model.
|
|
procedure ParseStream(Parser: TsdXmlParser);
|
|
|
|
// reset the defaults
|
|
procedure ResetDefaults;
|
|
|
|
// WriteStream is called from any of the XmlNode descendants
|
|
// and is the core method to write the xml data to the stream
|
|
procedure WriteStream(S: TStream);
|
|
public
|
|
|
|
// constructors
|
|
|
|
// Create an xml document with options for declaration, root element and root name.
|
|
constructor CreateEx(AOwner: TComponent; HasDeclaration, HasDocType, HasRoot: boolean; ARootName: Utf8String);
|
|
// Use CreateName to Create a new Xml document that will automatically
|
|
// contain a root element with name ARootName. This constructor also adds
|
|
// the default declaration. With default AOwner = nil, there is backward
|
|
// compatibility with legacy NativeXml
|
|
constructor CreateName(const ARootName: Utf8String; AOwner: TComponent = nil);
|
|
// constructor with just the root element with an empty name
|
|
constructor Create(AOwner: TComponent); override;
|
|
// Destroys a TNativeXml instance
|
|
destructor Destroy; override;
|
|
|
|
// general methods
|
|
|
|
// canonicalize XML (C14N process): after canonicalization of the document,
|
|
// it will be.. encoded in utf-8 only, xml declaration removed, entities
|
|
// expanded to their character equivalent, CDATA sections replaced by character
|
|
// equivalent, special < > and " entities encoded, attributes
|
|
// normalized as if by validating parser, empty elements opened with start
|
|
// and end tags, namespace declarations and attributes sorted.
|
|
// The function returns the number of entities expanded.
|
|
function Canonicalize: integer;
|
|
// Clear all the nodes in the xml document
|
|
procedure Clear; virtual;
|
|
// class method: Decode base64-encoded data (Utf8String) to binary data (RawByteString)
|
|
class function DecodeBase64(const Source: Utf8String; OnDebug: TsdDebugEvent): RawByteString;
|
|
// class method: encode binary data (RawByteString) to Utf8String, adding a
|
|
// control character (default #$0A) each 76 characters
|
|
class function EncodeBase64(const Source: RawByteString; const ControlChars: Utf8String = #$0A): Utf8String;
|
|
// Find first TXmlNode instance in the document, or nil if none found (aka document is empty)
|
|
function FindFirst: TXmlNode;
|
|
// Find next TXmlNode instance in the document, based on previous TXmlNode instance ANode
|
|
function FindNext(ANode: TXmlNode): TXmlNode;
|
|
// fire AEvent for each node in the document
|
|
procedure ForEach(Sender: TObject; AEvent: TsdXmlNodeEvent);
|
|
// IndentString is the string used for indentations. By default, it is a
|
|
// tab (#$09). Set IndentString to something else if you need to have
|
|
// specific indentation, or set it to an empty string to avoid indentation.
|
|
property IndentString: Utf8String read FIndentString write FIndentString;
|
|
// Function IsEmpty returns true if the root is clear, or in other words, the
|
|
// root contains no value, no name, no subnodes and no attributes.
|
|
function IsEmpty: boolean;
|
|
// load from binary xml file (bxm). The advisory file extension is *.BXM
|
|
procedure LoadFromBinaryFile(const AFileName: string); virtual;
|
|
// load from binary xml stream (bxm)
|
|
procedure LoadFromBinaryStream(AStream: TStream); virtual;
|
|
// load the xml from a URL, and return the loaded size in bytes
|
|
function LoadFromURL(const URL: Utf8String): int64; virtual;
|
|
// Call procedure LoadFromFile to load an XML document from the filename
|
|
// specified. See Create for an example. The LoadFromFile procedure will raise
|
|
// an exception when it encounters non-wellformed XML.
|
|
procedure LoadFromFile(const AFileName: string); virtual;
|
|
// Load an XML document from the stream AStream. The LoadFromStream
|
|
// procedure will raise an exception when it encounters non-wellformed XML.
|
|
// This method can be used with any TStream descendant. The stream is read
|
|
// chunk-wise (using 64K chunks). See also LoadFromFile and ReadFromString.
|
|
procedure LoadFromStream(AStream: TStream); virtual;
|
|
// Use New to make a new xml document
|
|
procedure New; virtual;
|
|
// parse substitute content from ANode (usually a TsdCharData). ANode will be
|
|
// removed and the substitute content gets parsed and becomes part of the object model.
|
|
function ParseSubstituteContentFromNode(ANode: TXmlNode; const ASubstitute: Utf8String): TXmlNode;
|
|
// Call procedure ReadFromString to load an XML document from the UTF8String AValue.
|
|
// The ReadFromString procedure will raise an exception of type EFilerError
|
|
// when it encounters non-wellformed XML.
|
|
procedure ReadFromString(const AValue: Utf8String); virtual;
|
|
// Remove whitespace chardata in nodes in order to compact the document. This
|
|
// method is auto called when changing the XmlFormat: from xfPreserve to
|
|
// xfCompact or xfReadable
|
|
procedure RemoveWhitespace;
|
|
// Call SaveToFile to save the XML document to a file with FileName. If the
|
|
// filename exists, it will be overwritten without warning. If the file cannot
|
|
// be created, a standard I/O exception will be generated. Set XmlFormat to
|
|
// xfReadable if you want the file to contain indentations to make the XML
|
|
// more human-readable. This is not the default and also not compliant with
|
|
// the XML specification.
|
|
procedure SaveToFile(const AFileName: string); virtual;
|
|
// Call SaveToStream to save the XML document to the Stream. Stream
|
|
// can be any TStream descendant. Set XmlFormat to xfReadable if you want
|
|
// the stream to contain indentations to make the XML more human-readable. This
|
|
// is not the default and also not compliant with the XML specification. See
|
|
// SaveToFile for information on how to save in special encoding.
|
|
procedure SaveToStream(Stream: TStream); virtual;
|
|
// Call SaveToBinaryFile to save XML to file in binary format. The advisory
|
|
// file extension is *.BXM
|
|
procedure SaveToBinaryFile(const AFileName: string); virtual;
|
|
// Call SaveToBinaryStream to save XML to stream in binary format (*.bxm)
|
|
procedure SaveToBinaryStream(Stream: TStream); virtual;
|
|
// Call WriteToString to write the entire XML document stream including
|
|
// optional BOM to a generic string.
|
|
function WriteToString: string; virtual;
|
|
// Call WriteToLocalString to write the XML document to a Utf8String.
|
|
function WriteToLocalString: Utf8String; virtual;
|
|
// Call WriteToLocalUnicodeString to write the XML document to a UnicodeString.
|
|
function WriteToLocalUnicodeString: UnicodeString; virtual;
|
|
|
|
// properties
|
|
|
|
// optional encryption key string in hex, must be hexadecimal notation of
|
|
// 16 bytes (ie 32 characters). Default is '00000000000000000000000000000000'.
|
|
// Workflow:
|
|
// - first make sure {$define USEAES} is defined!
|
|
// - set AesKeyHex := <your 32-char hexadecimal key>
|
|
// - Set BinaryMethod := bmAesz
|
|
// - to save and encrypt, use SaveToBinaryStream
|
|
// - to load and decrypt, use LoadFromBinaryStream
|
|
property AesKeyHex: Utf8String read FAesKeyHex write FAesKeyHex;
|
|
// Binary XML method: bmDefault (no compression), bmZlib (zlib compression)
|
|
// or bmAesz (AES encryption plus zlib compression)
|
|
property BinaryMethod: TsdXmlBinaryMethod read FBinaryMethod write SetBinaryMethod;
|
|
// Declaration is the xml declaration node. If present, it is the topmost node
|
|
property Declaration: TsdDeclaration read GetDeclaration;
|
|
// DeoctType is the Doctype Definition node (DTD). If present, it comes
|
|
// after the declaration
|
|
property DocType: TsdDocType read GetDocType;
|
|
// Root is the topmost element in the XML document. Access Root to read any
|
|
// child elements. When creating a new XML document, you can automatically
|
|
// include a Root element, by creating using CreateEx or CreateName.
|
|
property Root: TsdElement read GetRoot;
|
|
// RootNodes can be used to directly access the nodes in the root of the
|
|
// XML document. Usually this list consists of one declaration node followed
|
|
// by an element node which is the Root. You can use this property to add or
|
|
// delete comments, stylesheets, dtd's etc.
|
|
property RootNodes: TsdNodeList read FRootNodes;
|
|
// Payload rootnode class (TsdElement by default, but apps may create
|
|
// a class that descends from TsdElement)
|
|
property RootNodeClass: TsdNodeClass read GetRootNodeClass;
|
|
// item count of the RootNodeList, ie usually max 3: the declaration, the DTD,
|
|
// the Root (TsdElement or RootNodeClass descendant).
|
|
property RootNodeCount: integer read GetRootNodeCount;
|
|
// root containers
|
|
property RootContainers[Index: integer]: TsdContainerNode read GetRootContainers;
|
|
// number of root containers (as opposed to all root nodes)
|
|
property RootContainerCount: integer read GetRootContainerCount;
|
|
// A comment string above the root element <!--{comment}--> can be accessed with
|
|
// this property. Assign a comment to this property to add it to the XML document.
|
|
// Use property RootNodes to add/insert/extract multiple comments.
|
|
property CommentString: Utf8String read GetCommentString write SetCommentString;
|
|
// Set DropCommentsOnParse if you're not interested in any comment nodes in your object
|
|
// model data. All comments encountered during parsing will simply be skipped and
|
|
// not added as a node with ElementType = xeComment (which is default). Note that
|
|
// when you set this option, you cannot later reconstruct an XML file with the comments
|
|
// back in place.
|
|
property DropCommentsOnParse: boolean read FDropCommentsOnParse write FDropCommentsOnParse;
|
|
// After reading, this property contains the XML version (usually '1.0').
|
|
property VersionString: Utf8String read GetVersionString write SetVersionString;
|
|
// Charset (e.g. 'utf-8', 'utf-16' or any other multibyte/ansi codepage description.
|
|
// This charset description is stored in the declaration node.
|
|
// Example: In order to get this header:
|
|
// <?xml version="1.0" encoding="utf-16"?>
|
|
// enter this code:
|
|
// <CODE>MyXmlDocument.Charset := 'utf-16';</CODE>
|
|
// When reading a file, Charset will contain the encoding used.
|
|
property Charset: Utf8String read GetCharset write SetCharset;
|
|
// SymbolTable holds all the content (strings, base64binary, hexbinary,
|
|
// date, datetime) in the xml tree
|
|
property SymbolTable: TsdSymbolTable read FSymbolTable;
|
|
// Get the stylesheet used for this XML document. If the node does not
|
|
// exist yet, it will be created. TsdStyleSheet exists for backwards compatibility;
|
|
// StyleSheet is deprecated in the xml spec.
|
|
property StyleSheet: TsdStyleSheet read GetStyleSheet;
|
|
// External encoding is valid after loading, and indicates the encoding
|
|
// detected in the external xml document. Internally, all string values are always
|
|
// encoded in UTF8, so if the external stream is Ansi with codepage or UTF16, a conversion
|
|
// is done. When writing to a file/stream, a BOM is generated for the two-byte
|
|
// character encodings (UTF16LE and UUTF16BE). UTF8 uses *no BOM* according to
|
|
// the XML specification.
|
|
// Any conversion is done from UTF8 to external encodings if necessary. You can
|
|
// *set* ExternalEncoding too but only for welldefined encodings (seUTF8, seUTF16LE,
|
|
// seUTF16BE). If you want to use an ansi encoding, then set ExternalCodepage.
|
|
property ExternalEncoding: TsdStringEncoding read FExternalEncoding write SetExternalEncoding;
|
|
// the codepage used in the external xml document
|
|
property ExternalCodepage: integer read FExternalCodepage write SetExternalCodepage;
|
|
// if ncUnknown (default), parsed setting will be preserved per element.
|
|
// if ncFull, single tags will be left full (eg '<bla x="1"></bla>').
|
|
// if ncClose , single tags will be closed (eg '<bla x="1"/>').
|
|
property NodeClosingStyle: TsdNodeClosingStyle read FNodeClosingStyle write FNodeClosingStyle;
|
|
// XmlFormat by default is set to xfCompact. This setting is compliant to the spec,
|
|
// and NativeXml will not generate any more characters than necessary.
|
|
// By setting XmlFormat to xfReadable, you can generate readable XML
|
|
// files that contain indentation and end-of-lines after each element.
|
|
// By setting XmlFormat to xfPreserve, NativeXml preserves all the markup, no
|
|
// matter what, as the user/app typed/intended it. Note that setting xfPreserve
|
|
// may cause slightly more additional nodes being added in the xml when the
|
|
// document is parsed. If you switch to xfCompact or xfReadable after parsing,
|
|
// (or switch to xfPreserve too late, after parsing) all the markup is lost
|
|
// and cannot be retrieved.
|
|
property XmlFormat: TXmlFormatType read FXmlFormat write SetXmlFormat;
|
|
// EolStyle by default is set to esCRLF for Windows and esLF for non-Windows.
|
|
// - esLF (formerly esLinux) writes just a LF (#$0A) as end-of-line
|
|
// - esCRLF (formerly esWindows) writes a CRLF (#$0D#$0A) as end-of-line
|
|
// - esCR (used for Mac) writes a CR (#$0D) as end-of-line
|
|
// Please note that the esCRLF is the default in Windows. However,
|
|
// since it is not the default in normalized xml and the un-normalization
|
|
// adds an overhead in e.g. base64 processing of large binary chunks of data,
|
|
// it is best to avoid the esCRLF option, if not absolutely
|
|
// necessary. Many text processors work flawlessly with just LF as end-of-line,
|
|
// except Windows Notepad. CR-LF is a relic of the past, where mechanic
|
|
// typewriters would slam the carriage to the base position, then feed a line.
|
|
property EolStyle: TsdEolStyle read FEolStyle write FEolStyle;
|
|
// OnProgress event
|
|
property OnProgress: TXmlProgressEvent read FOnProgress write FOnProgress;
|
|
// Set PreserveWhiteSpace to True to preserve all whitespace present in the
|
|
// file when parsing. The blocks of whitespace are stored as CharData nodes.
|
|
// This sets XmlFormat := xfPreserve under the hood.
|
|
property PreserveWhiteSpace: boolean read GetPreserveWhiteSpace write SetPreserveWhiteSpace;
|
|
// Set AbortParsing to True if you use the OnNodeNew and OnNodeLoaded events in
|
|
// a SAX-like manner, and you want to abort the parsing process halfway.
|
|
property AbortParsing: boolean read FAbortParsing write FAbortParsing;
|
|
// when true, NativeXmlEx will try to fix certain structural errors in non-valid
|
|
// xml that usually come from single tags in html (default = False)
|
|
property FixStructuralErrors: boolean read FFixStructuralErrors write FFixStructuralErrors;
|
|
// Set WriteOnDefault to False if you do not want to write default values to
|
|
// the XML document. This option can avoid creating huge documents with
|
|
// redundant info, and will speed up writing.
|
|
property WriteOnDefault: boolean read FWriteOnDefault write FWriteOnDefault;
|
|
// When converting floating point values to strings (e.g. in WriteFloat),
|
|
// NativeXml will allow to output scientific notation in some cases, if the
|
|
// result is significantly shorter than normal output, but only if the value
|
|
// of FloatAllowScientific is True (default).
|
|
property FloatAllowScientific: boolean read FFloatAllowScientific write FFloatAllowScientific;
|
|
// When converting floating point values to strings (e.g. in WriteFloat),
|
|
// NativeXml will use this number of significant digits. The default is
|
|
// cDefaultFloatSignificantDigits, and set to 6.
|
|
property FloatSignificantDigits: integer read FFloatSignificantDigits write FFloatSignificantDigits;
|
|
// When converting date/time values to strings, NativeXml will use this
|
|
// number of digits after the seconds. The default is cDefaultSplitSecondDigits,
|
|
// and set to 0. With this default, no tens/hundreds/thousands after the second are used
|
|
property SplitSecondDigits: integer read FSplitSecondDigits write FSplitSecondDigits;
|
|
// When converting date/time values to strings, NativeXml will use a local bias
|
|
// towards UTC if this option is True. Default is False.
|
|
property UseLocalBias: boolean read FUseLocalBias write FUseLocalBias;
|
|
// Connect to OnNodeNew to get informed of new nodes being added while loading.
|
|
property OnNodeNew: TsdXmlNodeEvent read FOnNodeNew write FOnNodeNew;
|
|
// Connect to OnNodeLoaded to get informed of nodes being finished loading.
|
|
property OnNodeLoaded: TsdXmlNodeEvent read FOnNodeLoaded write FOnNodeLoaded;
|
|
// Connect to OnDebugOut to get debug information in the client application
|
|
property OnDebugOut: TsdDebugEvent read FOnDebugOut write FOnDebugOut;
|
|
|
|
// some more added methods in a LINQ-like way:
|
|
// attributes
|
|
function AttrText(AName, AValue: Utf8String): TsdAttribute;
|
|
function AttrInt(AName: Utf8String; AValue: integer): TsdAttribute;
|
|
function AttrInt64(AName: Utf8String; AValue: int64): TsdAttribute;
|
|
function AttrHex(AName: Utf8String; AValue, ADigits: integer): TsdAttribute; overload;
|
|
function AttrHex(AName: Utf8String; AValue: int64; ADigits: integer): TsdAttribute; overload;
|
|
function AttrFloat(AName: Utf8String; AValue: double): TsdAttribute; overload;
|
|
function AttrFloat(AName: Utf8String; AValue: double; ASignificantDigits: integer;
|
|
AAllowScientific: boolean): TsdAttribute; overload;
|
|
function AttrDateTime(AName: Utf8String; AValue: TDateTime): TsdAttribute;
|
|
function AttrBool(AName: Utf8String; AValue: boolean): TsdAttribute;
|
|
|
|
// container nodes
|
|
function NodeNew(AName: Utf8String): TXmlNode; overload; virtual;
|
|
function NodeNew(AName: Utf8String; SubNodes: array of TXmlNode): TXmlNode; overload; virtual;
|
|
function NodeNewEx(AName: Utf8String; out AXmlNode: TXmlNode): TXmlNode; overload;
|
|
function NodeNewEx(AName: Utf8String; out AXmlNode: TXmlNode; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
// string nodes
|
|
function NodeNewText(AName, AValue: Utf8String): TXmlNode; overload;
|
|
function NodeNewTextEx(AName, AValue: Utf8String; out AXmlNode: TXmlNode): TXmlNode; overload;
|
|
function NodeNewText(AName, AValue: Utf8String; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewTextEx(AName, AValue: Utf8String; out AXmlNode: TXmlNode;
|
|
SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
function NodeNewType(AName: Utf8String; AElementType: TsdElementType): TXmlNode; overload;
|
|
function NodeNewTypeEx(AName: Utf8String; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode): TXmlNode; overload;
|
|
function NodeNewType(AName: Utf8String; AElementType: TsdElementType;
|
|
SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewTypeEx(AName: Utf8String; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
function NodeNewAttr(AName: Utf8String; Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewAttrEx(AName: Utf8String; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewAttr(AName: Utf8String; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewAttrEx(AName: Utf8String; out AXMLNode: TXmlNode;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
function NodeNewTextType(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType): TXmlNode; overload;
|
|
function NodeNewTextTypeEx(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode): TXmlNode; overload;
|
|
function NodeNewTextType(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewTextTypeEx(AName, AValue: Utf8String; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
function NodeNewTextAttr(AName, AValue: Utf8string; Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewTextAttrEx(AName, AValue: Utf8String; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewTextAttr(AName, AValue: Utf8String; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewTextAttrEx(AName, AValue: Utf8String; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
function NodeNewTextTypeAttr(AName, AValue: Utf8String; AElementType: TsdElementType;
|
|
Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewTextTypeAttr(AName, AValue: Utf8String; AElementType: TsdElementType;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewTextTypeAttrEx(AName, AValue: Utf8String; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode; Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewTextTypeAttrEx(AName, AValue: Utf8String; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
// integer nodes
|
|
function NodeNewInt(AName: Utf8String; AValue: integer): TXmlNode; overload;
|
|
function NodeNewIntEx(AName: Utf8String; AValue: integer; out AXmlNode: TXmlNode): TXmlNode; overload;
|
|
function NodeNewInt(AName: Utf8String; AValue: integer; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewIntEx(AName: Utf8String; AValue: integer; out AXmlNode: TXmlNode;
|
|
SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
function NodeNewIntType(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType): TXmlNode; overload;
|
|
function NodeNewIntTypeEx(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode): TXmlNode; overload;
|
|
function NodeNewIntType(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewIntTypeEx(AName: Utf8String; AValue: integer; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
function NodeNewIntAttr(AName: Utf8String; AValue: integer; Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewIntAttrEx(AName: Utf8String; AValue: integer; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewIntAttr(AName: Utf8String; AValue: integer; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewIntAttrEx(AName: Utf8String; AValue: integer; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
|
|
function NodeNewIntTypeAttr(AName: Utf8String; AValue: integer; AElementType: TsdElementType;
|
|
Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewIntTypeAttrEx(AName: Utf8String; AValue: integer; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode; Attributes: array of TsdAttribute): TXmlNode; overload;
|
|
function NodeNewIntTypeAttr(AName: Utf8String; AValue: integer; AElementType: TsdElementType;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
function NodeNewIntTypeAttrEx(AName: Utf8String; AValue: integer; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode; overload;
|
|
end;
|
|
|
|
{ Canonicalize an xml document
|
|
|
|
The acronym for canonicalization is "C14N"
|
|
|
|
An xml document after C14N must be:
|
|
- encoded in utf-8 only
|
|
- xml declaration removed
|
|
- entities expanded to their character equivalent
|
|
- CDATA sections replaced by character equivalent
|
|
- special < > and " entities encoded
|
|
- attributes normalized as if by validating parser
|
|
- empty elements opened with start and end tags
|
|
- namespace declarations and attributes sorted
|
|
|
|
Experimental!
|
|
}
|
|
|
|
TsdXmlCanonicalizer = class(TDebugComponent)
|
|
public
|
|
function Canonicalize(AXml: TNativeXml): integer;
|
|
end;
|
|
|
|
|
|
{ binary xml
|
|
|
|
The idea here is that binary xml is a compact representation of the same
|
|
xml file, without the need of parsing or writing out the actual textual
|
|
representation.
|
|
|
|
A binary xml file just loads/saves the unique string table and the node structure.
|
|
Since there is the opportunity to compress and encrypt the binary xml with
|
|
additional methods, application code can use binary xml to work with efficient
|
|
binary files without the hassle of parsing/writing and with optional compression
|
|
or optional encryption. See also TNativeXml.LoadFromBinaryStream / SaveToBinaryStream.
|
|
|
|
}
|
|
TsdBinaryXml = class(TDebugComponent)
|
|
private
|
|
FDocument: TNativeXml;
|
|
FOnEncode: TXmlCoderEvent;
|
|
FOnDecode: TXmlCoderEvent;
|
|
FNewIDs: array of Cardinal;
|
|
FElementTypeCount: array[TsdElementType] of Cardinal;
|
|
function UpdateID(AID: Cardinal): Cardinal;
|
|
function IncrementFrequency(AID: Cardinal): Cardinal;
|
|
protected
|
|
function ReadCardinal(S: TStream): cardinal;
|
|
procedure ReadDocument(S: TStream);
|
|
function ReadNode(S: TStream; AParent: TXmlNode; var SubCount: integer): TXmlNode;
|
|
procedure SortByFrequency;
|
|
procedure WriteCardinal(S: TStream; ACardinal: cardinal);
|
|
procedure WriteDocument(S: TStream);
|
|
procedure WriteNode(S: TStream; ANode: TXmlNode);
|
|
public
|
|
constructor Create(AOwner: TComponent); override;
|
|
destructor Destroy; override;
|
|
procedure SaveToFile(const AFileName: string);
|
|
procedure SaveToStream(S: TStream); virtual;
|
|
procedure LoadFromFile(const AFileName: string);
|
|
procedure LoadFromStream(S: TStream); virtual;
|
|
property Document: TNativeXml read FDocument write FDocument;
|
|
property OnEncode: TXmlCoderEvent read FOnEncode write FOnEncode;
|
|
property OnDecode: TXmlCoderEvent read FOnDecode write FOnDecode;
|
|
end;
|
|
|
|
{
|
|
constants and utility functions of NativeXml
|
|
}
|
|
const
|
|
|
|
cNodeClass: array[TsdElementType] of TsdNodeClass =
|
|
(TsdElement, TsdAttribute, TsdCharData, TsdComment, TsdCData, TsdConditionalSection,
|
|
TsdDeclaration, TsdStyleSheet, TsdDocType, TsdDtdElement, TsdDtdAttList,
|
|
TsdDtdEntity, TsdDtdNotation, TsdInstruction, TsdWhiteSpace, TsdQuotedText,
|
|
nil, nil);
|
|
|
|
// chunk sizes: external stream is loaded/saved in these chunks of memory data
|
|
// - valid values are $4 - unbounded till memory size
|
|
// - sane values are $20 - $1000
|
|
cParserChunkSize = $100;
|
|
cWriterChunkSize = $100;
|
|
|
|
// Count of different escape phrases
|
|
cEscapePhraseCount = 5;
|
|
|
|
// These are phrases that must be escaped. Note that "&" is first since
|
|
// when another would be replaced first (eg ">" by "<") this could
|
|
// cause the new "&" in "<" to be replaced by "&";
|
|
cXmlEscapePhrases: array[0..cEscapePhraseCount - 1] of Utf8String =
|
|
('&',
|
|
'<',
|
|
'>',
|
|
'''',
|
|
'"');
|
|
|
|
// These are the phrases that replace the escape phrases - in the same order
|
|
// As a result, these phrases are visible in the core xml source
|
|
cXmlReplacePhrases: array[0..cEscapePhraseCount - 1] of Utf8String =
|
|
('&',
|
|
'<',
|
|
'>',
|
|
''',
|
|
'"');
|
|
|
|
// special characters used for whitespace / blanks
|
|
cXmlBlankChars: set of AnsiChar =
|
|
[#$09, #$0A, #$0D, #$20];
|
|
|
|
cXmlBlankCharsOrEndTag: set of AnsiChar =
|
|
[#$09, #$0A, #$0D, #$20, '[', '/', '>'];
|
|
|
|
cQuoteChars: set of AnsiChar =
|
|
['"', ''''];
|
|
|
|
cQuoteCharStyleNames: array[TsdQuoteCharStyle] of Utf8String =
|
|
('"', '''');
|
|
|
|
// codepage IBM852, used for GUI implementations
|
|
CP_852: integer = 852;
|
|
|
|
// Windows-1250 codepage, used for GUI implementations
|
|
CP_1250: integer = 1250;
|
|
|
|
// Windows-1252 codepage, used for GUI implementations
|
|
CP_1252: integer = 1252;
|
|
|
|
// UTF8 codepage (outcommented to avoid clash in BCB - it is already defined
|
|
// in windows)
|
|
//CP_UTF8: integer = 65001;
|
|
|
|
// UTF16 codepage
|
|
CP_UTF16: integer = 1200;
|
|
|
|
// ISO 8859-1 codepage, used for GUI implementations
|
|
CP_ISO8859_1: integer = 28591;
|
|
|
|
// These characters are used when generating BASE64 AnsiChars from buffer data
|
|
cBase64Char: array[0..63] of AnsiChar =
|
|
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
cBase64PadChar: AnsiChar = '=';
|
|
|
|
|
|
cBomInfoListCount = 15;
|
|
// array with Byte Order Mark (BOM) info
|
|
cBomInfoList: array[0..cBomInfoListCount - 1] of TsdBomInfo =
|
|
( (BOM: ($3C,$3F,$78,$6D); Len: 4; Encoding: seAnsi; HasBOM: false), // 0
|
|
(BOM: ($EF,$BB,$BF,$00); Len: 3; Encoding: seUTF8; HasBOM: true),
|
|
(BOM: ($00,$00,$FE,$FF); Len: 4; Encoding: seUTF32BE; HasBOM: true),
|
|
(BOM: ($FF,$FE,$00,$00); Len: 4; Encoding: seUTF32LE; HasBOM: true),
|
|
(BOM: ($00,$00,$FF,$FE); Len: 4; Encoding: seUCS4_2143; HasBOM: true),
|
|
(BOM: ($FE,$FF,$00,$00); Len: 4; Encoding: seUCS4_3412; HasBOM: true),
|
|
(BOM: ($FE,$FF,$00,$00); Len: 2; Encoding: seUTF16BE; HasBOM: true), // 6
|
|
(BOM: ($FF,$FE,$00,$00); Len: 2; Encoding: seUTF16LE; HasBOM: true), // 7
|
|
(BOM: ($00,$00,$00,$3C); Len: 4; Encoding: seUTF32BE; HasBOM: false),
|
|
(BOM: ($3C,$00,$00,$00); Len: 4; Encoding: seUTF32LE; HasBOM: false),
|
|
(BOM: ($00,$00,$3C,$00); Len: 4; Encoding: seUCS4_2143; HasBOM: false),
|
|
(BOM: ($00,$3C,$00,$00); Len: 4; Encoding: seUCS4_3412; HasBOM: false),
|
|
(BOM: ($00,$3C,$00,$3F); Len: 4; Encoding: seUTF16BE; HasBOM: false),
|
|
(BOM: ($3C,$00,$3F,$00); Len: 4; Encoding: seUTF16LE; HasBOM: false),
|
|
(BOM: ($4C,$6F,$A7,$94); Len: 4; Encoding: seEBCDIC; HasBOM: false)
|
|
);
|
|
cBomInfoIdxUTF16BE = 6;
|
|
cBomInfoIdxUTF16LE = 7;
|
|
|
|
cElementTypeNames: array[TsdElementType] of Utf8String =
|
|
('Element', 'Attribute', 'CharData', 'Comment', 'CData', 'ConditionalSection',
|
|
'Declaration', 'Stylesheet', 'DocType', 'DtdElement', 'DtdAttList', 'DtdEntity',
|
|
'DtdNotation', 'Instruction', 'WhiteSpace', 'QuotedText', 'EndTag', 'Error');
|
|
|
|
// binary xml version
|
|
// v1: stylesheet based on chardata
|
|
// v2: stylesheet based on containernode
|
|
cBinaryXmlVersion: cardinal = 2;
|
|
|
|
resourcestring
|
|
|
|
sPrematureEnd = 'stream terminated prematurely at pos %d';
|
|
sInvalidStream = 'invalid stream';
|
|
sUnknownEncoding = 'unknown encoding';
|
|
sUnsupportedEncoding = 'unsupported encoding (%s)';
|
|
sDefaultCharUsed = 'default char used for codepage substitution';
|
|
sNotSupported = 'feature is not supported yet';
|
|
sIllegalTag = 'illegal tag ("%s") at pos %d';
|
|
sUnsupportedTag = 'unsupported tag ("%s") at pos %d';
|
|
sIllegalEndTag = 'illegal end tag ("%s") at line %d (streampos %d)';
|
|
sQuoteCharExpected = 'quote char expected at pos %d';
|
|
sCannotAddNode = 'cannot add node to this type of element';
|
|
sCannotAddAttribute = 'cannot add attribute';
|
|
sCannotSetName = 'cannot set name on this type of element';
|
|
sCannotSetValue = 'cannot set value on this type of element';
|
|
sCannotManipulate = 'cannot manipulate nodes in this type of element';
|
|
sBeginEndMismatch = 'begin and end tag mismatch: "%s" and "%s" at line %d (pos %d)';
|
|
sLevelMismatch = 'level mismatch between subnode "%s" and endnode "%s" at line %d (pos %d)';
|
|
sRootElementNotDefined = 'XML root element not defined.';
|
|
sNonDefaultChardata = 'non-default chardata at line %d (pos %d)';
|
|
sSignificantDigitsOutOfRange = 'significant digits out of range';
|
|
sMissingDataInBinaryStream = 'missing data in binary stream';
|
|
sErrorCalcStreamLength = 'error while calculating streamlength';
|
|
sXmlNodeNotAssigned = 'XML node is not assigned';
|
|
sXmlOwnerNotAssigned = 'XML owner is not assigned';
|
|
sXmlParentNotAssigned = 'XML parent is not assigned';
|
|
sUnknownBinaryEncodingBinhex = 'unknown encoding: xbeBinHex (deprecated)';
|
|
sCopyFromOnlyWithXmlNode = 'CopyFrom can only be used with TXmlNode descendants';
|
|
|
|
var
|
|
|
|
// NativeXml defaults
|
|
cDefaultAesKeyHex: Utf8String = '00000000000000000000000000000000';
|
|
cDefaultBinaryMethod: TsdXmlBinaryMethod = bmDefault;
|
|
cDefaultDirectCloseTag: Utf8String = '/>';
|
|
cDefaultDropCommentsOnParse: boolean = False;
|
|
cDefaultFloatAllowScientific: boolean = True;
|
|
cDefaultFloatSignificantDigits: integer = 6;
|
|
cDefaultEncodingString: Utf8String = 'utf-8';
|
|
{$ifdef MSWINDOWS}
|
|
cDefaultEolStyle: TsdEolStyle = esCRLF;
|
|
{$else MSWINDOWS}
|
|
cDefaultEolStyle: TsdEolStyle = esLF;
|
|
{$endif MSWINDOWS}
|
|
cDefaultExternalEncoding: TsdStringEncoding = seUTF8;
|
|
cDefaultFixStructuralErrors: boolean = False;
|
|
cDefaultIndentString: Utf8String = #$09; // tab
|
|
cDefaultNodeClosingStyle: TsdNodeClosingStyle = ncClose;
|
|
cDefaultSortAttributes: boolean = False;
|
|
cDefaultSplitSecondDigits: integer = 0;
|
|
cDefaultVersionString: Utf8String = '1.0';
|
|
cDefaultXmlFormat: TXmlFormatType = xfPreserve;
|
|
cDefaultUseLocalBias: boolean = False;
|
|
cDefaultWriteOnDefault: boolean = True;
|
|
|
|
// helpful XML addtions
|
|
cReadableDirectCloseTag: Utf8String = ' />';
|
|
// see GetXmlFormatSettings in initialization section
|
|
cXmlFormatSettings: TFormatSettings;
|
|
|
|
{ Utility functions }
|
|
|
|
// Convert UnicodeString to Utf8String
|
|
function sdWideToUtf8(const W: UnicodeString): Utf8String;
|
|
|
|
// Convert UTF8 string to UnicodeString
|
|
function sdUtf8ToWide(const U: Utf8String): UnicodeString;
|
|
|
|
// Convert Ansi to Utf8 string
|
|
function sdAnsiToUtf8(const A: AnsiString; ACodePage: integer): Utf8String;
|
|
|
|
// Convert Utf8 to Ansi string
|
|
function sdUtf8ToAnsi(const U: Utf8String; ACodePage: integer): AnsiString;
|
|
|
|
function sdTrim(const S: Utf8String): Utf8String; overload;
|
|
function sdTrim(const S: Utf8String; var IsTrimmed: boolean): Utf8String; overload;
|
|
function sdTrim(const S: Utf8String; var PreString, PostString: Utf8String): Utf8String; overload;
|
|
|
|
// compress any eol (ie CR-LF) to normalised eol (LF)
|
|
function sdNormaliseEol(const S: Utf8String): Utf8String;
|
|
// expand any normalised eol (LF) to un-normalised eol (ie CR-LF) based on TsdEolStyle
|
|
function sdUnNormaliseEol(const S: Utf8String; const EolStyle: TsdEolStyle): Utf8String;
|
|
|
|
function sdEscapeString(const AValue: Utf8String): Utf8String;
|
|
|
|
// replace escaped phrases and references written in the core xml source
|
|
// with replacement characters
|
|
function sdReplaceString(const AValue: Utf8String; var HasNonStandardReferences: boolean; References: array of TXmlNode): Utf8String; overload;
|
|
function sdReplaceString(const AValue: Utf8String; var HasNonStandardReferences: boolean): Utf8String; overload;
|
|
function sdReplaceString(const AValue: Utf8String): Utf8String; overload;
|
|
|
|
function sdCommaToDot(const AValue: Utf8String): Utf8String;
|
|
|
|
function sdReadFromStream(S: TStream; CharCount: integer): Utf8String;
|
|
procedure sdWriteToStream(S: TStream; const Value: Utf8String);
|
|
function sdReadCardinal(S: TStream): cardinal;
|
|
procedure sdWriteCardinal(S: TStream; ACardinal: cardinal);
|
|
|
|
// Based on the charset, find the codepage. If no charset is
|
|
// matched, the function returns ADefaultCodepage (default utf-8, 65001)
|
|
function sdCharsetToCodePage(ACharset: Utf8String; ADefaultCodepage: integer = 65001): integer;
|
|
|
|
// Based on the charset, find the TsdStringEncoding. If no charset is
|
|
// matched, the function returns a encoding of seUTF8
|
|
function sdCharsetToStringEncoding(ACharset: Utf8String): TsdStringEncoding;
|
|
|
|
// find the charset corresponding to windows codepage
|
|
function sdCodepageToCharset(ACodepage: integer): Utf8String;
|
|
|
|
function Utf8CompareText(const S1, S2: Utf8String): integer;
|
|
|
|
// type conversions
|
|
|
|
// get the timezone bias
|
|
function GetTimeZoneBias: Integer;
|
|
|
|
// Convert the TDateTime ADate to a string according to the W3C date/time specification
|
|
// as found here: http://www.w3.org/TR/NOTE-datetime
|
|
function sdDateTimeToString(ADate: TDateTime; UseDate: boolean = True; UseTime: boolean = True;
|
|
SplitSecondDigits: integer = 0; UseLocalBias: boolean = False): Utf8String;
|
|
|
|
function sdBoolToString(Value: boolean): Utf8String;
|
|
function sdBoolFromString(Value: Utf8String): boolean;
|
|
|
|
// Convert a number to a Utf8String, using SignificantDigits to indicate the number of
|
|
// significant digits, and AllowScientific to allow for scientific notation if that
|
|
// results in much shorter notation.
|
|
function sdFloatToString(Value: double; SignificantDigits: integer; AllowScientific: boolean): Utf8String; overload;
|
|
function sdFloatToString(Value: double): Utf8String; overload;
|
|
function sdFloatFromString(Value: Utf8String): double;
|
|
|
|
function sdIntToString(Value: integer): Utf8String;
|
|
function sdIntFromString(Value: Utf8String): integer;
|
|
|
|
function sdInt64ToString(Value: int64): Utf8String;
|
|
function sdInt64FromString(Value: Utf8String): int64;
|
|
|
|
// Convert the Utf8String ADate to a TDateTime according to the W3C date/time specification
|
|
// as found here: http://www.w3.org/TR/NOTE-datetime
|
|
// If there is a conversion error, an exception will be raised.
|
|
function sdStringToDateTime(const ADate: Utf8String; UseLocalBias: boolean = False): TDateTime;
|
|
|
|
// Convert the UTF8String ADate to a TDateTime according to the W3C date/time specification
|
|
// as found here: http://www.w3.org/TR/NOTE-datetime
|
|
// If there is a conversion error, the default value ADefault is returned.
|
|
function sdStringToDateTimeDef(const ADate: Utf8String; ADefault: TDateTime;
|
|
UseLocalBias: boolean = False): TDateTime;
|
|
|
|
// Encode binary data in Source as BASE64. The function returns the BASE64 encoded
|
|
// data as UTF8String, without any linebreaks.
|
|
function EncodeBase64(const Source: RawByteString): Utf8String;
|
|
function EncodeBase64Buf(const Buffer; Count: Integer): Utf8String;
|
|
|
|
// Decode BASE64 data in Source into binary data. The function returns the binary
|
|
// data as Utf8String. The Source Utf8String may contain linebreaks and control characters,
|
|
// these will be stripped.
|
|
function DecodeBase64(const Source: Utf8String): RawByteString;
|
|
procedure DecodeBase64Buf(var Source: Utf8String; var Buffer; Count: Integer);
|
|
|
|
// Decode BINHEX data in Source into RawByteStrng with binary data (for compatibility with old NativeXml)
|
|
function DecodeBinHex(const Source: Utf8String): RawByteString;
|
|
procedure DecodeBinhexBuf(var Source: Utf8String; var Buffer; Count: Integer);
|
|
|
|
// This function removes control characters from Utf8String AValue (Tab, CR, LF and Space)
|
|
function sdRemoveControlChars(const AValue: Utf8String): Utf8String;
|
|
|
|
// This function adds control characters Chars repeatedly after each Interval
|
|
// of characters to UTF8String Value. Default interval is 76 (seems to be used in many
|
|
// applications)
|
|
function sdAddControlChars(const AValue: Utf8String; const ControlChars: Utf8String; Interval: integer = 76): Utf8String;
|
|
|
|
// Convert Ansi to Utf8 using buffers
|
|
// please note: Utf8Buf can use 3x more size than AnsiBuf in extreme cases.
|
|
// Result is the Utf8Buf bytecount
|
|
function sdAnsiToUtf8Buffer(const AnsiBuf; var Utf8Buf; ACodePage, AnsiCount: integer;
|
|
var LastChar0D: boolean): integer;
|
|
|
|
// convert raw buffer to normalized buffer (suitable for utf8 or ansi)
|
|
function sdNormaliseBuffer(const RawBuf; var NormBuf; Count: integer; var LastChar0D: boolean): integer;
|
|
|
|
// Convert Utf8 to Ansi using buffers
|
|
function sdUtf8ToAnsiBuffer(const Utf8Buf; var AnsiBuf; ACodePage, Utf8Count: integer;
|
|
var DefaultCharUsed: boolean): integer;
|
|
|
|
// determine the character length of the first Utf8 character in the buffer
|
|
function sdUtf8CharacterLength(const Buffer): integer;
|
|
|
|
// Convert a "WideString" (UTF16 LE) buffer to UTF8. This routine will process
|
|
// Count wide characters (2 bytes size) to Count UTF8 characters (1-3 bytes).
|
|
// Therefore, the Utf8Buf must be at least 1.5 the size of the WideBuf.
|
|
// The function returns the number of *bytes* written.
|
|
function sdWideToUtf8Buffer(const WideBuf; var Utf8Buf; WideCount: integer; var LastChar0D: boolean): integer;
|
|
|
|
// Convert an UTF8 memory block to Unicode (UTF16 LE). This routine will process
|
|
// Count *bytes* of UTF8 (each character 1-3 bytes) into UTF16 (each char 2 bytes).
|
|
// Therefore, the block at Dst must be at least 2 times the size of Count, since
|
|
// many UTF8 characters consist of just one byte, and are mapped to 2 bytes. The
|
|
// function returns the number of *wide chars* written. Note that the Src block must
|
|
// have an exact number of UTF8 characters in it, if Count doesn't match then
|
|
// the last character will be converted anyway (going past the block boundary!)
|
|
function sdUtf8ToWideBuffer(const Utf8Buf; var WideBuf; ByteCount: integer): integer;
|
|
|
|
implementation
|
|
|
|
type
|
|
TAnsiCharArray = array[0..32767] of AnsiChar;
|
|
|
|
|
|
{ TXmlNode }
|
|
|
|
procedure TXmlNode.AttributeAdd(const AName, AValue: Utf8String);
|
|
var
|
|
A: TsdAttribute;
|
|
begin
|
|
A := TsdAttribute.Create(TNativeXml(FOwner));
|
|
A.Name := AName;
|
|
A.Value := AValue;
|
|
AttributeAdd(A);
|
|
end;
|
|
|
|
procedure TXmlNode.AttributeAdd(AAttribute: TsdAttribute);
|
|
begin
|
|
{ if (AAttribute = nil) or (AAttribute.FOwner <> FOwner) then
|
|
begin
|
|
DoDebugOut(Self, wsFail, sXmlOwnerNotAssigned);
|
|
exit;
|
|
end;
|
|
NodeAdd(AAttribute);}
|
|
raise Exception.Create(sCannotAddAttribute);
|
|
end;
|
|
|
|
procedure TXmlNode.AttributesAdd(Attributes: array of TsdAttribute);
|
|
var
|
|
x: integer;
|
|
begin
|
|
for x := Low(Attributes) to High(Attributes) do
|
|
AttributeAdd(Attributes[x]);
|
|
end;
|
|
|
|
function TXmlNode.GetAttributeCount: integer;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := 0;
|
|
for i := 0 to GetNodeCount - 1 do
|
|
if GetNodes(i) is TsdAttribute then
|
|
inc(Result);
|
|
end;
|
|
|
|
constructor TXmlNode.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create;
|
|
if not (AOwner is TNativeXml) then
|
|
raise Exception.Create(sXmlOwnerNotAssigned);
|
|
FOwner := TNativeXml(AOwner);
|
|
end;
|
|
|
|
constructor TXmlNode.CreateParent(AOwner: TComponent; AParent: TXmlNode);
|
|
begin
|
|
Create(AOwner);
|
|
if not (AParent is TXmlNode) then
|
|
raise Exception.Create(sXmlParentNotAssigned);
|
|
AParent.NodeAdd(Self);
|
|
end;
|
|
|
|
constructor TXmlNode.CreateParentNear(AOwner: TComponent; AParent, ANode: TXmlNode; IsBefore: boolean);
|
|
begin
|
|
Create(AOwner);
|
|
if not (AParent is TsdContainerNode) then
|
|
raise Exception.Create(sXmlParentNotAssigned);
|
|
TsdContainerNode(AParent).NodeInsertNear(Self, ANode, IsBefore);
|
|
end;
|
|
|
|
constructor TXmlNode.CreateName(AOwner: TNativeXml; const AName: Utf8String);
|
|
begin
|
|
Create(AOwner);
|
|
Name := AName;
|
|
end;
|
|
|
|
constructor TXmlNode.CreateNameValue(AOwner: TNativeXml; const AName, AValue: Utf8String);
|
|
begin
|
|
Create(AOwner);
|
|
Name := AName;
|
|
Value := AValue;
|
|
end;
|
|
|
|
function TXmlNode.ElementType: TsdElementType;
|
|
begin
|
|
// overridden in descendants
|
|
Result := xeError;
|
|
end;
|
|
|
|
function TXmlNode.ElementTypeName: Utf8String;
|
|
begin
|
|
Result := cElementTypeNames[ElementType];
|
|
end;
|
|
|
|
class function TXmlNode.EscapeString(const S: Utf8String): Utf8String;
|
|
begin
|
|
Result := sdEscapeString(S);
|
|
end;
|
|
|
|
function TXmlNode.FirstNodeByType(AType: TsdElementType): TXmlNode;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
class function TXmlNode.WideToUtf8(const W: UnicodeString): Utf8String;
|
|
begin
|
|
Result := sdWideToUtf8(W);
|
|
end;
|
|
|
|
function TXmlNode.GetAttributeByName(const AName: Utf8String): TsdAttribute;
|
|
var
|
|
i: integer;
|
|
A: TsdAttribute;
|
|
begin
|
|
for i := 0 to GetAttributeCount - 1 do
|
|
begin
|
|
A := GetAttributes(i);
|
|
if Utf8CompareText(A.Name, AName) = 0 then
|
|
begin
|
|
Result := A;
|
|
exit;
|
|
end;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
function TXmlNode.GetAttributeName(Index: integer): Utf8String;
|
|
var
|
|
A: TsdAttribute;
|
|
begin
|
|
A := GetAttributes(Index);
|
|
if assigned(A) then
|
|
Result := A.Name
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TXmlNode.GetAttributes(Index: integer): TsdAttribute;
|
|
var
|
|
i, Idx: integer;
|
|
begin
|
|
Idx := 0;
|
|
Result := nil;
|
|
for i := 0 to GetNodeCount - 1 do
|
|
begin
|
|
if GetNodes(i) is TsdAttribute then
|
|
begin
|
|
if Idx = Index then
|
|
begin
|
|
Result := TsdAttribute(GetNodes(i));
|
|
exit;
|
|
end;
|
|
inc(Idx);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.GetAttributeValue(Index: integer): Utf8String;
|
|
var
|
|
A: TsdAttribute;
|
|
begin
|
|
A := GetAttributes(Index);
|
|
if assigned(A) then
|
|
Result := A.Value
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TXmlNode.GetAttributeValueAsInteger(Index: integer): integer;
|
|
begin
|
|
Result := StrToIntDef(GetAttributeValue(Index), 0);
|
|
end;
|
|
|
|
function TXmlNode.GetAttributeValueByName(const AName: Utf8String): Utf8String;
|
|
var
|
|
A: TsdAttribute;
|
|
begin
|
|
A := GetAttributeByName(AName);
|
|
if assigned(A) then
|
|
Result := A.Value
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TXmlNode.GetIndent: Utf8String;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := '';
|
|
if assigned(FOwner) then
|
|
begin
|
|
case GetXmlFormat of
|
|
xfCompact, xfPreserve: Result := '';
|
|
xfReadable:
|
|
for i := 0 to TreeDepth - 1 do
|
|
Result := Result + TNativeXml(FOwner).IndentString;
|
|
end; //case
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.GetEndOfLine: Utf8String;
|
|
begin
|
|
Result := '';
|
|
if GetXmlFormat = xfReadable then
|
|
Result := GetSeparator;
|
|
end;
|
|
|
|
function TXmlNode.GetSeparator: Utf8String;
|
|
begin
|
|
case GetEolStyle of
|
|
esLF: Result := #$0A; // linux
|
|
esCRLF: Result := #$0D#$0A; // windows
|
|
esCR: Result := #$0D; // mac
|
|
else
|
|
// default in case other separator styles emerge
|
|
Result := #$0A;
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.GetName: Utf8String;
|
|
begin
|
|
Result := '';
|
|
end;
|
|
|
|
function TXmlNode.GetNodes(Index: integer): TXmlNode;
|
|
begin
|
|
Result := nil;
|
|
end;
|
|
|
|
function TXmlNode.GetParentNode(ADepth: integer): TXmlNode;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := Self;
|
|
for i := 0 to ADepth do
|
|
begin
|
|
Result := Result.FParent;
|
|
if not assigned(Result) then
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.GetParentNodeName(ADepth: integer): Utf8String;
|
|
var
|
|
Node: TXmlNode;
|
|
begin
|
|
// parent node name
|
|
Node := GetParentNode(ADepth);
|
|
if assigned(Node) then
|
|
Result := Node.GetName
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TXmlNode.GetNameUnicode: UnicodeString;
|
|
begin
|
|
Result := sdUtf8ToWide(GetName);
|
|
end;
|
|
|
|
function TXmlNode.GetValue: Utf8String;
|
|
begin
|
|
Result := '';
|
|
end;
|
|
|
|
function TXmlNode.GetValueUnicode: UnicodeString;
|
|
begin
|
|
Result := sdUtf8ToWide(GetValue);
|
|
end;
|
|
|
|
function TXmlNode.IsClear: boolean;
|
|
begin
|
|
Result := IsEmpty and (length(Name) = 0);
|
|
end;
|
|
|
|
function TXmlNode.IsEmpty: boolean;
|
|
begin
|
|
Result := (GetNodeCount = 0) and (length(Value) = 0)
|
|
end;
|
|
|
|
function TXmlNode.IsEqualTo(ANode: TXmlNode; Options: TXmlCompareOptions; MismatchNodes: TList): boolean;
|
|
var
|
|
ThisSubNode, ThatSubNode: TXmlNode;
|
|
NodeResult, ChildResult: boolean;
|
|
// local
|
|
procedure AddMismatchNode(ANode: TXmlNode);
|
|
begin
|
|
if assigned(MismatchNodes) then
|
|
MismatchNodes.Add(ANode);
|
|
end;
|
|
// local
|
|
function NodeCompareOptions: boolean;
|
|
begin
|
|
// We assume there are differences
|
|
Result := False;
|
|
|
|
// node name
|
|
if xcNodeName in Options then
|
|
if Utf8CompareText(Name, ANode.Name) <> 0 then
|
|
exit;
|
|
|
|
// node type
|
|
if xcNodeType in Options then
|
|
if ElementType <> ANode.ElementType then
|
|
exit;
|
|
|
|
// node value
|
|
if xcNodeValue in Options then
|
|
if Utf8CompareText(Value, ANode.Value) <> 0 then
|
|
exit;
|
|
|
|
// attribute count
|
|
if xcAttribCount in Options then
|
|
if AttributeCount <> ANode.AttributeCount then
|
|
exit;
|
|
|
|
// child container count
|
|
if xcChildCount in Options then
|
|
if ContainerCount <> ANode.ContainerCount then
|
|
exit;
|
|
|
|
// If we arrive here, it means no differences were found, return True
|
|
Result := True;
|
|
end;
|
|
// local
|
|
function ChildCompareOptions: boolean;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := True;
|
|
|
|
// child and attribute node names and values
|
|
if Options * [xcChildNames, xcChildValues, xcAttribNames, xcAttribValues] <> [] then
|
|
begin
|
|
// iterate nodes
|
|
for i := 0 to NodeCount - 1 do
|
|
begin
|
|
ThisSubNode := Nodes[i];
|
|
if (ThisSubNode is TsdAttribute) or (ThisSubNode is TsdElement) then
|
|
begin
|
|
ThatSubNode := ANode.NodeByName(ThisSubNode.Name);
|
|
if not assigned(ThatSubNode) then
|
|
begin
|
|
// No we dont have it
|
|
if (xcChildNames in Options) or (xcAttribNames in Options) then
|
|
begin
|
|
AddMismatchNode(ThisSubNode);
|
|
Result := False;
|
|
end;
|
|
end else
|
|
begin
|
|
// Do child and attribute value check
|
|
if (xcChildValues in Options) or (xcAttribValues in Options) then
|
|
begin
|
|
if Utf8CompareText(ThisSubNode.Value, ThatSubNode.Value) <> 0 then
|
|
begin
|
|
AddMismatchNode(ThisSubNode);
|
|
Result := False;
|
|
end;
|
|
end;
|
|
// Do recursive check
|
|
if xcRecursive in Options then
|
|
if not ThisSubNode.IsEqualTo(ThatSubNode, Options, MismatchNodes) then
|
|
Result := False;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
// main
|
|
begin
|
|
Result := False;
|
|
if not assigned(ANode) then
|
|
exit;
|
|
|
|
// node compare options
|
|
NodeResult := NodeCompareOptions;
|
|
if NodeResult = False then
|
|
AddMismatchNode(Self);
|
|
|
|
// child compare options
|
|
ChildResult := ChildCompareOptions;
|
|
|
|
// final result
|
|
Result := NodeResult and ChildResult;
|
|
end;
|
|
|
|
function TXmlNode.NodeAdd(ANode: TXmlNode): integer;
|
|
begin
|
|
// functionality is in descendant TsdContainerNode
|
|
raise Exception.Create(sCannotAddNode);
|
|
end;
|
|
|
|
function TXmlNode.NodeByName(const AName: Utf8String): TXmlNode;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to GetNodeCount - 1 do
|
|
if Utf8CompareText(GetNodes(i).Name, AName) = 0 then
|
|
begin
|
|
Result := GetNodes(i);
|
|
exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
function TXmlNode.GetNodeCount: integer;
|
|
begin
|
|
// functionality is in descendant TsdContainerNode
|
|
Result := 0;
|
|
end;
|
|
|
|
procedure TXmlNode.NodeDelete(Index: integer);
|
|
begin
|
|
// functionality is in descendant TsdContainerNode
|
|
raise Exception.Create(sCannotManipulate);
|
|
end;
|
|
|
|
procedure TXmlNode.NodesClear;
|
|
begin
|
|
// functionality is in descendant TsdContainerNode
|
|
raise Exception.Create(sCannotManipulate);
|
|
end;
|
|
|
|
procedure TXmlNode.NodeRemove(ANode: TXmlNode);
|
|
var
|
|
Idx: integer;
|
|
begin
|
|
Idx := NodeIndexOf(ANode);
|
|
if Idx >= 0 then
|
|
NodeDelete(Idx);
|
|
end;
|
|
|
|
procedure TXmlNode.NodeRemoveEx(ANode: TXmlNode);
|
|
// Remove the line of the xml file that has the subnode ANode in it.
|
|
// The subnode and accompanying character data will be freed.
|
|
var
|
|
Idx, Idx1, Cnt: integer;
|
|
begin
|
|
Idx := NodeIndexOf(ANode);
|
|
Idx1 := Idx - 1;
|
|
|
|
while (Idx1 >= 0) and (Nodes[Idx1] is TsdWhiteSpace) do
|
|
dec(Idx1);
|
|
|
|
inc(Idx1);
|
|
|
|
Cnt := Idx - Idx1 + 1;
|
|
while Cnt > 0 do
|
|
begin
|
|
NodeDelete(Idx1);
|
|
dec(Cnt);
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.NodeExtract(ANode: TXmlNode): TXmlNode;
|
|
begin
|
|
// functionality is in descendant TsdContainerNode
|
|
raise Exception.Create(sCannotManipulate);
|
|
end;
|
|
|
|
procedure TXmlNode.NodeExchange(Index1, Index2: integer);
|
|
begin
|
|
// functionality is in descendant TsdContainerNode
|
|
raise Exception.Create(sCannotManipulate);
|
|
end;
|
|
|
|
function TXmlNode.NodeIndexOf(ANode: TXmlNode): integer;
|
|
begin
|
|
// functionality is in descendant TsdContainerNode
|
|
Result := -1;
|
|
end;
|
|
|
|
procedure TXmlNode.NodeInsert(Index: integer; ANode: TXmlNode);
|
|
begin
|
|
// functionality is in descendant TsdContainerNode
|
|
raise Exception.Create(sCannotAddNode);
|
|
end;
|
|
|
|
function TXmlNode.NodeNew(const AName: Utf8String): TXmlNode;
|
|
// Add a new child node and return its pointer
|
|
var
|
|
NodeClass: TsdNodeClass;
|
|
begin
|
|
NodeClass := cNodeClass[ElementType];
|
|
if not assigned(NodeClass) then
|
|
begin
|
|
Result := nil;
|
|
exit;
|
|
end;
|
|
|
|
// Create new node
|
|
Result := NodeClass.Create(TNativeXml(FOwner));
|
|
if assigned(Result) then
|
|
begin
|
|
Result.Name := AName;
|
|
NodeAdd(Result);
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.NodeNewAtIndex(Index: integer; const AName: Utf8String): TXmlNode;
|
|
// Create a new node with AName, and insert it into the subnode list at location
|
|
// Index, and return a pointer to it.
|
|
var
|
|
NodeClass: TsdNodeClass;
|
|
begin
|
|
NodeClass := cNodeClass[ElementType];
|
|
if not assigned(NodeClass) then
|
|
begin
|
|
Result := nil;
|
|
exit;
|
|
end;
|
|
|
|
// Create new node
|
|
Result := NodeClass.Create(TNativeXml(FOwner));
|
|
if assigned(Result) then
|
|
begin
|
|
Result.Name := AName;
|
|
NodeInsert(Index, Result);
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
begin
|
|
// XmlNode parsing is abstract, but in descending nodes the parsing begins here.
|
|
// The parsing starts right after Parser.ReadOpenTag and should stop after the
|
|
// matching endtag of the node. The result should be the matching endnode.
|
|
//
|
|
// Example: TsdElement.ParseStream
|
|
//
|
|
// functionality in descendants
|
|
Result := Self;
|
|
end;
|
|
|
|
procedure TXmlNode.SetAttributeName(Index: integer; const Value: Utf8String);
|
|
var
|
|
A: TsdAttribute;
|
|
begin
|
|
A := GetAttributes(Index);
|
|
if not assigned(A) then
|
|
exit;
|
|
A.Name := Value;
|
|
end;
|
|
|
|
procedure TXmlNode.SetAttributeValue(Index: integer; const Value: Utf8String);
|
|
var
|
|
A: TsdAttribute;
|
|
begin
|
|
A := GetAttributes(Index);
|
|
if not assigned(A) then
|
|
exit;
|
|
A.Value := Value;
|
|
end;
|
|
|
|
procedure TXmlNode.SetAttributeValueAsInteger(Index: integer; const Value: integer);
|
|
begin
|
|
SetAttributeValue(Index, IntToStr(Value));
|
|
end;
|
|
|
|
procedure TXmlNode.SetAttributeValueByName(const AName, Value: Utf8String);
|
|
var
|
|
A: TsdAttribute;
|
|
begin
|
|
A := GetAttributeByName(AName);
|
|
if not assigned(A) then
|
|
begin
|
|
A := TsdAttribute.Create(TNativeXml(FOwner));
|
|
A.Name := AName;
|
|
NodeAdd(A);
|
|
end;
|
|
A.Value := Value;
|
|
end;
|
|
|
|
procedure TXmlNode.SetName(const Value: Utf8String);
|
|
begin
|
|
// functionality in descendants
|
|
raise Exception.Create(sCannotSetName);
|
|
end;
|
|
|
|
procedure TXmlNode.SetNameUnicode(const Value: UnicodeString);
|
|
begin
|
|
SetName(sdWideToUtf8(Value));
|
|
end;
|
|
|
|
procedure TXmlNode.SetValue(const Value: Utf8String);
|
|
begin
|
|
// functionality in descendants
|
|
raise Exception.Create(sCannotSetValue);
|
|
end;
|
|
|
|
procedure TXmlNode.SetValueUnicode(const Value: UnicodeString);
|
|
begin
|
|
SetValue(sdWideToUtf8(Value));
|
|
end;
|
|
|
|
function TXmlNode.GetString(AID: integer): Utf8String;
|
|
var
|
|
Table: TsdSymbolTable;
|
|
begin
|
|
Result := '';
|
|
if assigned(FOwner) then
|
|
begin
|
|
Table := TNativeXml(FOwner).FSymbolTable;
|
|
if assigned(Table) then
|
|
Result := Table.GetString(AID);
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.AddString(const S: Utf8String): integer;
|
|
var
|
|
Table: TsdSymbolTable;
|
|
begin
|
|
Result := 0;
|
|
if assigned(FOwner) then
|
|
begin
|
|
Table := TNativeXml(FOwner).FSymbolTable;
|
|
if assigned(Table) then
|
|
Result := Table.AddString(S)
|
|
end;
|
|
end;
|
|
|
|
class function TXmlNode.Utf8ToWide(const S: Utf8String): UnicodeString;
|
|
begin
|
|
Result := sdUtf8ToWide(S);
|
|
end;
|
|
|
|
function TXmlNode.TreeDepth: integer;
|
|
begin
|
|
if assigned(FParent) then
|
|
Result := FParent.TreeDepth + 1
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
class function TXmlNode.ReplaceString(const S: Utf8String): Utf8String;
|
|
begin
|
|
Result := sdReplaceString(S);
|
|
end;
|
|
|
|
procedure TXmlNode.WriteStream(S: TStream);
|
|
begin
|
|
// functionality is in descendants
|
|
end;
|
|
|
|
function TXmlNode.ReadAttributeBool(const AName: Utf8String; ADefault: boolean = False): boolean;
|
|
begin
|
|
Result := StrToBoolDef(AttributeValueByName[AName], ADefault);
|
|
end;
|
|
|
|
function TXmlNode.ReadAttributeInteger(const AName: Utf8String; ADefault: integer = 0): integer;
|
|
begin
|
|
Result := StrToIntDef(AttributeValueByName[AName], ADefault);
|
|
end;
|
|
|
|
function TXmlNode.ReadAttributeInt64(const AName: Utf8String; ADefault: int64): int64; // added by hdk
|
|
begin
|
|
Result := StrToInt64Def(AttributeValueByName[AName], ADefault);
|
|
end;
|
|
|
|
function TXmlNode.ReadAttributeFloat(const AName: Utf8String; ADefault: double = 0): double;
|
|
begin
|
|
Result := StrToFloatDef(AttributeValueByName[AName], ADefault, cXmlFormatSettings); // changed by hdk
|
|
end;
|
|
|
|
function TXmlNode.ReadAttributeString(const AName: Utf8String; ADefault: Utf8String = ''): Utf8String;
|
|
begin
|
|
Result := AttributeValueByName[AName];
|
|
if Length(Result) = 0 then
|
|
Result := ADefault;
|
|
end;
|
|
|
|
function TXmlNode.ReadAttributeUnicodeString(const AName: Utf8String; ADefault: UnicodeString): UnicodeString; // added by hdk
|
|
begin
|
|
Result := sdUtf8ToWide(AttributeValueByName[AName]);
|
|
if Length(Result) = 0 then
|
|
Result := ADefault;
|
|
end;
|
|
|
|
function TXmlNode.ReadAttributeAnsiString(const AName: Utf8String; ADefault: AnsiString): AnsiString; // added by hdk
|
|
begin
|
|
Result := sdUtf8ToAnsi(AttributeValueByName[AName], CP_ACP);
|
|
if Length(Result) = 0 then
|
|
Result := ADefault;
|
|
end;
|
|
|
|
function TXmlNode.ReadAttributeDateTime(const AName: Utf8String; ADefault: TDateTime): TDateTime; // added by hdk
|
|
begin
|
|
Result := sdStringToDateTimeDef(AttributeValueByName[AName], ADefault, TNativeXml(FOwner).FUseLocalBias);
|
|
end;
|
|
|
|
function TXmlNode.ReadBool(const AName: Utf8String; ADefault: boolean = False): boolean;
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Result := ADefault;
|
|
Child := NodeByName(AName);
|
|
if assigned(Child) then
|
|
Result := Child.GetValueAsBoolDef(ADefault);
|
|
end;
|
|
|
|
{$ifdef USEGRAPHICS}
|
|
procedure TXmlNode.ReadPen(const AName: UTF8String; APen: TPen);
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Child := NodeByName(AName);
|
|
if assigned(Child) then with Child do
|
|
begin
|
|
// Read values
|
|
APen.Color := ReadColor('Color', clBlack);
|
|
APen.Mode := TPenMode(ReadInteger('Mode', integer(pmCopy)));
|
|
APen.Style := TPenStyle(ReadInteger('Style', integer(psSolid)));
|
|
APen.Width := ReadInteger('Width', 1);
|
|
end else
|
|
begin
|
|
// Defaults
|
|
APen.Color := clBlack;
|
|
APen.Mode := pmCopy;
|
|
APen.Style := psSolid;
|
|
APen.Width := 1;
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.ReadBrush(const AName: Utf8String; ABrush: TBrush);
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Child := NodeByName(AName);
|
|
if assigned(Child) then with Child do
|
|
begin
|
|
// Read values
|
|
ABrush.Color := ReadColor('Color', clWhite);
|
|
ABrush.Style := TBrushStyle(ReadInteger('Style', integer(bsSolid)));
|
|
end else
|
|
begin
|
|
// Defaults
|
|
ABrush.Bitmap := nil;
|
|
ABrush.Color := clWhite;
|
|
ABrush.Style := bsSolid;
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.ReadColor(const AName: Utf8String; ADefault: TColor = 0): TColor;
|
|
begin
|
|
Result := ReadInteger(AName, integer(ADefault));
|
|
end;
|
|
{$endif USEGRAPHICS}
|
|
|
|
function TXmlNode.ReadDateTime(const AName: Utf8String; ADefault: TDateTime): TDateTime;
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Result := ADefault;
|
|
Child := NodeByName(AName);
|
|
if assigned(Child) then
|
|
Result := Child.GetValueAsDateTimeDef(ADefault);
|
|
end;
|
|
|
|
function TXmlNode.ReadFloat(const AName: UTF8String; ADefault: double): double;
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Result := ADefault;
|
|
Child := NodeByName(AName);
|
|
if assigned(Child) then
|
|
Result := Child.GetValueAsFloatDef(ADefault);
|
|
end;
|
|
|
|
function TXmlNode.ReadInteger(const AName: Utf8String; ADefault: integer): integer;
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Result := ADefault;
|
|
Child := NodeByName(AName);
|
|
if assigned(Child) then
|
|
Result := Child.GetValueAsIntegerDef(ADefault);
|
|
end;
|
|
|
|
function TXmlNode.ReadInt64(const AName: Utf8String; ADefault: int64): int64; // added by hdk
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Result := ADefault;
|
|
Child := NodeByName(AName);
|
|
if assigned(Child) then
|
|
Result := Child.GetValueAsInt64Def(ADefault);
|
|
end;
|
|
|
|
function TXmlNode.ReadString(const AName: Utf8String; const ADefault: Utf8String = ''): Utf8String;
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Result := ADefault;
|
|
Child := NodeByName(AName);
|
|
if assigned(Child) then
|
|
Result := Child.Value;
|
|
end;
|
|
|
|
function TXmlNode.ReadUnicodeString(const AName: UTF8String; const ADefault: UnicodeString): UnicodeString;
|
|
begin
|
|
Result := sdUtf8ToWide(ReadString(AName, sdWideToUtf8(ADefault)));
|
|
end;
|
|
|
|
function TXmlNode.ReadAnsiString(const AName: Utf8String; const ADefault: AnsiString): AnsiString; // added by hdk
|
|
begin
|
|
Result := sdUtf8ToAnsi(ReadString(AName, sdAnsiToUtf8(ADefault, CP_ACP)), CP_ACP);
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsBoolDef(ADefault: boolean): boolean;
|
|
begin
|
|
Result := StrToBoolDef(GetValue, ADefault);
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsDateTimeDef(ADefault: TDateTime): TDateTime;
|
|
begin
|
|
Result := sdStringToDateTimeDef(GetValue, ADefault);
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsFloatDef(ADefault: double): double;
|
|
var
|
|
V: Utf8String;
|
|
begin
|
|
// backwards compat: old version used to allow commas in floats
|
|
V := sdCommaToDot(GetValue);
|
|
Result := StrToFloatDef(V, ADefault, cXmlFormatSettings); // changed by hdk
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsIntegerDef(ADefault: integer): integer;
|
|
begin
|
|
Result := StrToIntDef(GetValue, ADefault);
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsInt64Def(ADefault: int64): int64;
|
|
begin
|
|
Result := StrToInt64Def(GetValue, ADefault);
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsBool: boolean;
|
|
begin
|
|
Result := StrToBool(GetValue);
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsDateTime: TDateTime;
|
|
begin
|
|
Result := sdStringToDateTime(GetValue);
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsFloat: double;
|
|
begin
|
|
{$ifdef D7UP}
|
|
Result := StrToFloat(GetValue, cXmlFormatSettings); // changed by hdk
|
|
{$else D7UP}
|
|
// D5 version
|
|
Result := StrToFloat(GetValue);
|
|
{$endif D7UP}
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsInteger: integer;
|
|
begin
|
|
Result := StrToInt(GetValue);
|
|
end;
|
|
|
|
function TXmlNode.GetValueAsInt64: int64;
|
|
begin
|
|
Result := StrToInt64(GetValue);
|
|
end;
|
|
|
|
procedure TXmlNode.SetValueAsBool(const AValue: boolean);
|
|
begin
|
|
SetValue(sdBoolToString(AValue));
|
|
end;
|
|
|
|
procedure TXmlNode.SetValueAsDate(const AValue: TDateTime);
|
|
begin
|
|
SetValue(sdDateTimeToString(AValue, True, False, 0, False));
|
|
end;
|
|
|
|
procedure TXmlNode.SetValueAsTime(const AValue: TDateTime);
|
|
begin
|
|
SetValue(sdDateTimeToString(AValue, False, True,
|
|
TNativeXml(FOwner).SplitSecondDigits, TNativeXml(FOwner).FUseLocalBias));
|
|
end;
|
|
|
|
procedure TXmlNode.SetValueAsDateTime(const AValue: TDateTime);
|
|
begin
|
|
SetValue(sdDateTimeToString(AValue, True, True,
|
|
TNativeXml(FOwner).SplitSecondDigits, TNativeXml(FOwner).FUseLocalBias));
|
|
end;
|
|
|
|
procedure TXmlNode.SetValueAsFloat(const AValue: double);
|
|
begin
|
|
SetValue(sdFloatToString(AValue,
|
|
TNativeXml(FOwner).FFloatSignificantDigits,
|
|
TNativeXml(FOwner).FFloatAllowScientific));
|
|
end;
|
|
|
|
procedure TXmlNode.SetValueAsInteger(const AValue: integer);
|
|
begin
|
|
SetValue(sdIntToString(AValue));
|
|
end;
|
|
|
|
procedure TXmlNode.SetValueAsInt64(const AValue: int64);
|
|
begin
|
|
SetValue(sdInt64ToString(AValue));
|
|
end;
|
|
|
|
procedure TXmlNode.NodesByName(const AName: Utf8String; const AList: TList);
|
|
// Fill AList with nodes that have name AName
|
|
var
|
|
i: integer;
|
|
begin
|
|
if not assigned(AList) or not assigned(Self) then
|
|
exit;
|
|
AList.Clear;
|
|
for i := 0 to GetNodeCount - 1 do
|
|
if Utf8CompareText(Nodes[i].Name, AName) = 0 then
|
|
AList.Add(Nodes[i]);
|
|
end;
|
|
|
|
procedure TXmlNode.WriteBool(const AName: Utf8String; AValue, ADefault: boolean);
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
WriteValue(AName, sdBoolToString(AValue));
|
|
end;
|
|
|
|
procedure TXmlNode.WriteDateTime(const AName: Utf8String; AValue, ADefault: TDateTime);
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
WriteValue(AName, sdDateTimeToString(AValue, True, True,
|
|
TNativeXml(FOwner).FSplitSecondDigits, TNativeXml(FOwner).FUseLocalBias));
|
|
end;
|
|
|
|
procedure TXmlNode.WriteFloat(const AName: UTF8String; AValue, ADefault: double);
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
WriteValue(AName, sdFloatToString(AValue,
|
|
TNativeXml(FOwner).FFloatSignificantDigits,
|
|
TNativeXml(FOwner).FFloatAllowScientific));
|
|
end;
|
|
|
|
procedure TXmlNode.WriteHex(const AName: UTF8String; AValue, Digits: integer; ADefault: integer);
|
|
var
|
|
HexString: Utf8String;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
HexString := '$' + Utf8String(IntToHex(AValue, Digits));
|
|
WriteValue(AName, HexString);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteInteger(const AName: Utf8String; AValue, ADefault: integer);
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
WriteValue(AName, sdIntToString(AValue));
|
|
end;
|
|
|
|
procedure TXmlNode.WriteInt64(const AName: Utf8String; AValue, ADefault: int64);
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
WriteValue(AName, sdInt64ToString(AValue));
|
|
end;
|
|
|
|
procedure TXmlNode.WriteString(const AName, AValue, ADefault: Utf8String);
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
WriteValue(AName, AValue);
|
|
end;
|
|
|
|
procedure TXmlNode.WriteUnicodeString(const AName: Utf8String; const AValue, ADefault: UnicodeString);
|
|
begin
|
|
WriteString(AName, sdWideToUtf8(AValue), sdWideToUtf8(ADefault));
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAnsiString(const AName: Utf8String; const AValue, ADefault: AnsiString); // added by hdk
|
|
begin
|
|
WriteString(AName, sdAnsiToUtf8(AValue, CP_ACP), sdAnsiToUtf8(ADefault, CP_ACP));
|
|
end;
|
|
|
|
procedure TXmlNode.NodesAdd(Nodes: array of TXmlNode);
|
|
var
|
|
x: integer;
|
|
begin
|
|
for x := Low(Nodes) to High(Nodes) do
|
|
NodeAdd(Nodes[x]);
|
|
end;
|
|
|
|
function TXmlNode.GetWriteOnDefault: boolean;
|
|
begin
|
|
if assigned(FOwner) then
|
|
Result := TNativeXml(FOwner).WriteOnDefault
|
|
else
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TXmlNode.SetWriteOnDefault(const Value: boolean);
|
|
begin
|
|
if assigned(FOwner) then
|
|
TNativeXml(FOwner).WriteOnDefault := Value;
|
|
end;
|
|
|
|
function TXmlNode.NodeFindOrCreate(const AName: Utf8String): TXmlNode;
|
|
// Find the node with AName, and if not found, add new one
|
|
begin
|
|
Result := NodeByName(AName);
|
|
if not assigned(Result) then
|
|
Result := NodeNew(AName);
|
|
end;
|
|
|
|
function TXmlNode.NodeIndexByName(const AName: Utf8String): integer;
|
|
begin
|
|
Result := 0;
|
|
while Result < NodeCount do
|
|
begin
|
|
if Utf8CompareText(Nodes[Result].Name, AName) = 0 then
|
|
exit;
|
|
inc(Result);
|
|
end;
|
|
if Result = NodeCount then
|
|
Result := -1;
|
|
end;
|
|
|
|
function TXmlNode.AttributeIndexByName(const AName: Utf8String): integer;
|
|
begin
|
|
Result := 0;
|
|
// attributes are nodes from 0 to DirectNodeCount - 1
|
|
while Result < DirectNodeCount do
|
|
begin
|
|
if Utf8CompareText(Nodes[Result].Name, AName) = 0 then
|
|
exit;
|
|
inc(Result);
|
|
end;
|
|
if Result = DirectNodeCount then
|
|
Result := -1;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteValue(const AName, AValue: Utf8String);
|
|
var
|
|
Child: TXmlNode;
|
|
begin
|
|
Child := NodeFindOrCreate(AName);
|
|
if assigned(Child) then
|
|
Child.Value := AValue;
|
|
end;
|
|
|
|
procedure TXmlNode.DoProgress(Position: int64);
|
|
begin
|
|
// Call the onprogress
|
|
if assigned(FOwner) then
|
|
TNativeXml(FOwner).DoProgress(Position);
|
|
end;
|
|
|
|
function TXmlNode.BufferLength: integer;
|
|
var
|
|
BufData: Utf8String;
|
|
BufPos: integer;
|
|
begin
|
|
BufData := sdRemoveControlChars(GetValue);
|
|
Result := length(BufData) div 4;
|
|
if Result * 4 <> length(BufData) then
|
|
raise EFilerError.Create(sErrorCalcStreamLength);
|
|
Result := Result * 3;
|
|
// Check padding chars
|
|
BufPos := length(BufData);
|
|
if (BufPos > 0) and (BufData[BufPos] = cBase64PadChar) then
|
|
begin
|
|
dec(BufPos);
|
|
dec(Result);
|
|
if (BufPos > 0) and (BufData[BufPos] = cBase64PadChar) then
|
|
dec(Result);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.BufferRead(var Buffer; Count: Integer; BinaryEncoding: TsdBinaryEncoding);
|
|
// Read data from XML base64/Binhex to the buffer (default is xbeBase64)
|
|
var
|
|
BufData: Utf8String;
|
|
begin
|
|
BufData := sdRemoveControlChars(GetValue);
|
|
case BinaryEncoding of
|
|
xbeBase64:
|
|
// this is the default method
|
|
DecodeBase64Buf(BufData, Buffer, Count);
|
|
xbeBinHex:
|
|
// for compat with older versions
|
|
DecodeBinhexBuf(BufData, Buffer, Count);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.BufferWrite(const Buffer; Count: Integer);
|
|
// Write data from the buffer to XML in base64 format
|
|
var
|
|
BufData: Utf8String;
|
|
begin
|
|
if Count > 0 then
|
|
BufData := EncodeBase64Buf(Buffer, Count);
|
|
|
|
// For comformity with Base64, we must add linebreaks
|
|
SetValue(sdAddControlChars(BufData, GetEndOfLine + GetIndent));
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAttributeInteger(const AName: Utf8String; AValue, ADefault: integer);
|
|
var
|
|
S: Utf8String;
|
|
A: TsdAttribute;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
A := AttributeByName[AName];
|
|
S := sdIntToString(AValue);
|
|
if assigned(A) then
|
|
A.Value := S
|
|
else
|
|
AttributeAdd(AName, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAttributeInt64(const AName: UTF8String; AValue, ADefault: int64); // added by hdk
|
|
var
|
|
S: Utf8String;
|
|
A: TsdAttribute;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
A := AttributeByName[AName];
|
|
S := sdInt64ToString(AValue);
|
|
if assigned(A) then
|
|
A.Value := S
|
|
else
|
|
AttributeAdd(AName, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAttributeFloat(const AName: Utf8String; AValue, ADefault: double);
|
|
var
|
|
S: Utf8String;
|
|
A: TsdAttribute;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
A := AttributeByName[AName];
|
|
S := sdFloatToString(AValue,
|
|
TNativeXml(FOwner).FFloatSignificantDigits,
|
|
TNativeXml(FOwner).FFloatAllowScientific);
|
|
if assigned(A) then
|
|
A.Value := S
|
|
else
|
|
AttributeAdd(AName, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAttributeString(const AName: Utf8String; AValue, ADefault: Utf8String);
|
|
var
|
|
S: Utf8String;
|
|
A: TsdAttribute;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
A := AttributeByName[AName];
|
|
S := AValue;
|
|
if assigned(A) then
|
|
A.Value := S
|
|
else
|
|
AttributeAdd(AName, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAttributeUnicodeString(const AName: Utf8String; const AValue, ADefault: UnicodeString);
|
|
var
|
|
S: Utf8String;
|
|
A: TsdAttribute;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
A := AttributeByName[AName];
|
|
S := sdWideToUtf8(AValue);
|
|
if assigned(A) then
|
|
A.Value := S
|
|
else
|
|
AttributeAdd(AName, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAttributeAnsiString(const AName: Utf8String; const AValue, ADefault: AnsiString); // added by hdk
|
|
var
|
|
S: Utf8String;
|
|
A: TsdAttribute;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
A := AttributeByName[AName];
|
|
S := sdAnsiToUtf8(AValue, CP_ACP);
|
|
if assigned(A) then
|
|
A.Value := S
|
|
else
|
|
AttributeAdd(AName, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAttributeDateTime(const AName: Utf8String; AValue, ADefault: TDateTime);
|
|
var
|
|
S: Utf8String;
|
|
A: TsdAttribute;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
A := AttributeByName[AName];
|
|
S := sdDateTimeToString(AValue, True, True,
|
|
TNativeXml(FOwner).FSplitSecondDigits, TNativeXml(FOwner).FUseLocalBias);
|
|
if assigned(A) then
|
|
A.Value := S
|
|
else
|
|
AttributeAdd(AName, S);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteAttributeBool(const AName: Utf8String; AValue, ADefault: boolean);
|
|
var
|
|
S: Utf8String;
|
|
A: TsdAttribute;
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
begin
|
|
A := AttributeByName[AName];
|
|
S := sdBoolToString(AValue);
|
|
if assigned(A) then
|
|
A.Value := S
|
|
else
|
|
AttributeAdd(AName, S);
|
|
end;
|
|
end;
|
|
|
|
{$ifdef USEGRAPHICS}
|
|
procedure TXmlNode.WritePen(const AName: Utf8String; APen: TPen);
|
|
begin
|
|
with NodeFindOrCreate(AName) do
|
|
begin
|
|
WriteColor('Color', APen.Color, clBlack);
|
|
WriteInteger('Mode', integer(APen.Mode), 0);
|
|
WriteInteger('Style', integer(APen.Style), 0);
|
|
WriteInteger('Width', APen.Width, 0);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteBrush(const AName: Utf8String; ABrush: TBrush);
|
|
begin
|
|
with NodeFindOrCreate(AName) do
|
|
begin
|
|
WriteColor('Color', ABrush.Color, clBlack);
|
|
WriteInteger('Style', integer(ABrush.Style), 0);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteColor(const AName: UTF8String; AValue, ADefault: TColor);
|
|
begin
|
|
if WriteOnDefault or (AValue <> ADefault) then
|
|
WriteHex(AName, ColorToRGB(AValue), 8, 0);
|
|
end;
|
|
{$endif USEGRAPHICS}
|
|
|
|
function TXmlNode.GetBinaryString: RawByteString;
|
|
begin
|
|
SetLength(Result, BufferLength);
|
|
if length(Result) > 0 then
|
|
BufferRead(Result[1], length(Result));
|
|
end;
|
|
|
|
procedure TXmlNode.SetBinaryString(const Value: RawByteString);
|
|
begin
|
|
if length(Value) = 0 then
|
|
begin
|
|
SetValue('');
|
|
exit;
|
|
end;
|
|
// fill the buffer
|
|
BufferWrite(Value[1], length(Value));
|
|
end;
|
|
|
|
function TXmlNode.GetEolStyle: TsdEolStyle;
|
|
begin
|
|
if assigned(FOwner) then
|
|
Result := TNativeXml(FOwner).FEolStyle
|
|
else
|
|
Result := cDefaultEolStyle;
|
|
end;
|
|
|
|
function TXmlNode.GetPreserveWhiteSpace: boolean;
|
|
begin
|
|
if assigned(FOwner) then
|
|
Result := TNativeXml(FOwner).GetPreserveWhiteSpace
|
|
else
|
|
Result := True;
|
|
end;
|
|
|
|
function TXmlNode.GetXmlFormat: TXmlFormatType;
|
|
begin
|
|
if assigned(FOwner) then
|
|
Result := TNativeXml(FOwner).FXmlFormat
|
|
else
|
|
Result := cDefaultXmlFormat;
|
|
end;
|
|
|
|
procedure TXmlNode.DoNodeLoaded(ANode: TXmlNode);
|
|
begin
|
|
if assigned(FOwner) then
|
|
TNativeXml(FOwner).DoNodeLoaded(ANode);
|
|
end;
|
|
|
|
procedure TXmlNode.DoNodeNew(ANode: TXmlNode);
|
|
begin
|
|
if assigned(FOwner) then
|
|
TNativeXml(FOwner).DoNodeNew(ANode);
|
|
end;
|
|
|
|
function TXmlNode.GetContent: Utf8String;
|
|
var
|
|
S: TsdStringStream;
|
|
begin
|
|
S := TsdStringStream.Create('');
|
|
try
|
|
WriteContent(S);
|
|
Result := S.DataString;
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.WriteContent(S: TStream);
|
|
begin
|
|
// functionality in descendants
|
|
end;
|
|
|
|
function TXmlNode.NodeByAttributeValue(const NodeName, AttribName,
|
|
AttribValue: Utf8String; ShouldRecurse: boolean): TXmlNode;
|
|
// This function returns a pointer to the first subnode that has an attribute with
|
|
// name AttribName and value AttribValue.
|
|
var
|
|
i: integer;
|
|
Node: TXmlNode;
|
|
begin
|
|
Result := nil;
|
|
// Find all nodes that are potential results
|
|
for i := 0 to NodeCount - 1 do
|
|
begin
|
|
Node := Nodes[i];
|
|
if (UTF8CompareText(Node.Name, NodeName) = 0) and
|
|
Node.HasAttribute(AttribName) and
|
|
(UTF8CompareText(Node.AttributeValueByName[AttribName], AttribValue) = 0) then
|
|
begin
|
|
Result := Node;
|
|
exit;
|
|
end;
|
|
// Recursive call
|
|
if ShouldRecurse then
|
|
Result := Node.NodeByAttributeValue(NodeName, AttribName, AttribValue, True);
|
|
if assigned(Result) then
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
function TXmlNode.HasAttribute(const AName: Utf8String): boolean;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to AttributeCount - 1 do
|
|
if AttributeName[i] = AName then
|
|
begin
|
|
Result := True;
|
|
exit;
|
|
end;
|
|
Result := False;
|
|
end;
|
|
|
|
procedure TXmlNode.Clear;
|
|
begin
|
|
// functionality in descendants
|
|
end;
|
|
|
|
procedure TXmlNode.DeleteEmptyNodes;
|
|
var
|
|
i: integer;
|
|
Node: TXmlNode;
|
|
begin
|
|
for i := NodeCount - 1 downto 0 do
|
|
begin
|
|
Node := Nodes[i];
|
|
// Recursive call
|
|
Node.DeleteEmptyNodes;
|
|
// Check if we should delete child node
|
|
if Node.IsEmpty then
|
|
NodeDelete(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.Assign(Source: TPersistent);
|
|
begin
|
|
if Source is TXmlNode then
|
|
begin
|
|
CopyFrom(TXmlNode(Source));
|
|
end else
|
|
inherited;
|
|
end;
|
|
|
|
function TXmlNode.WriteToString: Utf8String;
|
|
var
|
|
SS: TsdStringStream;
|
|
begin
|
|
SS := TsdStringStream.Create('');
|
|
try
|
|
WriteStream(SS);
|
|
Result := SS.DataString;
|
|
finally
|
|
SS.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.CopyFrom(ANode: TObject);
|
|
begin
|
|
if not (ANode is TXmlNode) then
|
|
raise Exception.Create(sCopyFromOnlyWithXmlNode);
|
|
|
|
Clear;
|
|
// other functionality is in descendants
|
|
end;
|
|
|
|
function TXmlNode.FindNode(const NodeName: Utf8String): TXmlNode;
|
|
// Find the first node which has name NodeName. Contrary to the NodeByName
|
|
// function, this function will search the whole subnode tree, using the
|
|
// DepthFirst method.
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := nil;
|
|
// Loop through all subnodes
|
|
for i := 0 to NodeCount - 1 do
|
|
begin
|
|
Result := Nodes[i];
|
|
// If the subnode has name NodeName then we have a result, exit
|
|
if Result.CompareNodeName(NodeName) = 0 then
|
|
exit;
|
|
// If not, we will search the subtree of this node
|
|
Result := Result.FindNode(NodeName);
|
|
if assigned(Result) then
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.FindNodes(const NodeName: Utf8String; const AList: TList);
|
|
// local
|
|
procedure FindNodesRecursive(ANode: TXmlNode; AList: TList);
|
|
var
|
|
i: integer;
|
|
SubNode: TXmlNode;
|
|
begin
|
|
for i := 0 to ANode.NodeCount - 1 do
|
|
begin
|
|
SubNode := ANode.Nodes[i];
|
|
if SubNode.CompareNodeName(NodeName) = 0 then
|
|
AList.Add(SubNode);
|
|
FindNodesRecursive(SubNode, AList);
|
|
end;
|
|
end;
|
|
// main
|
|
begin
|
|
AList.Clear;
|
|
FindNodesRecursive(Self, AList);
|
|
end;
|
|
|
|
function TXmlNode.CompareNodeName(const NodeName: Utf8String): integer;
|
|
begin
|
|
// Compare with FullPath or local name based on NodeName's first character
|
|
if length(NodeName) > 0 then
|
|
begin
|
|
if NodeName[1] = '/' then
|
|
begin
|
|
// FullPath
|
|
Result := Utf8CompareText(FullPath, NodeName);
|
|
exit;
|
|
end;
|
|
end;
|
|
// local name
|
|
Result := Utf8CompareText(Name, NodeName);
|
|
end;
|
|
|
|
function TXmlNode.GetFullPath: Utf8String;
|
|
// GetFullpath will return the complete path of the node from the root, e.g.
|
|
// /Root/SubNode1/SubNode2/ThisNode
|
|
begin
|
|
Result := '/' + Name;
|
|
if Treedepth > 0 then
|
|
// Recursive call
|
|
Result := Parent.GetFullPath + Result;
|
|
end;
|
|
|
|
procedure TXmlNode.Delete;
|
|
begin
|
|
if assigned(Parent) then
|
|
Parent.NodeRemove(Self);
|
|
end;
|
|
|
|
function TXmlNode.GetDirectNodeCount: integer;
|
|
begin
|
|
// functionality in descendants
|
|
Result := 0;
|
|
end;
|
|
|
|
function TXmlNode.GetContainerCount: integer;
|
|
begin
|
|
// functionality in descendants
|
|
Result := 0;
|
|
end;
|
|
|
|
function TXmlNode.GetContainers(Index: integer): TXmlNode;
|
|
begin
|
|
// functionality in descendants
|
|
Result := nil;
|
|
end;
|
|
|
|
function TXmlNode.GetElementCount: integer;
|
|
begin
|
|
// functionality in descendants
|
|
Result := 0;
|
|
end;
|
|
|
|
function TXmlNode.GetElements(Index: integer): TsdElement;
|
|
begin
|
|
// functionality in descendants
|
|
Result := nil;
|
|
end;
|
|
|
|
function TXmlNode.GetDocument: TNativeXml;
|
|
begin
|
|
if FOwner is TNativeXml then
|
|
Result := TNativeXml(FOwner)
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
procedure TXmlNode.SetAttributeValueByNameWide(const AName: Utf8String; const Value: UnicodeString);
|
|
begin
|
|
AttributeValueByName[AName] := sdWidetoUTF8(Value);
|
|
end;
|
|
|
|
function TXmlNode.GetAttributeValueByNameWide(const AName: Utf8String): UnicodeString;
|
|
begin
|
|
Result := sdUtf8ToWide(AttributeValueByName[AName]);
|
|
end;
|
|
|
|
function TXmlNode.IndexInParent: integer;
|
|
// Retrieve our index in the parent's nodelist
|
|
begin
|
|
Result := -1;
|
|
if assigned(Parent) then
|
|
Result := Parent.NodeIndexOf(Self);
|
|
end;
|
|
|
|
{$ifdef D7UP}
|
|
function TXmlNode.NodeByAttributeValue(const NodeName, AttribName: Utf8String; const AttribValue: UnicodeString;
|
|
ShouldRecurse: boolean): TXmlNode;
|
|
begin
|
|
Result := NodeByAttributeValue(NodeName, AttribName, sdWideToUtf8(AttribValue), ShouldRecurse);
|
|
end;
|
|
{$endif D7UP}
|
|
|
|
procedure TXmlNode.SortChildNodes(Compare: TXmlNodeCompareFunction);
|
|
// Sort the child nodes using the quicksort algorithm
|
|
//local
|
|
function DoNodeCompare(Node1, Node2: TXmlNode): integer;
|
|
begin
|
|
if assigned(Compare) then
|
|
Result := Compare(Node1, Node2)
|
|
else
|
|
Result := Utf8CompareText(Node1.Name, Node2.Name);
|
|
end;
|
|
// local
|
|
procedure QuickSort(iLo, iHi: Integer);
|
|
var
|
|
Lo, Hi, Mid: longint;
|
|
begin
|
|
Lo := iLo;
|
|
Hi := iHi;
|
|
Mid:= (Lo + Hi) div 2;
|
|
repeat
|
|
while DoNodeCompare(Nodes[Lo], Nodes[Mid]) < 0 do
|
|
Inc(Lo);
|
|
while DoNodeCompare(Nodes[Hi], Nodes[Mid]) > 0 do
|
|
Dec(Hi);
|
|
if Lo <= Hi then
|
|
begin
|
|
// Swap pointers;
|
|
NodeExchange(Lo, Hi);
|
|
if Mid = Lo then
|
|
Mid := Hi
|
|
else
|
|
if Mid = Hi then
|
|
Mid := Lo;
|
|
Inc(Lo);
|
|
Dec(Hi);
|
|
end;
|
|
until Lo > Hi;
|
|
if Hi > iLo then
|
|
QuickSort(iLo, Hi);
|
|
if Lo < iHi then
|
|
QuickSort(Lo, iHi);
|
|
end;
|
|
// main
|
|
begin
|
|
if NodeCount > 1 then
|
|
QuickSort(0, NodeCount - 1);
|
|
end;
|
|
|
|
procedure TXmlNode.AttributesClear;
|
|
begin
|
|
while AttributeCount > 0 do
|
|
begin
|
|
AttributeDelete(0);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.AttributeDelete(Index: integer);
|
|
var
|
|
Attribute: TsdAttribute;
|
|
begin
|
|
Attribute := Attributes[Index];
|
|
if assigned(Attribute) then
|
|
NodeRemove(Attribute);
|
|
end;
|
|
|
|
procedure TXmlNode.ElementsClear;
|
|
begin
|
|
while ElementCount > 0 do
|
|
begin
|
|
ElementDelete(0);
|
|
end;
|
|
end;
|
|
|
|
procedure TXmlNode.ElementDelete(Index: integer);
|
|
var
|
|
Element: TsdElement;
|
|
begin
|
|
Element := Elements[Index];
|
|
if assigned(Element) then
|
|
NodeRemove(Element);
|
|
end;
|
|
|
|
function TXmlNode.NextSibling(ANode: TXmlNode): TXmlNode;
|
|
begin
|
|
// default is nil, iterating only starts from TsdContainerNode
|
|
Result := nil;
|
|
end;
|
|
|
|
function TXmlNode.GetSourcePos: int64;
|
|
begin
|
|
{$ifdef SOURCEPOS}
|
|
Result := FSourcePos;
|
|
{$else SOURCEPOS}
|
|
Result := 0;
|
|
{$endif SOURCEPOS}
|
|
end;
|
|
|
|
{ TsdCharData }
|
|
|
|
destructor TsdCharData.Destroy;
|
|
begin
|
|
FValueID := 0;
|
|
inherited;
|
|
end;
|
|
|
|
function TsdCharData.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeCharData;
|
|
end;
|
|
|
|
function TsdCharData.GetName: Utf8String;
|
|
begin
|
|
Result := ElementTypeName;
|
|
end;
|
|
|
|
function TsdCharData.GetCoreValue: Utf8String;
|
|
begin
|
|
Result := GetString(FValueID);
|
|
end;
|
|
|
|
function TsdCharData.GetPlatformValue: Utf8String;
|
|
begin
|
|
Result := sdUnNormaliseEol(GetCoreValue, GetEolStyle);
|
|
end;
|
|
|
|
function TsdCharData.GetValue: Utf8String;
|
|
begin
|
|
// value is the replaced, eol-unnormalized corevalue
|
|
Result := sdReplaceString(
|
|
sdUnNormaliseEol(GetCoreValue, GetEolStyle));
|
|
end;
|
|
|
|
function TsdCharData.GetValueUsingReferences(Nodes: array of TXmlNode): Utf8String;
|
|
var
|
|
HasNonStandardReferences: boolean;
|
|
begin
|
|
Result := sdReplaceString(
|
|
sdUnNormaliseEol(GetCoreValue, GetEolStyle),
|
|
HasNonStandardReferences,
|
|
Nodes);
|
|
end;
|
|
|
|
procedure TsdCharData.SetCoreValue(const Value: Utf8String);
|
|
begin
|
|
FValueID := AddString(Value);
|
|
end;
|
|
|
|
procedure TsdCharData.SetValue(const Value: Utf8String);
|
|
begin
|
|
// core value is the escaped, eol-normalized value
|
|
SetCoreValue(sdEscapeString(
|
|
sdNormaliseEol(Value)))
|
|
end;
|
|
|
|
procedure TsdCharData.WriteStream(S: TStream);
|
|
begin
|
|
// write the chardata: the platform value is the
|
|
sdWriteToStream(S, GetPlatformValue);
|
|
// sdWriteToStream(S, GetCoreValue);
|
|
end;
|
|
|
|
procedure TsdCharData.SetName(const Value: Utf8String);
|
|
begin
|
|
// since the API is general with LINQ style, we allow a setter but in the
|
|
// XML the chardata name will not be present
|
|
if Length(Value) > 0 then
|
|
DoDebugOut(Self, wsHint, sCannotSetName);
|
|
end;
|
|
|
|
procedure TsdCharData.CopyFrom(ANode: TObject);
|
|
begin
|
|
inherited;
|
|
SetCoreValue(TsdCharData(ANode).GetCoreValue);
|
|
end;
|
|
|
|
function TsdCharData.HasNonStandardReferences: boolean;
|
|
var
|
|
Res: boolean;
|
|
begin
|
|
sdReplaceString(GetCoreValue, Res);
|
|
Result := Res;
|
|
end;
|
|
|
|
function TsdCharData.IsWhiteSpace: boolean;
|
|
var
|
|
S: Utf8String;
|
|
begin
|
|
S := TNativeXml(FOwner).FSymbolTable.GetString(FValueID);
|
|
S := sdTrim(S, Result);
|
|
end;
|
|
|
|
{ TsdWhitespace }
|
|
|
|
function TsdWhitespace.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeWhiteSpace;
|
|
end;
|
|
|
|
{ TsdAttribute }
|
|
|
|
procedure TsdAttribute.CopyFrom(ANode: TObject);
|
|
begin
|
|
inherited;
|
|
// copy local data
|
|
FCoreValue.CopyFrom(TsdAttribute(ANode).FCoreValue);
|
|
SetName(TsdAttribute(ANode).GetName);
|
|
end;
|
|
|
|
constructor TsdAttribute.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FCoreValue := TsdQuotedText.Create(AOwner);
|
|
FCoreValue.FParent := Self;
|
|
end;
|
|
|
|
destructor TsdAttribute.Destroy;
|
|
begin
|
|
FNameID := 0;
|
|
FreeAndNil(FCoreValue);
|
|
inherited;
|
|
end;
|
|
|
|
function TsdAttribute.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeAttribute;
|
|
end;
|
|
|
|
function TsdAttribute.GetName: Utf8String;
|
|
begin
|
|
Result := GetString(FNameID);
|
|
end;
|
|
|
|
function TsdAttribute.GetValue: Utf8String;
|
|
begin
|
|
if assigned(FCoreValue) then
|
|
Result := sdReplaceString(FCoreValue.GetCoreValue)
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function TsdAttribute.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
var
|
|
IsTrimmed: boolean;
|
|
begin
|
|
Result := Self;
|
|
{$ifdef SOURCEPOS}
|
|
FSourcePos := P.Position;
|
|
{$endif SOURCEPOS}
|
|
// Get the attribute name
|
|
FNameID := AddString(sdTrim(P.ReadStringUntilChar('='), IsTrimmed));
|
|
if assigned(FCoreValue) then
|
|
// value
|
|
FCoreValue.ParseStream(P);
|
|
end;
|
|
|
|
procedure TsdAttribute.SetName(const Value: Utf8String);
|
|
begin
|
|
FNameID := AddString(Value);
|
|
end;
|
|
|
|
procedure TsdAttribute.SetValue(const Value: Utf8String);
|
|
begin
|
|
// FCoreValue is directly created in TsdAttribute.Create, so safe
|
|
FCoreValue.SetCoreValue(sdEscapeString(Value));
|
|
end;
|
|
|
|
procedure TsdAttribute.WriteStream(S: TStream);
|
|
begin
|
|
sdWriteToStream(S, GetName + '=');
|
|
// now add the quoted value
|
|
if assigned(FCoreValue) then
|
|
FCoreValue.WriteStream(S);
|
|
end;
|
|
|
|
{ TsdQuotedText }
|
|
|
|
procedure TsdQuotedText.CopyFrom(ANode: TObject);
|
|
begin
|
|
inherited;
|
|
FQuoteStyle := TsdQuotedText(ANode).FQuoteStyle;
|
|
end;
|
|
|
|
constructor TsdQuotedText.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FQuoteStyle := qsQuote;
|
|
end;
|
|
|
|
function TsdQuotedText.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeQuotedText;
|
|
end;
|
|
|
|
function TsdQuotedText.GetName: Utf8String;
|
|
begin
|
|
Result := ElementTypeName;
|
|
end;
|
|
|
|
function TsdQuotedText.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
var
|
|
Blanks: Utf8String;
|
|
QuoteChar: AnsiChar;
|
|
begin
|
|
Result := Self;
|
|
// Get the quoted value
|
|
QuoteChar := P.NextCharSkipBlanks(Blanks);
|
|
if QuoteChar = cQuoteCharStyleNames[qsQuote] then
|
|
FQuoteStyle := qsQuote
|
|
else
|
|
if QuoteChar = cQuoteCharStyleNames[qsApos] then
|
|
FQuoteStyle := qsApos
|
|
else
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sQuoteCharExpected, [P.Position]));
|
|
if Document.FixStructuralErrors then
|
|
begin
|
|
// an unquoted value.. we try to read till space
|
|
QuoteChar := ' ';
|
|
P.MoveBack;
|
|
end else
|
|
exit;
|
|
end;
|
|
|
|
FValueID := AddString(P.ReadQuotedString(QuoteChar));
|
|
end;
|
|
|
|
procedure TsdQuotedText.WriteStream(S: TStream);
|
|
var
|
|
QC: Utf8String;
|
|
begin
|
|
QC := cQuoteCharStyleNames[FQuoteStyle];
|
|
sdWriteToStream(S, QC + GetPlatformValue + QC);
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdContainerNode }
|
|
|
|
constructor TsdContainerNode.Create(AOwner: TComponent);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FNodes := TsdNodeList.Create(True);
|
|
FDirectNodeCount := 0;
|
|
FValueIndex := -1;
|
|
end;
|
|
|
|
destructor TsdContainerNode.Destroy;
|
|
begin
|
|
FreeAndNil(FNodes);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TsdContainerNode.AttributeAdd(AAttribute: TsdAttribute);
|
|
begin
|
|
if (AAttribute = nil) or (AAttribute.FOwner <> FOwner) then
|
|
begin
|
|
DoDebugOut(Self, wsFail, sXmlOwnerNotAssigned);
|
|
exit;
|
|
end;
|
|
NodeInsert(FDirectNodeCount, AAttribute);
|
|
inc(FDirectNodeCount);
|
|
end;
|
|
|
|
function TsdContainerNode.FirstNodeByType(AType: TsdElementType): TXmlNode;
|
|
begin
|
|
Result := FNodes.ByType(AType);
|
|
end;
|
|
|
|
function TsdContainerNode.GetNodes(Index: integer): TXmlNode;
|
|
begin
|
|
if (Index >= 0) and (Index < FNodes.Count) then
|
|
Result := FNodes[Index]
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TsdContainerNode.HasSubContainers: boolean;
|
|
var
|
|
i: integer;
|
|
begin
|
|
// determine if there is at least one subcontainer
|
|
Result := False;
|
|
for i := FDirectNodeCount to FNodes.Count - 1 do
|
|
begin
|
|
if FNodes[i] is TsdContainerNode then
|
|
begin
|
|
Result := True;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TsdContainerNode.NodeAdd(ANode: TXmlNode): integer;
|
|
begin
|
|
Result := -1;
|
|
if not assigned(ANode) then
|
|
exit;
|
|
|
|
// by default add at the end of the list
|
|
Result := FNodes.Add(ANode);
|
|
ANode.FParent := Self;
|
|
end;
|
|
|
|
function TsdContainerNode.GetNodeCount: integer;
|
|
begin
|
|
Result := FNodes.Count
|
|
end;
|
|
|
|
procedure TsdContainerNode.NodeDelete(Index: integer);
|
|
begin
|
|
FNodes.Delete(Index);
|
|
|
|
// BUGFIX: node index below FDirectNodeCount: decrease FDirectNodeCount
|
|
// ("below" instead of "below or on")
|
|
if Index < FDirectNodeCount then
|
|
dec(FDirectNodeCount);
|
|
|
|
if FValueIndex >= 0 then
|
|
begin
|
|
// node index = FValueIndex: set FValueIndex to -1
|
|
if Index = FValueIndex then
|
|
begin
|
|
FValueIndex := -1;
|
|
exit;
|
|
end;
|
|
// node index below FValueIndex: decrease FValueIndex
|
|
if Index < FValueIndex then
|
|
dec(FValueIndex);
|
|
end;
|
|
end;
|
|
|
|
procedure TsdContainerNode.NodeExchange(Index1, Index2: integer);
|
|
begin
|
|
FNodes.Exchange(Index1, Index2);
|
|
end;
|
|
|
|
function TsdContainerNode.NodeExtract(ANode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := TXmlNode(FNodes.Extract(ANode));
|
|
end;
|
|
|
|
function TsdContainerNode.NodeIndexOf(ANode: TXmlNode): integer;
|
|
begin
|
|
Result := FNodes.IndexOf(ANode);
|
|
end;
|
|
|
|
procedure TsdContainerNode.NodesClear;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := NodeCount - 1 downto 0 do
|
|
begin
|
|
NodeDelete(i);
|
|
end;
|
|
FDirectNodeCount := 0;
|
|
FValueIndex := -1;
|
|
end;
|
|
|
|
procedure TsdContainerNode.NodeInsert(Index: integer; ANode: TXmlNode);
|
|
begin
|
|
FNodes.Insert(Index, ANode);
|
|
ANode.FParent := Self;
|
|
end;
|
|
|
|
procedure TsdContainerNode.NodeInsertNear(ANode, AOther: TXmlNode; IsBefore: boolean);
|
|
var
|
|
Idx: integer;
|
|
begin
|
|
Idx := NodeIndexOf(AOther);
|
|
if IsBefore then
|
|
NodeInsert(Idx, ANode)
|
|
else
|
|
NodeInsert(Idx + 1, ANode);
|
|
end;
|
|
|
|
function TsdContainerNode.ParseAttributeList(P: TsdXmlParser): AnsiChar;
|
|
var
|
|
Blanks: Utf8String;
|
|
AttributeNode: TsdAttribute;
|
|
WhiteSpaceNode: TsdWhiteSpace;
|
|
begin
|
|
repeat
|
|
Result := P.NextCharSkipBlanks(Blanks);
|
|
if Length(Blanks) > 0 then
|
|
begin
|
|
if Blanks <> ' ' then
|
|
begin
|
|
DoDebugOut(Self, wsHint, Format(sNonDefaultChardata, [P.LineNumber, P.Position]));
|
|
// add non-default blank chardata in attribute list
|
|
if GetPreserveWhiteSpace then
|
|
begin
|
|
WhiteSpaceNode := TsdWhiteSpace.Create(TNativeXml(FOwner));
|
|
NodeAdd(WhiteSpaceNode);
|
|
inc(FDirectNodeCount);
|
|
WhiteSpaceNode.SetCoreValue(Blanks);
|
|
end;
|
|
end
|
|
end;
|
|
|
|
// Are any of the characters determining the end?
|
|
if Result in ['!', '/', '>' ,'?'] then
|
|
exit;
|
|
|
|
// regular user error, signal this
|
|
if Result in cQuoteChars then
|
|
begin
|
|
|
|
DoDebugOut(Self, wsWarn, format('illegal quote at pos %d', [P.Position]));
|
|
|
|
end else
|
|
begin
|
|
|
|
P.MoveBack;
|
|
AttributeNode := TsdAttribute.Create(TNativeXml(FOwner));
|
|
AttributeAdd(AttributeNode);
|
|
DoNodeNew(AttributeNode);
|
|
AttributeNode.ParseStream(P);
|
|
DoNodeLoaded(AttributeNode);
|
|
|
|
end;
|
|
until P.EndOfStream;
|
|
end;
|
|
|
|
function TsdContainerNode.ParseFixStructuralErrors(const AEndTagName: Utf8String): TXmlNode;
|
|
var
|
|
Depth: integer;
|
|
DeeperNodeName: Utf8String;
|
|
Idx: integer;
|
|
begin
|
|
// check if there is a parent node with this name that is already parsed
|
|
Depth := 0;
|
|
repeat
|
|
if assigned(FParent) then
|
|
DeeperNodeName := FParent.Name
|
|
else
|
|
DeeperNodeName := '';
|
|
|
|
if DeeperNodeName = AEndTagName then
|
|
begin
|
|
// this is the parent's node name, so we must defer execution to the parent
|
|
DoDebugOut(Self, wsHint,
|
|
Format('parent%d = "%s", this endtag = "%s": maybe "%s" should be closed',
|
|
[Depth, DeeperNodeName, AEndTagName, GetName]));
|
|
|
|
// add to the single tag names list
|
|
if not TNativeXml(FOwner).FSingleTagNames.Find(GetName, Idx) then
|
|
TNativeXml(FOwner).FSingleTagNames.Add(GetName);
|
|
|
|
// we now break
|
|
break;
|
|
end;
|
|
|
|
// move the node to a lower hierarchy
|
|
if assigned(FParent) and assigned(FParent.Parent) then
|
|
begin
|
|
DoDebugOut(Self, wsInfo,
|
|
Format('moving node "%s" from parent "%s" to grandparent "%s"',
|
|
[GetName, FParent.Name, FParent.Parent.Name]));
|
|
|
|
// add to the single tag names list
|
|
if not TNativeXml(FOwner).FSingleTagNames.Find(Parent.Name, Idx) then
|
|
TNativeXml(FOwner).FSingleTagNames.Add(Parent.Parent.Name);
|
|
|
|
FParent.NodeExtract(Self);
|
|
FParent.Parent.NodeAdd(Self);
|
|
end;
|
|
|
|
inc(Depth);
|
|
until Length(DeeperNodeName) = 0;
|
|
|
|
// signal that this parser hierarchy is no longer valid
|
|
Result := FParent;
|
|
end;
|
|
|
|
function TsdContainerNode.ParseElementList(P: TsdXmlParser; const SupportedTags: TsdElementTypes): TXmlNode;
|
|
// parse the element list, the result (endnode) should be this element
|
|
var
|
|
B: AnsiChar;
|
|
BeginTagName, EndTagName: Utf8String;
|
|
Tag: TsdElementType;
|
|
NodeClass: TsdNodeClass;
|
|
SubNode, EndNode: TXmlNode;
|
|
EndNodeName: Utf8String;
|
|
IsTrimmed: boolean;
|
|
begin
|
|
Result := nil;
|
|
|
|
repeat
|
|
// Process char data
|
|
ParseIntermediateData(P);
|
|
|
|
// Process subtags and end tag
|
|
if P.EndOfStream then
|
|
begin
|
|
DoDebugOut(Self, wsFail, Format(sPrematureEnd, [P.Position]));
|
|
exit;
|
|
end;
|
|
P.MoveBack;
|
|
|
|
B := P.NextChar;
|
|
if B = '<' then
|
|
begin
|
|
|
|
// Determine tag type
|
|
Tag := P.ReadOpenTag;
|
|
if not (Tag in SupportedTags) then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalTag, [cElementTypeNames[Tag], P.Position]));
|
|
exit;
|
|
end;
|
|
|
|
// End tag?
|
|
if Tag = xeEndTag then
|
|
begin
|
|
// up front this is the end tag so the result is this node
|
|
Result := Self;
|
|
|
|
// Read end tag
|
|
EndTagName := sdTrim(P.ReadStringUntilChar('>'), IsTrimmed);
|
|
FNodeClosingStyle := ncFull;
|
|
|
|
// Check if begin and end tags match
|
|
if GetName <> EndTagName then
|
|
begin
|
|
BeginTagName := GetName;
|
|
|
|
// usually a user error with omitted direct end tag
|
|
DoDebugOut(Self, wsWarn, Format(sBeginEndMismatch,
|
|
[GetName, EndTagName, P.LineNumber, P.Position]));
|
|
|
|
// try to fix structural errors?
|
|
if TNativeXml(FOwner).FFixStructuralErrors then
|
|
Result := ParseFixStructuralErrors(EndTagName);
|
|
|
|
end;
|
|
|
|
// We're done reading this element, so we will set the capacity of the
|
|
// nodelist to just the amount of items to avoid having overhead.
|
|
FNodes.SetCapacity(FNodes.Count);
|
|
exit;
|
|
end;
|
|
|
|
// Determine node class
|
|
NodeClass := cNodeClass[Tag];
|
|
if not assigned(NodeClass) then
|
|
begin
|
|
DoDebugOut(Self, wsfail, format(sUnsupportedTag, [P.Position]));
|
|
exit;
|
|
end;
|
|
|
|
// Create new node and add
|
|
SubNode := NodeClass.Create(TNativeXml(FOwner));
|
|
NodeAdd(SubNode);
|
|
if Tag <> xeElement then
|
|
DoNodeNew(SubNode);
|
|
|
|
// The node will parse itself
|
|
EndNode := SubNode.ParseStream(P);
|
|
if EndNode <> SubNode then
|
|
begin
|
|
if assigned(EndNode) then
|
|
EndNodeName := EndNode.GetName
|
|
else
|
|
EndNodeName := 'nil';
|
|
DoDebugOut(Self, wsWarn, Format(sLevelMismatch,
|
|
[SubNode.GetName, EndNodeName, P.LineNumber, P.Position]));
|
|
Result := EndNode;
|
|
Exit;
|
|
end;
|
|
|
|
// CDATA subnodes could provide the value of the element
|
|
if SubNode is TsdCData then
|
|
begin
|
|
if FValueIndex < 0 then
|
|
FValueIndex := FNodes.Count - 1;
|
|
end;
|
|
|
|
DoNodeLoaded(SubNode);
|
|
|
|
end else
|
|
begin
|
|
// Since this virtual proc is also used for doctype parsing.. check
|
|
// end char here
|
|
if (B = ']') and (ElementType = xeDocType) then
|
|
break;
|
|
end;
|
|
until TNativeXml(FOwner).FAbortParsing or P.EndOfStream;
|
|
end;
|
|
|
|
procedure TsdContainerNode.ParseIntermediateData(P: TsdXmlParser);
|
|
begin
|
|
// default does nothing
|
|
end;
|
|
|
|
function TsdContainerNode.ParseQuotedTextList(P: TsdXmlParser): AnsiChar;
|
|
var
|
|
Blanks: Utf8String;
|
|
QuotedTextNode: TsdQuotedText;
|
|
begin
|
|
repeat
|
|
Result := P.NextCharSkipBlanks(Blanks);
|
|
if (Length(Blanks) > 0) and (Blanks <> ' ') then
|
|
begin
|
|
DoDebugOut(Self, wsHint, Format(sNonDefaultChardata, [P.Position]));
|
|
end;
|
|
|
|
// Are any of the characters determining the end?
|
|
//if Result in ['!', '/', '>' ,'?'] then
|
|
// avoid question mark "?" in quoted text (this proc only called by TsdDtdElement)
|
|
if Result in ['!', '/', '>'] then
|
|
exit;
|
|
|
|
P.MoveBack;
|
|
QuotedTextNode := TsdQuotedText.Create(TNativeXml(FOwner));
|
|
NodeAdd(QuotedTextNode);
|
|
DoNodeNew(QuotedTextNode);
|
|
QuotedTextNode.ParseStream(P);
|
|
DoNodeLoaded(QuotedTextNode);
|
|
until P.EndOfStream;
|
|
end;
|
|
|
|
procedure TsdContainerNode.WriteAttributeList(S: TStream; Count: integer);
|
|
var
|
|
i: integer;
|
|
PrevSubNode, ThisSubNode: TXmlNode;
|
|
begin
|
|
PrevSubNode := nil;
|
|
for i := 0 to Count - 1 do
|
|
begin
|
|
ThisSubNode := FNodes[i];
|
|
// write attributes and intermingled chardata
|
|
if ThisSubNode is TsdAttribute then
|
|
begin
|
|
if not (PrevSubNode is TsdCharData) then
|
|
// write blank if there is no previous chardata
|
|
sdWriteToStream(S, ' ');
|
|
// write attribute
|
|
ThisSubNode.WriteStream(S);
|
|
end;
|
|
if ThisSubNode is TsdCharData then
|
|
begin
|
|
// write chardata
|
|
ThisSubNode.WriteStream(S);
|
|
end;
|
|
|
|
// next iteration
|
|
PrevSubNode := ThisSubNode;
|
|
end;
|
|
end;
|
|
|
|
procedure TsdContainerNode.Clear;
|
|
begin
|
|
inherited;
|
|
FNodes.Clear;
|
|
FDirectNodeCount := 0;
|
|
FValueIndex := -1;
|
|
end;
|
|
|
|
procedure TsdContainerNode.CopyFrom(ANode: TObject);
|
|
var
|
|
i: integer;
|
|
ThisSubNode, ThatSubNode: TXmlNode;
|
|
NodeClass: TsdNodeClass;
|
|
begin
|
|
inherited;
|
|
|
|
// copy nodes
|
|
for i := 0 to TsdContainerNode(ANode).FNodes.Count - 1 do
|
|
begin
|
|
ThatSubNode := TsdContainerNode(ANode).FNodes[i];
|
|
NodeClass := TsdNodeClass(ThatSubNode.ClassType);
|
|
ThisSubNode := NodeClass.Create(TNativeXml(FOwner));
|
|
FNodes.Add(ThisSubNode);
|
|
ThisSubNode.FParent := Self;
|
|
ThisSubNode.CopyFrom(ThatSubNode);
|
|
end;
|
|
|
|
// copy local data
|
|
FDirectNodeCount := TsdContainerNode(ANode).FDirectNodeCount;
|
|
FValueIndex := TsdContainerNode(ANode).FValueIndex;
|
|
end;
|
|
|
|
function TsdContainerNode.GetContainers(Index: integer): TXmlNode;
|
|
var
|
|
i, Idx: integer;
|
|
begin
|
|
Result := nil;
|
|
Idx := 0;
|
|
for i := FDirectNodeCount to FNodes.Count - 1 do
|
|
begin
|
|
if FNodes[i] is TsdContainerNode then
|
|
begin
|
|
if Idx = Index then
|
|
begin
|
|
Result := TsdContainerNode(FNodes[i]);
|
|
exit;
|
|
end;
|
|
inc(Idx);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TsdContainerNode.GetContainerCount: integer;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := 0;
|
|
for i := FDirectNodeCount to FNodes.Count - 1 do
|
|
begin
|
|
if FNodes[i] is TsdContainerNode then
|
|
inc(Result);
|
|
end;
|
|
end;
|
|
|
|
function TsdContainerNode.GetElements(Index: integer): TsdElement;
|
|
var
|
|
i, Idx: integer;
|
|
begin
|
|
Result := nil;
|
|
Idx := 0;
|
|
for i := FDirectNodeCount to FNodes.Count - 1 do
|
|
begin
|
|
if FNodes[i] is TsdElement then
|
|
begin
|
|
if Idx = Index then
|
|
begin
|
|
Result := TsdElement(FNodes[i]);
|
|
exit;
|
|
end;
|
|
inc(Idx);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TsdContainerNode.GetElementCount: integer;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := 0;
|
|
for i := FDirectNodeCount to FNodes.Count - 1 do
|
|
begin
|
|
if FNodes[i] is TsdElement then
|
|
inc(Result);
|
|
end;
|
|
end;
|
|
|
|
function TsdContainerNode.GetDirectNodeCount: integer;
|
|
begin
|
|
Result := FDirectNodeCount;
|
|
end;
|
|
|
|
function TsdContainerNode.NextSibling(ANode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := FNodes.GetNextSiblingOf(ANode);
|
|
end;
|
|
|
|
function TsdContainerNode.GetNodeClosingStyle: TsdNodeClosingStyle;
|
|
begin
|
|
Result := TNativeXml(FOwner).NodeClosingStyle;
|
|
if Result = ncDefault then
|
|
Result := FNodeClosingStyle;
|
|
end;
|
|
|
|
{ TsdElement }
|
|
|
|
procedure TsdElement.CopyFrom(ANode: TObject);
|
|
begin
|
|
inherited;
|
|
// copy other data
|
|
SetName(TsdElement(ANode).GetName);
|
|
FNodeClosingStyle := TsdElement(ANode).FNodeClosingStyle;
|
|
end;
|
|
|
|
function TsdElement.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeElement;
|
|
end;
|
|
|
|
function TsdElement.GetName: Utf8String;
|
|
begin
|
|
Result := GetString(FNameID);
|
|
end;
|
|
|
|
function TsdElement.GetValue: Utf8String;
|
|
begin
|
|
// Return the value of the CharData subnode designated by the parser
|
|
if (FValueIndex >= 0) and (FValueIndex < FNodes.Count) then
|
|
begin
|
|
// chardata value at FValueIndex
|
|
// This calls TsdCharData.GetValue(),
|
|
// then TsdCharData.GetCoreValue().
|
|
Result := FNodes[FValueIndex].Value;
|
|
|
|
// BUGFIX: no longer allowed, value is already unnormalized in chardata
|
|
{ // do un-normalisation if mac/windows
|
|
if GetEolStyle <> esLF then
|
|
Result := sdUnNormaliseEol(Result, GetEolStyle);}
|
|
|
|
end else
|
|
// default value
|
|
Result := '';
|
|
end;
|
|
|
|
procedure TsdElement.ParseIntermediateData(P: TsdXmlParser);
|
|
var
|
|
S, CharDataString: Utf8String;
|
|
CharDataNode: TsdCharData;
|
|
WhiteSpaceNode: TsdCharData;
|
|
{$ifdef SOURCEPOS}
|
|
SourcePos: int64;
|
|
{$endif SOURCEPOS}
|
|
PreString, PostString: Utf8String;
|
|
begin
|
|
{$ifdef SOURCEPOS}
|
|
SourcePos := P.Position;
|
|
{$endif SOURCEPOS}
|
|
|
|
S := P.ReadStringUntilChar('<');
|
|
CharDataString := sdTrim(S, PreString, PostString);
|
|
|
|
if GetPreserveWhiteSpace and (Length(PreString) > 0) then
|
|
begin
|
|
WhiteSpaceNode := TsdWhiteSpace.Create(TNativeXml(FOwner));
|
|
WhiteSpaceNode.FValueID := AddString(PreString);
|
|
NodeAdd(WhiteSpaceNode);
|
|
end;
|
|
|
|
if length(CharDataString) > 0 then
|
|
begin
|
|
// Insert CharData node
|
|
CharDataNode := TsdCharData.Create(TNativeXml(FOwner));
|
|
{$ifdef SOURCEPOS}
|
|
CharDataNode.FSourcePos := SourcePos;
|
|
{$endif SOURCEPOS}
|
|
CharDataNode.FValueID := AddString(CharDataString);
|
|
NodeAdd(CharDataNode);
|
|
|
|
// ParseIntermediateData can be called multiple times from ParseElementList.
|
|
// if there was no chardata node yet before, this is the value index
|
|
if FValueIndex = -1 then
|
|
begin
|
|
FValueIndex := FNodes.Count - 1;
|
|
end;
|
|
|
|
DoNodeNew(CharDataNode);
|
|
DoNodeLoaded(CharDataNode);
|
|
end;
|
|
|
|
if GetPreserveWhiteSpace and (Length(PostString) > 0) then
|
|
begin
|
|
WhiteSpaceNode := TsdWhiteSpace.Create(TNativeXml(FOwner));
|
|
WhiteSpaceNode.FValueID := AddString(PostString);
|
|
NodeAdd(WhiteSpaceNode);
|
|
end;
|
|
end;
|
|
|
|
function TsdElement.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
var
|
|
Ch: AnsiChar;
|
|
AName: Utf8String;
|
|
IsTrimmed: boolean;
|
|
begin
|
|
Result := Self;
|
|
|
|
// Flush the reader.
|
|
P.Flush;
|
|
|
|
// the index of the chardata subnode that will hold the value, initially -1
|
|
FValueIndex := -1;
|
|
|
|
{$ifdef SOURCEPOS}
|
|
FSourcePos := P.Position;
|
|
{$endif SOURCEPOS}
|
|
|
|
// Parse name
|
|
AName := sdTrim(P.ReadStringUntilBlankOrEndTag, IsTrimmed);
|
|
SetName(AName);
|
|
|
|
DoNodeNew(Self);
|
|
|
|
// Parse attribute list
|
|
Ch := ParseAttributeList(P);
|
|
|
|
// up till now attributes and optional chardata are direct nodes
|
|
FDirectNodeCount := FNodes.Count;
|
|
|
|
if Ch = '/' then
|
|
begin
|
|
// Direct tag
|
|
Ch := P.NextChar;
|
|
if Ch <> '>' then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalEndTag, [Ch, P.LineNumber, P.Position]));
|
|
exit;
|
|
end;
|
|
NodeClosingStyle := ncClose;
|
|
end else
|
|
begin
|
|
if Ch <> '>' then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalEndTag, [Ch, P.LineNumber, P.Position]));
|
|
exit;
|
|
end;
|
|
|
|
// parse subelements
|
|
Result := ParseElementList(P, [xeElement..xeCData, xeInstruction..xeEndTag]);
|
|
end;
|
|
|
|
// progress for elements
|
|
DoProgress(P.Position);
|
|
end;
|
|
|
|
procedure TsdElement.SetName(const Value: Utf8String);
|
|
begin
|
|
FNameID := AddString(Value);
|
|
end;
|
|
|
|
procedure TsdElement.SetValue(const Value: Utf8String);
|
|
var
|
|
Res: Utf8String;
|
|
Node: TXmlNode;
|
|
begin
|
|
if Length(Value) > 0 then
|
|
begin
|
|
// value that will be set.
|
|
Res := Value;
|
|
|
|
// add or update a value
|
|
if FValueIndex < 0 then
|
|
begin
|
|
|
|
// we do not have a value node, so we will add it after FDirectNodeCount
|
|
Node := TsdCharData.Create(TNativeXml(FOwner));
|
|
Node.Value := Res;
|
|
NodeInsert(FDirectNodeCount, Node);
|
|
FValueIndex := FDirectNodeCount;
|
|
|
|
end else
|
|
begin
|
|
|
|
// just update the value
|
|
FNodes[FValueIndex].Value := Res;
|
|
|
|
end;
|
|
end else
|
|
begin
|
|
// remove the value
|
|
if FValueIndex >= 0 then
|
|
NodeDelete(FValueIndex); // this sets it to -1 as well
|
|
end;
|
|
end;
|
|
|
|
procedure TsdElement.WriteStream(S: TStream);
|
|
var
|
|
i: integer;
|
|
SubNode: TXmlNode;
|
|
HasSubElements: boolean;
|
|
begin
|
|
// determine if there is at least one subelement
|
|
HasSubElements := HasSubContainers;
|
|
|
|
// write element
|
|
sdStreamWriteString(S, GetIndent + '<' + GetName);
|
|
|
|
// write attributes
|
|
WriteAttributeList(S, FDirectNodeCount);
|
|
|
|
if (FNodes.Count = FDirectNodeCount) and (NodeClosingStyle = ncClose) then
|
|
begin
|
|
|
|
// directly write close tag
|
|
sdStreamWriteString(S, TNativeXml(FOwner).FDirectCloseTag);
|
|
sdStreamWriteString(S, GetEndOfLine);
|
|
|
|
end else
|
|
begin
|
|
// indirect node
|
|
sdStreamWriteString(S, '>');
|
|
|
|
// write sub-nodes
|
|
for i := FDirectNodeCount to FNodes.Count - 1 do
|
|
begin
|
|
SubNode := FNodes[i];
|
|
|
|
// due to optional chardatas after the parent we use these "if"s
|
|
if (i = FDirectNodeCount) and not (SubNode is TsdCharData) then
|
|
begin
|
|
sdStreamWriteString(S, GetEndOfLine);
|
|
end;
|
|
if (i > FDirectNodeCount) and (SubNode is TsdCharData) and HasSubElements then
|
|
begin
|
|
sdStreamWriteString(S, SubNode.GetIndent);
|
|
end;
|
|
|
|
if (SubNode is TsdElement) or (SubNode is TsdCharData) then
|
|
SubNode.WriteStream(S);
|
|
|
|
if HasSubElements and (SubNode is TsdCharData) then
|
|
sdStreamWriteString(S, GetEndOfLine);
|
|
end;
|
|
|
|
// endtag
|
|
if HasSubElements then
|
|
sdStreamWriteString(S, GetIndent);
|
|
|
|
sdStreamWriteString(S, '</' + GetName + '>' + GetEndOfLine);
|
|
end;
|
|
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdDeclaration }
|
|
|
|
function TsdDeclaration.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeDeclaration;
|
|
end;
|
|
|
|
function TsdDeclaration.GetEncoding: Utf8String;
|
|
begin
|
|
Result := AttributeValueByName['encoding'];
|
|
end;
|
|
|
|
function TsdDeclaration.GetName: Utf8String;
|
|
begin
|
|
Result := 'xml';
|
|
end;
|
|
|
|
function TsdDeclaration.GetVersion: Utf8String;
|
|
begin
|
|
Result := AttributeValueByName['version'];
|
|
end;
|
|
|
|
function TsdDeclaration.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
var
|
|
B: AnsiChar;
|
|
begin
|
|
Result := Self;
|
|
|
|
// Directly parse the attribute list
|
|
B := ParseAttributeList(P);
|
|
if B <> '?' then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalEndTag, [B, P.LineNumber, P.Position]));
|
|
exit;
|
|
end;
|
|
B := P.NextChar;
|
|
if B <> '>' then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalEndTag, [B, P.LineNumber, P.Position]));
|
|
exit;
|
|
end;
|
|
|
|
// declaration is special, we check $0D and $0A and allow them at the end
|
|
B := P.NextChar;
|
|
if B <> #$0D then
|
|
P.MoveBack;
|
|
|
|
B := P.NextChar;
|
|
if B <> #$0A then
|
|
P.MoveBack;
|
|
end;
|
|
|
|
procedure TsdDeclaration.SetEncoding(const Value: Utf8String);
|
|
begin
|
|
AttributeValueByName['encoding'] := Value;
|
|
end;
|
|
|
|
procedure TsdDeclaration.SetVersion(const Value: Utf8String);
|
|
begin
|
|
AttributeValueByName['version'] := Value;
|
|
end;
|
|
|
|
procedure TsdDeclaration.WriteStream(S: TStream);
|
|
begin
|
|
// XML declaration <?xml{declaration}?>
|
|
sdWriteToStream(S, GetIndent + '<?xml');
|
|
WriteAttributeList(S, FNodes.Count);
|
|
|
|
sdWriteToStream(S, '?>');
|
|
|
|
sdWriteToStream(S, GetSeparator);
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdComment }
|
|
|
|
function TsdComment.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeComment;
|
|
end;
|
|
|
|
function TsdComment.GetName: Utf8String;
|
|
begin
|
|
Result := ElementTypeName;
|
|
end;
|
|
|
|
function TsdComment.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
begin
|
|
Result := Self;
|
|
FValueID := AddString(P.ReadStringUntil('-->'));
|
|
end;
|
|
|
|
procedure TsdComment.WriteStream(S: TStream);
|
|
begin
|
|
// Comment <!--{comment}-->
|
|
sdWriteToStream(S, '<!--' + GetCoreValue + '-->');
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdCData }
|
|
|
|
function TsdCData.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeCData;
|
|
end;
|
|
|
|
function TsdCData.GetName: Utf8String;
|
|
begin
|
|
Result := ElementTypeName;
|
|
end;
|
|
|
|
function TsdCData.GetValue: Utf8String;
|
|
begin
|
|
Result := GetString(FValueID);
|
|
end;
|
|
|
|
function TsdCData.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
begin
|
|
Result := Self;
|
|
// assumes that the "<![CDATA[" is aleady parsed
|
|
FValueID := AddString(P.ReadStringUntil(']]>'));
|
|
end;
|
|
|
|
procedure TsdCData.SetValue(const Value: Utf8String);
|
|
begin
|
|
FValueID := AddString(Value);
|
|
end;
|
|
|
|
procedure TsdCData.WriteStream(S: TStream);
|
|
begin
|
|
// literal data <![CDATA[{data}]]>
|
|
sdWriteToStream(S, '<![CDATA[' + GetCoreValue + ']]>');
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdDocType }
|
|
|
|
procedure TsdDocType.CopyFrom(ANode: TObject);
|
|
begin
|
|
inherited;
|
|
// copy depending data
|
|
FExternalID.CopyFrom(TsdDocType(ANode).FExternalID);
|
|
FSystemLiteral.CopyFrom(TsdDocType(ANode).FSystemLiteral);
|
|
FPubIDLiteral.CopyFrom(TsdDocType(ANode).FPubIDLiteral);
|
|
end;
|
|
|
|
constructor TsdDocType.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
FExternalID := TsdCharData.Create(AOwner);
|
|
FSystemLiteral := TsdQuotedText.Create(AOwner);
|
|
FPubIDLiteral := TsdQuotedText.Create(AOwner);
|
|
end;
|
|
|
|
destructor TsdDocType.Destroy;
|
|
begin
|
|
FreeAndNil(FExternalID);
|
|
FreeAndNil(FSystemLiteral);
|
|
FreeAndNil(FPubIDLiteral);
|
|
inherited;
|
|
end;
|
|
|
|
function TsdDocType.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeDocType;
|
|
end;
|
|
|
|
function TsdDocType.GetName: Utf8String;
|
|
begin
|
|
Result := GetString(FNameID);
|
|
end;
|
|
|
|
procedure TsdDocType.ParseIntermediateData(P: TsdXmlParser);
|
|
// DocType has no value, just add whitespace node if there are non-default blanks
|
|
var
|
|
Blanks: Utf8String;
|
|
B: AnsiChar;
|
|
WhitespaceNode: TsdWhiteSpace;
|
|
begin
|
|
repeat
|
|
B := P.NextCharSkipBlanks(Blanks);
|
|
|
|
if GetPreserveWhiteSpace and(Length(Blanks) > 0) and (Blanks <> ' ') then
|
|
begin
|
|
WhitespaceNode := TsdWhiteSpace.Create(TNativeXml(FOwner));
|
|
WhitespaceNode.FValueID := AddString(Blanks);
|
|
NodeAdd(WhitespaceNode);
|
|
end;
|
|
|
|
if not (B in [ ']', '<' ]) then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, format(sIllegalTag, [B, P.Position]));
|
|
P.ReadStringUntilBlankOrEndTag
|
|
end
|
|
else
|
|
break;
|
|
until False;
|
|
end;
|
|
|
|
function TsdDocType.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
var
|
|
Blanks1, Blanks2, Blanks3, Blanks4: Utf8String;
|
|
B: AnsiChar;
|
|
IsTrimmed: boolean;
|
|
begin
|
|
Result := Self;
|
|
// sequence <!DOCTYPE is already parsed here
|
|
// Parse name
|
|
P.NextCharSkipBlanks(Blanks1);
|
|
P.MoveBack;
|
|
SetName(sdTrim(P.ReadStringUntilBlankOrEndTag, IsTrimmed));
|
|
P.NextCharSkipBlanks(Blanks2);
|
|
P.MoveBack;
|
|
B := P.NextChar;
|
|
if not (B in ['[', '>']) then
|
|
begin
|
|
P.MoveBack;
|
|
// Parse external ID
|
|
if P.CheckString('SYSTEM') then
|
|
begin
|
|
FExternalID.Value := 'SYSTEM';
|
|
FSystemLiteral.ParseStream(P);
|
|
end else
|
|
begin
|
|
if P.CheckString('PUBLIC') then
|
|
begin
|
|
FExternalID.Value := 'PUBLIC';
|
|
FPubIDLiteral.ParseStream(P);
|
|
FSystemLiteral.ParseStream(P);
|
|
end else
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalTag, [B, P.Position]));
|
|
exit;
|
|
end;
|
|
end;
|
|
B := P.NextCharSkipBlanks(Blanks3);
|
|
end;
|
|
if B = '[' then
|
|
begin
|
|
Result := ParseElementList(P,
|
|
// we allow these elements in the DTD
|
|
[xeComment, xeDtdElement, xeDtdAttList, xeDtdEntity, xeDtdNotation, xeInstruction, xeCharData]);
|
|
B := P.NextCharSkipBlanks(Blanks4);
|
|
end;
|
|
if B <> '>' then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalTag, [B, P.Position]));
|
|
end;
|
|
end;
|
|
|
|
procedure TsdDocType.SetName(const Value: Utf8String);
|
|
begin
|
|
FNameID := AddString(Value);
|
|
end;
|
|
|
|
procedure TsdDocType.WriteStream(S: TStream);
|
|
var
|
|
i: integer;
|
|
Line: Utf8String;
|
|
begin
|
|
Line := GetIndent + '<!DOCTYPE ' + GetName;
|
|
sdWriteToStream(S, Line);
|
|
|
|
// in case of specific external id
|
|
if FExternalID.Value = 'SYSTEM' then
|
|
begin
|
|
Line := ' SYSTEM ';
|
|
sdWriteToStream(S, Line);
|
|
FSystemLiteral.WriteStream(S);
|
|
end;
|
|
if FExternalID.Value = 'PUBLIC' then
|
|
begin
|
|
Line := ' PUBLIC ';
|
|
sdWriteToStream(S, Line);
|
|
FPubIDLiteral.WriteStream(S);
|
|
sdWriteToStream(S, ' ');
|
|
FSystemLiteral.WriteStream(S);
|
|
end;
|
|
|
|
sdWriteToStream(S, ' ');
|
|
|
|
if GetNodeCount > 0 then
|
|
begin
|
|
sdWriteToStream(S, '[' + GetEndOfLine);
|
|
for i := 0 to GetNodeCount - 1 do
|
|
begin
|
|
Nodes[i].WriteStream(S);
|
|
end;
|
|
sdWriteToStream(S, ']');
|
|
end;
|
|
sdWriteToStream(S, '>' + GetEndOfLine);
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdDtdElement }
|
|
|
|
function TsdDtdElement.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeDtdElement;
|
|
end;
|
|
|
|
function TsdDtdElement.GetValue: Utf8String;
|
|
var
|
|
S: TsdStringStream;
|
|
begin
|
|
S := TsdStringStream.Create('');
|
|
try
|
|
WriteContent(S);
|
|
Result := S.DataString;
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TsdDtdElement.ParseContent(P: TsdXmlParser);
|
|
var
|
|
ChardataNode: TsdChardata;
|
|
begin
|
|
ChardataNode := TsdChardata.Create(TNativeXml(FOwner));
|
|
NodeAdd(ChardataNode);
|
|
DoNodeNew(ChardataNode);
|
|
ChardataNode.FValueID := AddString(P.ReadStringUntil('>'));
|
|
DoNodeLoaded(ChardataNode);
|
|
end;
|
|
|
|
function TsdDtdElement.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
var
|
|
Blanks1, Blanks2: Utf8string;
|
|
IsTrimmed: boolean;
|
|
begin
|
|
Result := Self;
|
|
P.NextCharSkipBlanks(Blanks1);
|
|
P.MoveBack;
|
|
Name := sdTrim(P.ReadStringUntilBlankOrEndTag, IsTrimmed);
|
|
P.NextCharSkipBlanks(Blanks2);
|
|
P.MoveBack;
|
|
// parse the content of element, attlist, entity, etc.
|
|
// default ParseContent is in TsdDtdElement, and TsdDtdEntity has overridden
|
|
// method.
|
|
ParseContent(P);
|
|
end;
|
|
|
|
procedure TsdDtdElement.WriteContent(S: TStream);
|
|
var
|
|
i: integer;
|
|
begin
|
|
if GetNodeCount > 0 then
|
|
begin
|
|
for i := 0 to GetNodeCount - 1 do
|
|
begin
|
|
Nodes[i].WriteStream(S);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TsdDtdElement.WriteStream(S: TStream);
|
|
var
|
|
ElementTypeString: Utf8String;
|
|
begin
|
|
case ElementType of
|
|
xeDtdElement: ElementTypeString := 'ELEMENT';
|
|
xeDtdAttList: ElementTypeString := 'ATTLIST';
|
|
xeDtdEntity: ElementTypeString := 'ENTITY';
|
|
xeDtdNotation: ElementTypeString := 'NOTATION';
|
|
else
|
|
raise EFilerError.Create(sUnsupportedTag);
|
|
end; //case
|
|
|
|
// write front matter
|
|
sdStreamWriteString(S, '<!' + ElementTypeString + ' ' + GetName + ' ');
|
|
|
|
// write content
|
|
WriteContent(S);
|
|
|
|
// write end matter
|
|
sdStreamWriteString(S, '>' + GetEndOfLine);
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdDtdAttList }
|
|
|
|
function TsdDtdAttList.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeDtdAttList;
|
|
end;
|
|
|
|
{ TsdDtdEntity }
|
|
|
|
function TsdDtdEntity.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeDtdEntity;
|
|
end;
|
|
|
|
procedure TsdDtdEntity.ParseContent(P: TsdXmlParser);
|
|
var
|
|
Ch: AnsiChar;
|
|
begin
|
|
// list of quotedtext
|
|
Ch := ParseQuotedTextList(P);
|
|
if Ch <> '>' then
|
|
DoDebugOut(Self, wsFail, format(sIllegalEndTag, [Ch, P.LineNumber, P.Position]));
|
|
end;
|
|
|
|
{ TsdDtdNotation }
|
|
|
|
function TsdDtdNotation.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeDtdNotation;
|
|
end;
|
|
|
|
{ TsdInstruction }
|
|
|
|
function TsdInstruction.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeInstruction;
|
|
end;
|
|
|
|
function TsdInstruction.GetName: Utf8String;
|
|
begin
|
|
Result := 'PI';
|
|
end;
|
|
|
|
function TsdInstruction.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
begin
|
|
Result := Self;
|
|
FValueID := AddString(P.ReadStringUntil('?>'));
|
|
end;
|
|
|
|
procedure TsdInstruction.WriteStream(S: TStream);
|
|
var
|
|
Line: Utf8String;
|
|
begin
|
|
// processing instruction <?{value}?>
|
|
Line := GetIndent + '<?' + GetValue + '?>' + GetEndOfLine;
|
|
sdWriteToStream(S, Line);
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdStyleSheet }
|
|
|
|
function TsdStyleSheet.ElementType: TsdElementType;
|
|
begin
|
|
Result := xeStyleSheet;
|
|
end;
|
|
|
|
function TsdStyleSheet.GetName: Utf8String;
|
|
begin
|
|
Result := 'xml-stylesheet';
|
|
end;
|
|
|
|
{function TsdStyleSheet.ParseStream(P: TsdXmlParser): TXmlNode;
|
|
var
|
|
B: AnsiChar;
|
|
begin
|
|
Result := Self;
|
|
// Directly parse the attribute list
|
|
B := ParseAttributeList(P);
|
|
if B <> '?' then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalEndTag, [B, P.LineNumber, P.Position]));
|
|
exit;
|
|
end;
|
|
B := P.NextChar;
|
|
if B <> '>' then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalEndTag, [B, P.LineNumber, P.Position]));
|
|
exit;
|
|
end;
|
|
end;}
|
|
|
|
procedure TsdStyleSheet.WriteStream(S: TStream);
|
|
begin
|
|
// Stylesheet <?xml-stylesheet{stylesheet}?>
|
|
sdWriteToStream(S, GetIndent + '<?xml-stylesheet');
|
|
WriteAttributeList(S, FNodes.Count);
|
|
sdWriteToStream(S, '?>' + GetEndOfLine);
|
|
DoProgress(S.Position);
|
|
end;
|
|
|
|
{ TsdNodeList }
|
|
|
|
function TsdNodeList.ByType(AType: TsdElementType): TXmlNode;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to Count - 1 do
|
|
if Items[i].ElementType = AType then
|
|
begin
|
|
Result := Items[i];
|
|
exit;
|
|
end;
|
|
Result := nil;
|
|
end;
|
|
|
|
constructor TsdNodeList.Create(AOwnsObjects: boolean);
|
|
begin
|
|
inherited Create(AOwnsObjects);
|
|
end;
|
|
|
|
function TsdNodeList.GetItems(Index: integer): TXmlNode;
|
|
begin
|
|
if (Index >= 0) and (Index < Count) then
|
|
Result := Get(Index)
|
|
else
|
|
Result := nil;
|
|
end;
|
|
|
|
function TsdNodeList.FindFirst: TXmlNode;
|
|
begin
|
|
if Count = 0 then
|
|
Result := nil
|
|
else
|
|
Result := Items[0];
|
|
end;
|
|
|
|
function TsdNodeList.FindNext(ANode: TXmlNode): TXmlNode;
|
|
var
|
|
Last: TXmlNode;
|
|
begin
|
|
Result := nil;
|
|
if not assigned(ANode) then
|
|
exit;
|
|
|
|
if ANode.NodeCount > 0 then
|
|
begin
|
|
Result := ANode.Nodes[0];
|
|
exit;
|
|
end;
|
|
|
|
while assigned(ANode) do
|
|
begin
|
|
Last := GetLastSiblingOf(ANode);
|
|
if ANode = Last then
|
|
begin
|
|
ANode := ANode.Parent;
|
|
end else
|
|
begin
|
|
Result := GetNextSiblingOf(ANode);
|
|
exit;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
function TsdNodeList.GetNextSiblingOf(ANode: TXmlNode): TXmlNode;
|
|
var
|
|
Parent: TXmlNode;
|
|
Idx: integer;
|
|
begin
|
|
Parent := ANode.Parent;
|
|
if Parent = nil then
|
|
begin
|
|
Idx := IndexOf(ANode);
|
|
if Idx < 0 then
|
|
raise Exception.Create('index must be >= 0');
|
|
Result := Items[Idx + 1];
|
|
end else
|
|
begin
|
|
Idx := Parent.NodeIndexOf(ANode);
|
|
Result := Parent.Nodes[Idx + 1];
|
|
end;
|
|
end;
|
|
|
|
function TsdNodeList.GetLastSiblingOf(ANode: TXmlNode): TXmlNode;
|
|
var
|
|
Parent: TXmlNode;
|
|
LastIdx: integer;
|
|
begin
|
|
Result := nil;
|
|
if ANode = nil then
|
|
exit;
|
|
|
|
Parent := ANode.Parent;
|
|
if Parent = nil then
|
|
begin
|
|
LastIdx := Count - 1;
|
|
if LastIdx >= 0 then
|
|
Result := Items[LastIdx];
|
|
end else
|
|
begin
|
|
LastIdx := Parent.NodeCount - 1;
|
|
if LastIdx >= 0 then
|
|
Result := Parent.Nodes[LastIdx];
|
|
end;
|
|
end;
|
|
|
|
{ TNativeXml }
|
|
|
|
function TNativeXml.Canonicalize: integer;
|
|
var
|
|
C14N: TsdXmlCanonicalizer;
|
|
begin
|
|
C14N := TsdXmlCanonicalizer.Create(Self);
|
|
try
|
|
Result := C14N.Canonicalize(Self);
|
|
finally
|
|
C14N.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.Clear;
|
|
begin
|
|
ClearData(FHasDeclaration, FHasDocType, FHasRoot);
|
|
end;
|
|
|
|
procedure TNativeXml.ClearData(AHasDeclaration, AHasDocType, AHasRoot: boolean);
|
|
var
|
|
Declaration: TsdDeclaration;
|
|
DocType: TsdDocType;
|
|
Root: TXmlNode;
|
|
begin
|
|
// clear symboltable and rootnodes
|
|
FSymbolTable.Clear;
|
|
FRootNodes.Clear;
|
|
|
|
// build default items in rootnodes
|
|
|
|
// add declaration
|
|
if AHasDeclaration then
|
|
begin
|
|
Declaration := TsdDeclaration.Create(Self);
|
|
Declaration.Version := cDefaultVersionString;
|
|
Declaration.Encoding := cDefaultEncodingString;
|
|
// when NativeXml is cleared instead of parsed,
|
|
// the declaration needs to be initialised
|
|
// using the two attributes
|
|
Declaration.FDirectNodeCount := 2;
|
|
FRootNodes.Add(Declaration);
|
|
end;
|
|
|
|
// add doctype
|
|
if AHasDocType then
|
|
begin
|
|
DocType := TsdDocType.Create(Self);
|
|
DocType.Name := FRootName;
|
|
DocType.ExternalId.Value := 'SYSTEM';
|
|
FRootNodes.Add(DocType);
|
|
end;
|
|
|
|
// add the root element
|
|
if AHasRoot then
|
|
begin
|
|
Root := GetRootNodeClass.Create(Self);
|
|
Root.Name := FRootName;
|
|
FRootNodes.Add(Root);
|
|
end;
|
|
end;
|
|
|
|
constructor TNativeXml.CreateEx(AOwner: TComponent; HasDeclaration, HasDocType, HasRoot: boolean; ARootName: Utf8String);
|
|
begin
|
|
inherited Create(AOwner);
|
|
|
|
// the symboltable holds all the string snippets (UTF8) in this component
|
|
FSymbolTable := TsdSymbolTable.Create(Self);
|
|
|
|
// FRootNodes is an owned list
|
|
FRootNodes := TsdNodeList.Create(True);
|
|
|
|
// CreateEx options
|
|
FHasDeclaration := HasDeclaration;
|
|
FHasDocType := HasDocType;
|
|
FHasRoot := HasRoot;
|
|
FRootName := ARootName;
|
|
|
|
// this resets defaults
|
|
ResetDefaults;
|
|
|
|
// now clear the rootnodes and create optional declaration, doctype and root
|
|
ClearData(FHasDeclaration, FHasDocType, FHasRoot);
|
|
end;
|
|
|
|
constructor TNativeXml.CreateName(const ARootName: Utf8String; AOwner: TComponent);
|
|
begin
|
|
// we create a standard declaration and root element with rootname
|
|
CreateEx(AOwner, True, False, True, ARootName);
|
|
end;
|
|
|
|
constructor TNativeXml.Create(AOwner: TComponent);
|
|
begin
|
|
// simple constructor without declaration, but with a standard root element
|
|
CreateEx(AOwner, False, False, True, '');
|
|
end;
|
|
|
|
destructor TNativeXml.Destroy;
|
|
begin
|
|
FreeAndNil(FRootNodes);
|
|
FreeAndNil(FSymbolTable);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TNativeXml.DoNodeLoaded(ANode: TXmlNode);
|
|
begin
|
|
if assigned(FOnNodeLoaded) then
|
|
FOnNodeLoaded(Self, ANode);
|
|
end;
|
|
|
|
procedure TNativeXml.DoNodeNew(ANode: TXmlNode);
|
|
begin
|
|
if assigned(FOnNodeNew) then
|
|
FOnNodeNew(Self, ANode);
|
|
end;
|
|
|
|
procedure TNativeXml.DoProgress(Position: int64);
|
|
begin
|
|
if assigned(FOnProgress) then
|
|
FOnProgress(Self, Position);
|
|
end;
|
|
|
|
function TNativeXml.GetCommentString: Utf8String;
|
|
// Get the first comment node, and return its value
|
|
var
|
|
Node: TXmlNode;
|
|
begin
|
|
Result := '';
|
|
Node := FRootNodes.ByType(xeComment);
|
|
if assigned(Node) then
|
|
Result := Node.Value;
|
|
end;
|
|
|
|
function TNativeXml.GetOrCreateDeclarationNode: TXmlNode;
|
|
begin
|
|
// write declaration node (if not there)
|
|
Result := FRootNodes[0];
|
|
if not (Result is TsdDeclaration) then
|
|
begin
|
|
Result := TsdDeclaration.Create(Self);
|
|
FRootNodes.Insert(0, Result);
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.GetCharset: Utf8String;
|
|
begin
|
|
Result := '';
|
|
if FRootNodes.Count > 0 then
|
|
if FRootNodes[0] is TsdDeclaration then
|
|
Result := TsdDeclaration(FRootNodes[0]).Encoding;
|
|
end;
|
|
|
|
function TNativeXml.GetPreserveWhitespace: boolean;
|
|
begin
|
|
Result := (FXmlFormat = xfPreserve);
|
|
end;
|
|
|
|
function TNativeXml.GetParserLineNumber(P: TsdXmlParser): int64;
|
|
begin
|
|
if assigned(P) then
|
|
Result := P.LineNumber
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TNativeXml.GetParserPosition(P: TsdXmlParser): int64;
|
|
begin
|
|
if assigned(P) then
|
|
Result := P.Position
|
|
else
|
|
Result := 0;
|
|
end;
|
|
|
|
function TNativeXml.GetDeclaration: TsdDeclaration;
|
|
begin
|
|
// the first xeDeclaration node in the root nodes
|
|
Result := TsdDeclaration(FRootNodes.ByType(xeDeclaration));
|
|
end;
|
|
|
|
function TNativeXml.GetDocType: TsdDocType;
|
|
begin
|
|
// the first xeDocType node in the root nodes
|
|
Result := TsdDocType(FRootNodes.ByType(xeDocType));
|
|
end;
|
|
|
|
function TNativeXml.GetRoot: TsdElement;
|
|
begin
|
|
// the first xeElement node in the root nodes
|
|
Result := TsdElement(FRootNodes.ByType(xeElement));
|
|
end;
|
|
|
|
function TNativeXml.GetRootNodeClass: TsdNodeClass;
|
|
begin
|
|
// default node class is TsdElement
|
|
Result := TsdElement;
|
|
end;
|
|
|
|
function TNativeXml.GetRootNodeCount: integer;
|
|
begin
|
|
Result := FRootNodes.Count;
|
|
end;
|
|
|
|
function TNativeXml.GetRootContainers(Index: integer): TsdContainerNode;
|
|
var
|
|
i, Idx: integer;
|
|
begin
|
|
Result := nil;
|
|
Idx := 0;
|
|
for i := 0 to FRootNodes.Count - 1 do
|
|
begin
|
|
if FRootNodes[i] is TsdContainerNode then
|
|
begin
|
|
if Idx = Index then
|
|
begin
|
|
Result := TsdContainerNode(FRootNodes[i]);
|
|
exit;
|
|
end;
|
|
inc(Idx);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.GetRootContainerCount: integer;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := 0;
|
|
for i := 0 to FRootNodes.Count - 1 do
|
|
begin
|
|
if FRootNodes[i] is TsdContainerNode then
|
|
inc(Result);
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.GetStyleSheet: TsdStyleSheet;
|
|
begin
|
|
Result := TsdStyleSheet(FRootNodes.ByType(xeStylesheet));
|
|
if not assigned(Result) then
|
|
begin
|
|
// Add a stylesheet node as second one if none present
|
|
Result := TsdStyleSheet.Create(Self);
|
|
FRootNodes.Insert(1, Result);
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.GetVersionString: Utf8String;
|
|
begin
|
|
Result := '';
|
|
if FRootNodes.Count > 0 then
|
|
if FRootNodes[0] is TsdDeclaration then
|
|
Result := TsdDeclaration(FRootNodes[0]).Version;
|
|
end;
|
|
|
|
function TNativeXml.IsEmpty: boolean;
|
|
var
|
|
R: TXmlNode;
|
|
begin
|
|
R := GetRoot;
|
|
Result := not assigned(R) or R.IsClear;
|
|
end;
|
|
|
|
function TNativeXml.LineFeed: Utf8String;
|
|
begin
|
|
case FXmlFormat of
|
|
xfReadable:
|
|
Result := #13#10;
|
|
xfCompact:
|
|
Result := #10;
|
|
else
|
|
Result := #10;
|
|
end;//case
|
|
end;
|
|
|
|
procedure TNativeXml.LoadFromBinaryFile(const AFileName: string);
|
|
var
|
|
Bxm: TsdBinaryXml;
|
|
begin
|
|
Bxm := TsdBinaryXml.Create(Self);
|
|
try
|
|
SetBinaryDocument(Bxm);
|
|
Bxm.LoadFromFile(AFileName);
|
|
finally
|
|
Bxm.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.LoadFromBinaryStream(AStream: TStream);
|
|
var
|
|
Bxm: TsdBinaryXml;
|
|
DeclarationNode: TsdDeclaration;
|
|
DeclarationEncodingString: Utf8String;
|
|
begin
|
|
Bxm := TsdBinaryXml.Create(Self);
|
|
try
|
|
SetBinaryDocument(Bxm);
|
|
Bxm.LoadFromStream(AStream);
|
|
|
|
// after loading the bxm, we must set external encoding and external codepage
|
|
// in case we save the original xml later
|
|
DeclarationNode := GetDeclaration;
|
|
if assigned(DeclarationNode) then
|
|
begin
|
|
DeclarationEncodingString := DeclarationNode.Encoding;
|
|
FExternalEncoding := sdCharsetToStringEncoding(DeclarationEncodingString);
|
|
FExternalCodePage := sdCharsetToCodePage(DeclarationEncodingString);
|
|
end;
|
|
finally
|
|
Bxm.Free;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef MSWINDOWS}
|
|
function TNativeXml.LoadFromURL(const URL: Utf8String): int64;
|
|
var
|
|
M: TMemoryStream;
|
|
NetHandle, UrlHandle: HINTERNET;
|
|
Buffer: array[0..$400 - 1] of AnsiChar;
|
|
BytesRead: cardinal;
|
|
begin
|
|
Result := 0;
|
|
|
|
NetHandle := InternetOpenA('nativexml', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
|
|
|
|
if not assigned(NetHandle) then
|
|
begin
|
|
// NetHandle is not valid.
|
|
DoDebugOut(Self, wsFail, 'Unable to initialize WinInet');
|
|
exit;
|
|
end;
|
|
|
|
try
|
|
UrlHandle := InternetOpenUrlA(NetHandle, PAnsiChar(Url), nil, 0, INTERNET_FLAG_RELOAD, 0);
|
|
if not assigned(UrlHandle) then
|
|
begin
|
|
// UrlHandle is not valid.
|
|
DoDebugOut(Self, wsFail, format('Cannot open URL %s', [Url]));
|
|
exit;
|
|
end;
|
|
|
|
M := TMemoryStream.Create;
|
|
try
|
|
// UrlHandle valid? Proceed with download
|
|
FillChar(Buffer, SizeOf(Buffer), 0);
|
|
repeat
|
|
InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
|
|
if BytesRead > 0 then
|
|
M.Write(Buffer, BytesRead);
|
|
until BytesRead = 0;
|
|
|
|
InternetCloseHandle(UrlHandle);
|
|
|
|
// now load the stream
|
|
M.Position := 0;
|
|
LoadFromStream(M);
|
|
// final size in bytes of the url stream
|
|
Result := M.Size;
|
|
|
|
finally
|
|
M.Free;
|
|
end;
|
|
|
|
finally
|
|
InternetCloseHandle(NetHandle);
|
|
end;
|
|
end;
|
|
{$else MSWINDOWS}
|
|
function TNativeXml.LoadFromURL(const URL: Utf8String): int64;
|
|
begin
|
|
DoDebugOut(Self, wsFail, 'not implemented (needs WININET)');
|
|
Result := 0;
|
|
end;
|
|
{$endif MSWINDOWS}
|
|
|
|
procedure TNativeXml.LoadFromFile(const AFileName: string);
|
|
var
|
|
F: TFileStream;
|
|
begin
|
|
F := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
|
|
try
|
|
LoadFromStream(F);
|
|
finally
|
|
F.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.LoadFromStream(AStream: TStream);
|
|
var
|
|
Parser: TsdXmlParser;
|
|
S: TMemoryStream;
|
|
begin
|
|
FSymbolTable.Clear;
|
|
FRootNodes.Clear;
|
|
|
|
Parser := TsdXmlParser.Create(AStream, cParserChunkSize);
|
|
try
|
|
Parser.Owner := Self;
|
|
|
|
try
|
|
// we also allow binary xml since v4.02
|
|
if Parser.IsBinaryXml then
|
|
begin
|
|
AStream.Position := 0;
|
|
// do this encapsulation because AStream could be
|
|
// a filestream and this buffering will be faster
|
|
S := TMemoryStream.Create;
|
|
try
|
|
S.LoadFromStream(AStream);
|
|
LoadFromBinaryStream(S);
|
|
finally
|
|
S.Free;
|
|
end;
|
|
exit;
|
|
end;
|
|
|
|
// parse the stream
|
|
ParseStream(Parser);
|
|
|
|
// copy encoding data from the parser
|
|
FExternalEncoding := Parser.Encoding;
|
|
FExternalCodePage := Parser.CodePage;
|
|
FExternalBomInfo := Parser.BomInfo;
|
|
|
|
finally
|
|
// final onprogress
|
|
DoProgress(AStream.Size);
|
|
end;
|
|
finally
|
|
FreeAndNil(Parser);
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.MoveSubNodes(AList: TsdNodeList; FromNode,
|
|
ToNode: TXmlNode);
|
|
var
|
|
i: integer;
|
|
Node: TXmlNode;
|
|
begin
|
|
if (AList = nil) or (FromNode = nil) or (ToNode = nil) then
|
|
exit;
|
|
if AList.Count = 0 then
|
|
exit;
|
|
|
|
// move subnodes
|
|
for i := 0 to AList.Count - 1 do
|
|
begin
|
|
Node := AList[i];
|
|
if Node.Parent = FromNode then
|
|
begin
|
|
FromNode.NodeExtract(Node);
|
|
ToNode.NodeAdd(Node);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.New;
|
|
begin
|
|
// backward-compatible procedure New: Ensure FHasDeclaration option and then
|
|
// clear the document
|
|
FHasDeclaration := True;
|
|
Clear;
|
|
end;
|
|
|
|
procedure TNativeXml.ParseStream(Parser: TsdXmlParser);
|
|
var
|
|
B: AnsiChar;
|
|
ElementType: TsdElementType;
|
|
NodeClass: TsdNodeClass;
|
|
Node: TXmlNode;
|
|
StringData: Utf8String;
|
|
CD: TsdCharData;
|
|
{$ifdef SOURCEPOS}
|
|
SP: int64;
|
|
{$endif SOURCEPOS}
|
|
IsTrimmed: boolean;
|
|
DeclarationEncodingString: Utf8String;
|
|
i, Idx: integer;
|
|
Parent, Content: TXmlNode;
|
|
Container: TsdContainerNode;
|
|
HasClosed: boolean;
|
|
begin
|
|
FAbortParsing := False;
|
|
|
|
// prepare for "FixStructuralErrors"
|
|
if FFixStructuralErrors then
|
|
begin
|
|
FSingleTagNames := TStringList.Create;
|
|
FSingleTagNames.Sorted := True;
|
|
end else
|
|
FSingleTagNames := nil;
|
|
|
|
// read BOM
|
|
Parser.ReadBOM;
|
|
|
|
// store external bominfo for use later when writing
|
|
FExternalBomInfo := Parser.BomInfo;
|
|
|
|
// Read next tag
|
|
repeat
|
|
{$ifdef SOURCEPOS}
|
|
SP := Parser.Position;
|
|
{$endif SOURCEPOS}
|
|
StringData := Parser.ReadStringUntilChar('<');
|
|
|
|
// if we do not preserve whitespace, then trim the data in the parsing process
|
|
if not GetPreserveWhiteSpace then
|
|
StringData := sdTrim(StringData, IsTrimmed);
|
|
|
|
if length(StringData) > 0 then
|
|
begin
|
|
// inbetween nodes, add chardata node (usually whitespace)
|
|
CD := TsdCharData.Create(Self);
|
|
{$ifdef SOURCEPOS}
|
|
CD.FSourcePos := SP;
|
|
{$endif SOURCEPOS}
|
|
CD.SetCoreValue(StringData);
|
|
FRootNodes.Add(CD);
|
|
DoNodeNew(CD);
|
|
DoNodeLoaded(CD);
|
|
end;
|
|
|
|
// At the end of the stream? Then stop
|
|
if Parser.EndOfStream then
|
|
break;
|
|
Parser.MoveBack;
|
|
|
|
B := Parser.NextChar;
|
|
if B = '<' then
|
|
begin
|
|
// Determine tag type
|
|
ElementType := Parser.ReadOpenTag;
|
|
if ElementType = xeError then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalTag, [B, Parser.Position]));
|
|
exit;
|
|
end;
|
|
|
|
// Determine node class
|
|
NodeClass := cNodeClass[ElementType];
|
|
if not assigned(NodeClass) then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sUnsupportedTag,
|
|
[cElementTypeNames[ElementType], Parser.Position]));
|
|
exit;
|
|
end;
|
|
|
|
// Create new node and add
|
|
Node := NodeClass.Create(Self);
|
|
FRootNodes.Add(Node);
|
|
if ElementType <> xeElement then
|
|
DoNodeNew(Node);
|
|
|
|
// The node will parse itself
|
|
Node.ParseStream(Parser);
|
|
DoNodeLoaded(Node);
|
|
|
|
// After adding nodes:
|
|
// see if we added the declaration node
|
|
if Node.ElementType = xeDeclaration then
|
|
begin
|
|
// give the parser the codepage from encoding in the declaration.
|
|
// The .SetCodePage setter cares for the re-encoding of the chunk.
|
|
DeclarationEncodingString := TsdDeclaration(Node).Encoding;
|
|
Parser.Encoding := sdCharsetToStringEncoding(DeclarationEncodingString);
|
|
Parser.CodePage := sdCharsetToCodePage(DeclarationEncodingString);
|
|
|
|
DoDebugOut(Self, wsInfo, Format('declaration with encoding "%s" and codepage %d',
|
|
[TsdDeclaration(Node).Encoding, Parser.CodePage]));
|
|
end;
|
|
|
|
// drop comments when parsing?
|
|
if (Node.ElementType = xeComment) and FDropCommentsOnParse then
|
|
begin
|
|
// drop comment on parse
|
|
DoDebugOut(Self, wsInfo, 'option DropCommentsOnParse is true, deleting comment');
|
|
FRootNodes.Remove(Node);
|
|
end;
|
|
|
|
end;
|
|
|
|
// Check if application has aborted parsing
|
|
until FAbortParsing or Parser.EndOfStream;
|
|
|
|
// fix structural errors option..
|
|
if FFixStructuralErrors then
|
|
begin
|
|
// list the single tag names
|
|
{for Idx := 0 to FSingleTagNames.Count - 1 do
|
|
DoDebugOut(Self, wsInfo, format('single tag name: %s', [FSingleTagNames[Idx]]));}
|
|
|
|
Node := FRootNodes.FindFirst;
|
|
while assigned(Node) do
|
|
begin
|
|
// notorious single tag?
|
|
if FSingleTagNames.Find(Node.Name, Idx) then
|
|
begin
|
|
// here's the scoop: the single tag inadvertently might have subnodes which
|
|
// it should not have, i.e. any nodes with index >= FDirectNodeCount.
|
|
// We extract these, and we add them to the parent nodelist after the
|
|
// node's index.
|
|
Parent := Node.Parent;
|
|
Container := TsdContainerNode(Node);
|
|
Idx := Parent.NodeIndexOf(Container);
|
|
HasClosed := False;
|
|
// extract in reverse order, then insert at parent Idx + 1 in reverse
|
|
// order too, so in all the order is correct
|
|
for i := Container.FNodes.Count - 1 downto Container.FDirectNodeCount do
|
|
begin
|
|
Content := Container.NodeList[i];
|
|
Container.NodeExtract(Content);
|
|
Parent.NodeInsert(Idx + 1, Content);
|
|
HasClosed := True;
|
|
end;
|
|
if HasClosed then
|
|
DoDebugOut(Self, wsInfo, format('correctly closed node "%s" at pos %d',
|
|
[Container.Name, Container.SourcePos]));
|
|
end;
|
|
// iterate. The nice thing about FindFirst/FindNext is that finding the
|
|
// next node is evaluated on the spot. No dirty list history problems
|
|
Node := FRootNodes.FindNext(Node);
|
|
end;
|
|
// finally, free the single tag names list
|
|
FreeAndNil(FSingleTagNames);
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.ParseSubstituteContentFromNode(ANode: TXmlNode; const ASubstitute: Utf8String): TXmlNode;
|
|
// this is a simple version of TNativeXml.ParseStream, in order to re-parse
|
|
// substituted chardata (e.g. from entities, see also NativeXmlC14n.pas)
|
|
var
|
|
S: TsdStringStream;
|
|
Parser: TsdXmlParser;
|
|
Parent: TXmlNode;
|
|
|
|
// local
|
|
function ParseSubstituteStream(Parser: TsdXmlParser): TXmlNode;
|
|
var
|
|
B: AnsiChar;
|
|
ElementType: TsdElementType;
|
|
NodeClass: TsdNodeClass;
|
|
Node: TXmlNode;
|
|
StringData: Utf8String;
|
|
CD: TsdCharData;
|
|
IsTrimmed: boolean;
|
|
begin
|
|
FAbortParsing := False;
|
|
|
|
// result will have the first re-parsed node
|
|
Result := nil;
|
|
|
|
Parser.EncodeChunk;
|
|
|
|
// Read next tag
|
|
repeat
|
|
StringData := sdTrim(Parser.ReadStringUntilChar('<'), IsTrimmed);
|
|
|
|
if length(StringData) > 0 then
|
|
begin
|
|
// Add chardata node
|
|
CD := TsdCharData.Create(Self);
|
|
CD.Value := StringData;
|
|
Parent.NodeAdd(CD);
|
|
if not assigned(Result) then
|
|
Result := CD;
|
|
end;
|
|
|
|
// At the end of the stream? Then stop
|
|
if Parser.EndOfStream then
|
|
break;
|
|
Parser.MoveBack;
|
|
|
|
B := Parser.NextChar;
|
|
if B = '<' then
|
|
begin
|
|
// Determine tag type
|
|
ElementType := Parser.ReadOpenTag;
|
|
if ElementType = xeError then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sIllegalTag, [B, Parser.Position]));
|
|
exit;
|
|
end;
|
|
|
|
// Determine node class
|
|
NodeClass := cNodeClass[ElementType];
|
|
if not assigned(NodeClass) then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sUnsupportedTag,
|
|
[cElementTypeNames[ElementType], Parser.Position]));
|
|
exit;
|
|
end;
|
|
|
|
// Create new node and add
|
|
Node := NodeClass.Create(Self);
|
|
Parent.NodeAdd(Node);
|
|
if not assigned(Result) then
|
|
Result := Node;
|
|
|
|
// The node will parse itself
|
|
Node.ParseStream(Parser);
|
|
end;
|
|
|
|
// Check if application has aborted parsing
|
|
until FAbortParsing or Parser.EndOfStream;
|
|
end;
|
|
|
|
// main
|
|
begin
|
|
Result := nil;
|
|
Parent := ANode.Parent;
|
|
if not assigned(Parent) then
|
|
exit;
|
|
|
|
// remove the node that gets substituted
|
|
Parent.NodeRemove(ANode);
|
|
|
|
S := TsdStringStream.Create(ASubstitute);
|
|
try
|
|
S.Position := 0;
|
|
Parser := TsdXmlParser.Create(S, cParserChunkSize);
|
|
try
|
|
Parser.Owner := Self;
|
|
Result := ParseSubstituteStream(Parser);
|
|
finally
|
|
FreeAndNil(Parser);
|
|
end;
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.ReadFromString(const AValue: Utf8String);
|
|
var
|
|
S: TStream;
|
|
begin
|
|
S := TsdStringStream.Create(AValue);
|
|
try
|
|
LoadFromStream(S);
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.RemoveWhitespace;
|
|
var
|
|
Node, Sub: TXmlNode;
|
|
CN: TsdContainerNode;
|
|
ChardataString: Utf8String;
|
|
i: integer;
|
|
IsTrimmed: boolean;
|
|
begin
|
|
Node := FindFirst;
|
|
while assigned(Node) do
|
|
begin
|
|
// check container node
|
|
if Node is TsdContainerNode then
|
|
begin
|
|
CN := TsdContainerNode(Node);
|
|
|
|
// search for whitespace nodes and whitespace in chardata
|
|
i := 0;
|
|
while i < CN.NodeCount do
|
|
begin
|
|
Sub := CN.Nodes[i];
|
|
|
|
// trim chardata for values
|
|
if (Sub.ClassType = TsdCharData) then
|
|
begin
|
|
ChardataString := FSymbolTable.GetString(TsdCharData(Sub).FValueID);
|
|
CharDataString := sdTrim(CharDataString, IsTrimmed);
|
|
if IsTrimmed then
|
|
begin
|
|
if length(ChardataString) = 0 then
|
|
begin
|
|
// no longer used, so remove chardata
|
|
CN.NodeDelete(i); // this also sets FValueIndex to -1
|
|
// next node
|
|
continue;
|
|
end else
|
|
begin
|
|
// update trimmed chardata in table
|
|
TsdCharData(Sub).FValueID := FSymbolTable.AddString(CharDataString);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// remove whitespace nodes
|
|
if Sub is TsdWhitespace then
|
|
begin
|
|
CN.NodeDelete(i);
|
|
// next node
|
|
continue;
|
|
end;
|
|
|
|
inc(i);
|
|
end;
|
|
end;
|
|
|
|
// iterate
|
|
Node := FindNext(Node);
|
|
end;
|
|
|
|
// remove chardata nodes in the rootnodelist
|
|
i := 0;
|
|
while i < FRootNodes.Count do
|
|
begin
|
|
if (FRootNodes[i] is TsdChardata) then
|
|
begin
|
|
FRootNodes.Delete(i);
|
|
continue;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.ResetDefaults;
|
|
begin
|
|
// reset the options to these defaults:
|
|
FDirectCloseTag := cDefaultDirectCloseTag;
|
|
FDropCommentsOnParse := cDefaultDropCommentsOnParse;
|
|
FEolStyle := cDefaultEolStyle;
|
|
FExternalEncoding := cDefaultExternalEncoding;
|
|
FFixStructuralErrors := cDefaultFixStructuralErrors;
|
|
FFloatAllowScientific := cDefaultFloatAllowScientific;
|
|
FFloatSignificantDigits := cDefaultFloatSignificantDigits;
|
|
FIndentString := cDefaultIndentString;
|
|
FNodeClosingStyle := cDefaultNodeClosingStyle;
|
|
FSplitSecondDigits := cDefaultSplitSecondDigits;
|
|
FUseLocalBias := cDefaultUseLocalBias;
|
|
FWriteOnDefault := cDefaultWriteOnDefault;
|
|
FXmlFormat := cDefaultXmlFormat;
|
|
FAesKeyHex := cDefaultAesKeyHex;
|
|
FBinaryMethod := cDefaultBinaryMethod;
|
|
end;
|
|
|
|
procedure TNativeXml.SaveToFile(const AFileName: string);
|
|
var
|
|
S: TStream;
|
|
begin
|
|
S := TFileStream.Create(AFileName, fmCreate);
|
|
try
|
|
SaveToStream(S);
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.SaveToStream(Stream: TStream);
|
|
var
|
|
Writer: TsdXmlWriter;
|
|
BomInfo: TsdBomInfo;
|
|
begin
|
|
// Create xml writer, which enabes correct BOM, encoding and codepage.
|
|
Writer := TsdXmlWriter.Create(Self, Stream, cWriterChunkSize);
|
|
try
|
|
|
|
// based on externalencoding, we create the external BOM
|
|
case FExternalEncoding of
|
|
seAnsi:
|
|
begin
|
|
BomInfo.Len := 0;
|
|
BomInfo.Encoding := seAnsi;
|
|
BomInfo.HasBOM := False;
|
|
end;
|
|
seUTF8:
|
|
begin
|
|
BomInfo.Len := 0;
|
|
BomInfo.Encoding := seUTF8;
|
|
BomInfo.HasBOM := False;
|
|
end;
|
|
seUTF16BE:
|
|
begin
|
|
// Len = 2 and HasBom = True
|
|
BomInfo := cBomInfoList[cBomInfoIdxUTF16BE];
|
|
end;
|
|
seUTF16LE:
|
|
begin
|
|
// Len = 2 and HasBom = True
|
|
BomInfo := cBomInfoList[cBomInfoIdxUTF16LE];
|
|
end;
|
|
else
|
|
DoDebugOut(Self, wsFail, sUnsupportedEncoding);
|
|
exit;
|
|
end;
|
|
FExternalBomInfo := BomInfo;
|
|
|
|
// external byte order mark
|
|
if FExternalBomInfo.HasBOM then
|
|
begin
|
|
Writer.FEncoding := seUTF8; // only to write BOM without hassle
|
|
Writer.Write(FExternalBomInfo.BOM[0], FExternalBomInfo.Len);
|
|
end;
|
|
|
|
// set external encoding
|
|
Writer.FEncoding := FExternalEncoding;
|
|
Writer.FCodePage := FExternalCodePage;
|
|
|
|
// write the stream
|
|
WriteStream(Writer);
|
|
finally
|
|
Writer.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.SaveToBinaryFile(const AFileName: string);
|
|
var
|
|
Bxm: TsdBinaryXml;
|
|
begin
|
|
Bxm := TsdBinaryXml.Create(Self);
|
|
try
|
|
SetBinaryDocument(Bxm);
|
|
Bxm.SaveToFile(AFileName);
|
|
finally
|
|
Bxm.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.SaveToBinaryStream(Stream: TStream);
|
|
var
|
|
Bxm: TsdBinaryXml;
|
|
begin
|
|
Bxm := TsdBinaryXml.Create(Self);
|
|
try
|
|
SetBinaryDocument(Bxm);
|
|
Bxm.SaveToStream(Stream);
|
|
finally
|
|
Bxm.Free;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef USEZLIB}
|
|
function TNativeXml.ZlibEncode(SIn, SOut: TStream; CodecSize: int64): Utf8String;
|
|
var
|
|
CS: TCompressionStream;
|
|
begin
|
|
// tell application this method uses zlib
|
|
Result := 'zlib';
|
|
|
|
// param codecsize not used
|
|
|
|
// SOut is the destination stream, clMax is the maximum compression level
|
|
CS := TCompressionStream.Create(clMax, SOut);
|
|
try
|
|
CS.CopyFrom(SIn, SIn.Size);
|
|
finally
|
|
CS.Free;
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.ZlibDecode(SIn, SOut: TStream; PlainSize: int64): Utf8String;
|
|
var
|
|
DS: TDecompressionStream;
|
|
begin
|
|
Result := 'zlib';
|
|
SIn.Position := 0;
|
|
SOut.Position := 0;
|
|
|
|
DS := TDecompressionStream.Create(SIn);
|
|
try
|
|
// codec size is plain size
|
|
SOut.CopyFrom(DS, PlainSize);
|
|
SOut.Position := 0;
|
|
finally
|
|
DS.Free;
|
|
end;
|
|
end;
|
|
|
|
{$ifdef USEAES}
|
|
function TNativeXml.AeszEncode(SIn, SOut: TStream; CodecSize: int64): Utf8String;
|
|
var
|
|
CS: TCompressionStream;
|
|
Count: integer;
|
|
SDec: TMemoryStream;
|
|
AESKey128: TAESKey128;
|
|
RawKey: RawByteString;
|
|
ExpandedKey: TAESExpandedKey128;
|
|
begin
|
|
// tell application this method uses aesz (AES + ZLib)
|
|
Result := 'aesz';
|
|
|
|
// param codecsize not used
|
|
|
|
SDec := TMemoryStream.Create;
|
|
try
|
|
// SDec is the itermediary stream, clMax is the maximum compression level
|
|
CS := TCompressionStream.Create(clMax, SDec);
|
|
try
|
|
CS.CopyFrom(SIn, SIn.Size);
|
|
finally
|
|
CS.Free;
|
|
end;
|
|
|
|
Count := SDec.Size;
|
|
RawKey := DecodeBinHex(FAesKeyHex);
|
|
Move(RawKey[1], AesKey128, 16);
|
|
|
|
// not necessary for just one stream but principle is easy
|
|
ExpandAESKeyForEncryption(AesKey128, ExpandedKey);
|
|
|
|
SDec.Position := 0;
|
|
EncryptAESStreamECB(SDec, Count, ExpandedKey, SOut);
|
|
|
|
finally
|
|
SDec.Free;
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.AeszDecode(SIn, SOut: TStream; PlainSize: int64): Utf8String;
|
|
var
|
|
DS: TDecompressionStream;
|
|
SDec: TMemoryStream;
|
|
Count: integer;
|
|
AESKey128: TAESKey128;
|
|
RawKey: RawByteString;
|
|
ExpandedKey: TAESExpandedKey128;
|
|
begin
|
|
Result := 'aesz';
|
|
SIn.Position := 0;
|
|
SOut.Position := 0;
|
|
|
|
SDec := TMemoryStream.Create;
|
|
try
|
|
Count := SIn.Size;
|
|
RawKey := DecodeBinHex(FAesKeyHex);
|
|
Move(RawKey[1], AesKey128, 16);
|
|
|
|
// not necessary for just one stream but principle is easy
|
|
ExpandAESKeyForDecryption(AesKey128, ExpandedKey);
|
|
|
|
SIn.Position := 0;
|
|
DecryptAESStreamECB(SIn, Count, ExpandedKey, SDec);
|
|
SDec.Position := 0;
|
|
|
|
DS := TDecompressionStream.Create(SDec);
|
|
try
|
|
// codec size is plain size
|
|
SOut.CopyFrom(DS, PlainSize);
|
|
SOut.Position := 0;
|
|
finally
|
|
DS.Free;
|
|
end;
|
|
|
|
finally
|
|
SDec.Free;
|
|
end;
|
|
end;
|
|
{$endif USEAES}
|
|
{$endif USEZLIB}
|
|
|
|
|
|
|
|
procedure TNativeXml.SetCommentString(const Value: Utf8String);
|
|
// Find first comment node and set it's value, otherwise add new comment node
|
|
// right below the xml declaration
|
|
var
|
|
Node: TXmlNode;
|
|
begin
|
|
Node := FRootNodes.ByType(xeComment);
|
|
if not assigned(Node) and (length(Value) > 0) then
|
|
begin
|
|
Node := TsdComment.Create(Self);
|
|
FRootNodes.Insert(1, Node);
|
|
end;
|
|
if assigned(Node) then
|
|
Node.Value := Value;
|
|
end;
|
|
|
|
procedure TNativeXml.SetCharset(const Value: Utf8String);
|
|
var
|
|
Node: TXmlNode;
|
|
begin
|
|
if (Value = GetCharset) or (length(Value) = 0) then
|
|
exit;
|
|
|
|
// write declaration (if not there)
|
|
Node := GetOrCreateDeclarationNode;
|
|
|
|
// write charset
|
|
if Node is TsdDeclaration then
|
|
TsdDeclaration(Node).Encoding := Value;
|
|
|
|
// write the external codepage
|
|
FExternalCodepage := sdCharsetToCodepage(Value);
|
|
// write external encoding
|
|
FExternalEncoding := sdCharsetToStringEncoding(Value);
|
|
end;
|
|
|
|
procedure TNativeXml.SetPreserveWhiteSpace(const Value: boolean);
|
|
begin
|
|
if GetPreserveWhiteSpace <> Value then
|
|
begin
|
|
if Value = True then
|
|
SetXmlFormat(xfPreserve);
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.SetXmlFormat(const Value: TXmlFormatType);
|
|
begin
|
|
if FXmlFormat = Value then
|
|
exit;
|
|
|
|
// allow removing whitespace if current FXmlFormat is
|
|
// xfPreserve and new value is not
|
|
if FXmlFormat = xfPreserve then
|
|
RemoveWhitespace;
|
|
|
|
FXmlFormat := Value;
|
|
end;
|
|
|
|
procedure TNativeXml.SetVersionString(const Value: Utf8String);
|
|
var
|
|
Node: TXmlNode;
|
|
begin
|
|
if Value = GetVersionString then
|
|
exit;
|
|
Node := FRootNodes[0];
|
|
if not (Node is TsdDeclaration) then
|
|
begin
|
|
if length(Value) > 0 then
|
|
begin
|
|
Node := TsdDeclaration.Create(Self);
|
|
FRootNodes.Insert(0, Node);
|
|
end;
|
|
end;
|
|
if assigned(Node) then
|
|
TsdDeclaration(Node).Version := Value;
|
|
end;
|
|
|
|
procedure TNativeXml.WriteStream(S: TStream);
|
|
var
|
|
i: integer;
|
|
Node: TXmlNode;
|
|
begin
|
|
if not assigned(Root) and FParserWarnings then
|
|
raise EFilerError.Create(sRootElementNotDefined);
|
|
|
|
DoProgress(0);
|
|
|
|
|
|
// write the root nodes
|
|
for i := 0 to FRootNodes.Count - 1 do
|
|
begin
|
|
// external codepage info
|
|
if i = 0 then
|
|
begin
|
|
Node := FRootNodes[i];
|
|
if Node.ElementType = xeDeclaration then
|
|
DoDebugOut(Self, wsInfo, Format('writing declaration with encoding "%s" and codepage %d',
|
|
[TsdDeclaration(Node).Encoding, FExternalCodePage]));
|
|
end;
|
|
|
|
FRootNodes[i].WriteStream(S);
|
|
end;
|
|
|
|
DoProgress(S.Size);
|
|
end;
|
|
|
|
function TNativeXml.WriteToString: string;
|
|
var
|
|
S: TsdStringStream;
|
|
begin
|
|
S := TsdStringStream.Create('');
|
|
try
|
|
SaveToStream(S);
|
|
Result := S.DataString;
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.WriteToLocalString: Utf8String;
|
|
var
|
|
S: TsdStringStream;
|
|
begin
|
|
S := TsdStringStream.Create('');
|
|
try
|
|
WriteStream(S);
|
|
Result := S.DataString;
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.WriteToLocalUnicodeString: UnicodeString;
|
|
begin
|
|
Result := sdUtf8ToWide(WriteToLocalString);
|
|
end;
|
|
|
|
function TNativeXml.FindFirst: TXmlNode;
|
|
begin
|
|
if not assigned(FRootNodes) then
|
|
Result := nil
|
|
else
|
|
Result := FRootNodes.FindFirst;
|
|
end;
|
|
|
|
function TNativeXml.FindNext(ANode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := FRootNodes.FindNext(ANode);
|
|
end;
|
|
|
|
procedure TNativeXml.ForEach(Sender: TObject; AEvent: TsdXmlNodeEvent);
|
|
var
|
|
Node: TXmlNode;
|
|
begin
|
|
if not assigned(AEvent) or not assigned(Sender) then
|
|
exit;
|
|
Node := FindFirst;
|
|
while assigned(Node) do
|
|
begin
|
|
AEvent(Sender, Node);
|
|
Node := FindNext(Node);
|
|
end;
|
|
end;
|
|
|
|
function TNativeXml.AttrText(AName, AValue: Utf8String): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := AValue;
|
|
end;
|
|
|
|
function TNativeXml.AttrInt(AName: Utf8String; AValue: integer): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := sdIntToString(AValue);
|
|
end;
|
|
|
|
function TNativeXml.AttrInt64(AName: Utf8String; AValue: int64): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := sdInt64ToString(AValue);
|
|
end;
|
|
|
|
function TNativeXml.AttrHex(AName: Utf8String; AValue, ADigits: integer): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := '$' + IntToHex(AValue, ADigits);
|
|
end;
|
|
|
|
function TNativeXml.AttrHex(AName: Utf8String; AValue: int64; ADigits: integer): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := '$' + IntToHex(AValue, ADigits);
|
|
end;
|
|
|
|
function TNativeXml.AttrFloat(AName: Utf8String; AValue: double): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := sdFloatToString(AValue, cDefaultFloatSignificantDigits,
|
|
cDefaultFloatAllowScientific);
|
|
end;
|
|
|
|
function TNativeXml.AttrFloat(AName: Utf8String; AValue: double; ASignificantDigits: integer;
|
|
AAllowScientific: boolean): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := sdFloatToString(AValue, ASignificantDigits, AAllowScientific);
|
|
end;
|
|
|
|
function TNativeXml.AttrDateTime(AName: Utf8String; AValue: TDateTime): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := sdDateTimeToString(AValue, True, True, FSplitSecondDigits);
|
|
end;
|
|
|
|
function TNativeXml.AttrBool(AName: Utf8String; AValue: boolean): TsdAttribute;
|
|
begin
|
|
Result := TsdAttribute.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := sdBoolToString(AValue);
|
|
end;
|
|
|
|
function TNativeXml.NodeNew(AName: Utf8String): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, '', xeElement, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewEx(AName: Utf8String; out AXmlNode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, '', xeElement, AXmlNode, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNew(AName: Utf8String; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, '', xeElement, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewEx(AName: Utf8String; out AXmlNode: TXmlNode;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, '', xeElement, AXmlNode, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewType(AName: Utf8String; AElementType: TsdElementType): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, '', AElementType, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTypeEx(AName: Utf8string; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, '', AElementType, AXmlNode, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewType(AName: Utf8string; AElementType: TsdElementType;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, '', AElementType, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTypeEx(AName: Utf8String; AElementType: TsdElementType;
|
|
out AXmlNode: TXmlNode; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, '', AElementType, AXmlNode, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewAttr(AName: Utf8String;
|
|
Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, '', xeElement, Attributes, []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewAttrEx(AName: Utf8String; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, '', xeElement, AXmlNode, Attributes, []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewAttr(AName: Utf8String; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, '', xeElement, Attributes, SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewAttrEx(AName: Utf8String; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, '', xeElement, AXmlNode, Attributes,
|
|
SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewText(AName, AValue: Utf8String): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, AValue, xeElement, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextEx(AName, AValue: Utf8String;
|
|
out AXmlNode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, AValue, xeElement, AXmlNode, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewText(AName, AValue: Utf8String; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, AValue, xeElement, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextEx(AName, AValue: Utf8String; out AXmlNode: TXmlNode;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, AValue, xeElement, AXmlNode, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextType(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, AValue, AElementType, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextTypeEx(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, AValue, AElementType, AXmlNode, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextType(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, AValue, AElementType, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextTypeEx(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, AValue, AElementType, AXmlNode, [],
|
|
SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextAttr(AName, AValue: Utf8String;
|
|
Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, AValue, xeElement, Attributes, []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextAttrEx(AName, AValue: Utf8String; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, AValue, xeElement, AXmlNode, Attributes,
|
|
[]);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextAttr(AName, AValue: Utf8String;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, AValue, xeElement, Attributes, SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextAttrEx(AName, AValue: Utf8String; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, AValue, xeElement, AXmlNode, Attributes,
|
|
SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextTypeAttr(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, AValue, AElementType, Attributes, []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextTypeAttrEx(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, AValue, AElementType, AXmlNode,
|
|
Attributes, []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextTypeAttr(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
var
|
|
NodeClass: TsdNodeClass;
|
|
begin
|
|
NodeClass := cNodeClass[AElementType];
|
|
Result := NodeClass.Create(Self);
|
|
Result.Name := AName;
|
|
Result.Value := AValue;
|
|
|
|
Result.AttributesAdd(Attributes);
|
|
Result.NodesAdd(SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewTextTypeAttrEx(AName, AValue: Utf8String;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
AXmlNode := NodeNewTextTypeAttr(AName, AValue, AElementType, Attributes,
|
|
SubNodes);
|
|
Result := AXmlNode;
|
|
end;
|
|
|
|
function TNativeXml.NodeNewInt(AName: Utf8String; AValue: integer): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, sdIntToString(AValue), xeElement, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntEx(AName: Utf8String; AValue: integer;
|
|
out AXmlNode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, sdIntToString(AValue), xeElement, AXmlNode,
|
|
[], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewInt(AName: Utf8String; AValue: integer;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, sdIntToString(AValue), xeElement, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntEx(AName: Utf8String; AValue: integer;
|
|
out AXmlNode: TXmlNode; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, sdIntToString(AValue), xeElement, AXmlNode,
|
|
[], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntAttr(AName: Utf8String; AValue: integer;
|
|
Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, sdIntToString(AValue), xeElement, Attributes,
|
|
[]);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntAttrEx(AName: Utf8String; AValue: integer;
|
|
out AXmlNode: TXmlNode; Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, sdIntToString(AValue), xeElement, AXmlNode,
|
|
Attributes, []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntAttr(AName: Utf8String; AValue: integer;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, sdIntToString(AValue), xeElement, Attributes,
|
|
SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntAttrEx(AName: Utf8String; AValue: integer;
|
|
out AXmlNode: TXmlNode; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, sdIntToString(AValue), xeElement, AXmlNode,
|
|
Attributes, SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntTypeAttr(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, sdIntToString(AValue), AElementType, Attributes,
|
|
[]);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntTypeAttrEx(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, sdIntToString(AValue), AElementType, AXmlNode,
|
|
Attributes, []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntType(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, sdIntToString(AValue), AElementType, [], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntTypeEx(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, sdIntToString(AValue), AElementType, AXmlNode,
|
|
[], []);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntType(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, sdIntToString(AValue), AElementType, [], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntTypeEx(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, sdIntToString(AValue), AElementType, AXmlNode,
|
|
[], SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntTypeAttr(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; Attributes: array of TsdAttribute;
|
|
SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttr(AName, sdIntToString(AValue), AElementType, Attributes,
|
|
SubNodes);
|
|
end;
|
|
|
|
function TNativeXml.NodeNewIntTypeAttrEx(AName: Utf8String; AValue: integer;
|
|
AElementType: TsdElementType; out AXmlNode: TXmlNode;
|
|
Attributes: array of TsdAttribute; SubNodes: array of TXmlNode): TXmlNode;
|
|
begin
|
|
Result := NodeNewTextTypeAttrEx(AName, sdIntToString(AValue), AElementType, AXmlNode,
|
|
Attributes, SubNodes);
|
|
end;
|
|
|
|
class function TNativeXml.DecodeBase64(const Source: Utf8String; OnDebug: TsdDebugEvent): RawByteString;
|
|
begin
|
|
try
|
|
Result := NativeXml.DecodeBase64(Source);
|
|
except
|
|
on EFilerError do
|
|
OnDebug(nil, wsFail, sErrorCalcStreamLength);
|
|
end;
|
|
end;
|
|
|
|
class function TNativeXml.EncodeBase64(const Source: RawByteString; const ControlChars: Utf8String): Utf8String;
|
|
begin
|
|
Result := sdAddControlChars(NativeXml.EncodeBase64(Source), ControlChars);
|
|
end;
|
|
|
|
procedure TNativeXml.SetExternalEncoding(const Value: TsdStringEncoding);
|
|
var
|
|
Codepage: integer;
|
|
Node: TXmlNode;
|
|
begin
|
|
Codepage := cStringEncodingCodepages[Value];
|
|
if Codepage = 0 then
|
|
begin
|
|
DoDebugOut(Self, wsFail,
|
|
Format('external encoding "%s" is not allowed (use ExternalCodepage)',
|
|
[cStringEncodingCharsetNames[Value]]));
|
|
exit;
|
|
end;
|
|
|
|
FExternalEncoding := Value;
|
|
FExternalCodepage := Codepage;
|
|
|
|
Node := GetOrCreateDeclarationNode;
|
|
TsdDeclaration(Node).Encoding := cStringEncodingCharsetNames[FExternalEncoding];
|
|
end;
|
|
|
|
procedure TNativeXml.SetExternalCodepage(const Value: integer);
|
|
var
|
|
Node: TXmlNode;
|
|
i, Idx: integer;
|
|
se: TsdStringEncoding;
|
|
begin
|
|
if FExternalCodepage = Value then
|
|
exit;
|
|
|
|
// search for codepage info
|
|
Idx := -1;
|
|
for i := 0 to cCodepageInfoCount - 1 do
|
|
begin
|
|
if cCodepageInfo[i].Codepage = Value then
|
|
begin
|
|
Idx := i;
|
|
break;
|
|
end;
|
|
end;
|
|
|
|
if Idx = -1 then
|
|
begin
|
|
DoDebugOut(Self, wsFail, Format('external codepage "%d" not found', [Value]));
|
|
exit;
|
|
end;
|
|
|
|
// codepage found
|
|
FExternalCodepage := Value;
|
|
|
|
// start by default for the bulk of codepages
|
|
FExternalEncoding := seAnsi;
|
|
|
|
// search string encodings for special codepages
|
|
for se := low(TsdStringEncoding) to high(TsdStringEncoding) do
|
|
begin
|
|
if cStringEncodingCodepages[se] = FExternalCodepage then
|
|
begin
|
|
FExternalEncoding := se;
|
|
end;
|
|
end;
|
|
|
|
Node := GetOrCreateDeclarationNode;
|
|
TsdDeclaration(Node).Encoding := cCodepageInfo[Idx].Name;
|
|
end;
|
|
|
|
procedure TNativeXml.SetBinaryDocument(ABinaryXml: TsdBinaryXml);
|
|
begin
|
|
ABinaryXml.Document := Self;
|
|
// additional options
|
|
case FBinaryMethod of
|
|
bmDefault:
|
|
begin
|
|
ABinaryXml.OnEncode := nil;
|
|
ABinaryXml.OnDecode := nil;
|
|
end;
|
|
{$ifdef USEZLIB}
|
|
bmZLib:
|
|
begin
|
|
ABinaryXml.OnEncode := ZLibEncode;
|
|
ABinaryXml.OnDecode := ZLibDecode;
|
|
end;
|
|
{$ifdef USEAES}
|
|
bmAesz:
|
|
begin
|
|
ABinaryXml.OnEncode := AeszEncode;
|
|
ABinaryXml.OnDecode := AeszDecode;
|
|
end;
|
|
{$endif USEAES}
|
|
{$endif USEZLIB}
|
|
else
|
|
DoDebugOut(Self, wsWarn, 'binary method not available (check compiler defines)');
|
|
end;
|
|
end;
|
|
|
|
procedure TNativeXml.SetBinaryMethod(const Value: TsdXmlBinaryMethod);
|
|
begin
|
|
FBinaryMethod := Value;
|
|
end;
|
|
|
|
{ TsdXmlCanonicalizer - experimental!}
|
|
|
|
function TsdXmlCanonicalizer.Canonicalize(AXml: TNativeXml): integer;
|
|
var
|
|
Decl: TXmlNode;
|
|
DTD: TsdDocType;
|
|
DtdEntityNodes: array of TXmlNode;
|
|
i, j, TotalNodeCount, CharDataCount,
|
|
NewReferencesCount: integer;
|
|
Node: TXmlNode;
|
|
CharData: TsdCharData;
|
|
SubstituteText: Utf8String;
|
|
// {$ifdef D5UP}
|
|
Src: PChar;
|
|
// {$endif D5UP}
|
|
begin
|
|
CharDataCount := 0;
|
|
Result := 0; // ReferencesCount
|
|
|
|
// encode in utf-8 only - this is already achieved by the parser
|
|
|
|
// xml compacted
|
|
//AXml.XmlFormat := xfCompact;
|
|
|
|
// remove xml declaration
|
|
Decl := AXml.RootNodes[0];
|
|
if Decl is TsdDeclaration then
|
|
begin
|
|
AXml.RootNodes.Delete(0);
|
|
end;
|
|
|
|
// recursively expand entities to their character equivalent:
|
|
|
|
// find dtdentity nodes in the dtd
|
|
DTD := TsdDocType(AXml.RootNodes.ByType(xeDocType));
|
|
if assigned(DTD) then
|
|
begin
|
|
j := 0;
|
|
SetLength(DtdEntityNodes, j);
|
|
for i := 0 to DTD.NodeCount - 1 do
|
|
if DTD.Nodes[i] is TsdDtdEntity then
|
|
begin
|
|
inc(j);
|
|
SetLength(DtdEntityNodes, j);
|
|
DtdEntityNodes[j - 1] := TsdDtdEntity(DTD.Nodes[i]);
|
|
end;
|
|
end;
|
|
|
|
// find references
|
|
repeat
|
|
NewReferencesCount := 0;
|
|
TotalNodeCount := 0;
|
|
Node := AXml.FindFirst;
|
|
|
|
while assigned(Node) do
|
|
begin
|
|
inc(TotalNodeCount);
|
|
|
|
// check for entity references
|
|
if Node is TsdCharData then
|
|
begin
|
|
inc(CharDataCount);
|
|
|
|
// non-standard references usually come from entity references in the dtd
|
|
CharData := TsdCharData(Node);
|
|
if CharData.HasNonStandardReferences then
|
|
begin
|
|
inc(NewReferencesCount);
|
|
|
|
// substitute chardata value using the references
|
|
{$ifdef D7UP}
|
|
SubstituteText := AnsiDequotedStr(CharData.GetValueUsingReferences(DtdEntityNodes), '"');
|
|
{$else D7UP}
|
|
// D5 version
|
|
Src := PChar(CharData.GetValueUsingReferences(DtdEntityNodes));
|
|
SubstituteText := AnsiExtractQuotedStr(Src, '"');
|
|
{$endif D7UP}
|
|
|
|
Node := AXml.ParseSubstituteContentFromNode(Chardata, SubstituteText);
|
|
end;
|
|
end;
|
|
|
|
Node := AXml.FindNext(Node);
|
|
end;
|
|
inc(Result, NewReferencesCount);
|
|
|
|
until NewReferencesCount = 0;
|
|
|
|
// replace CDATA sections by character equivalent
|
|
// todo
|
|
|
|
// encode special < > and " entities
|
|
// todo
|
|
|
|
// normalize attributes as if by validating parser
|
|
// todo
|
|
|
|
// open empty elements with start and end tags
|
|
// todo
|
|
|
|
// sort namespace declarations and attributes
|
|
// todo
|
|
|
|
DoDebugOut(Self, wsInfo, format('total node count: %d, chardata count: %d, references count: %d',
|
|
[TotalNodeCount, CharDataCount, Result]));
|
|
DoDebugOut(Self, wsInfo, 'C14N created');
|
|
end;
|
|
|
|
{ TsdXmlParser }
|
|
|
|
function TsdXmlParser.CheckString(const S: Utf8String): boolean;
|
|
// case-insensitive string check
|
|
var
|
|
i, Count, StartIdx: integer;
|
|
begin
|
|
Count := MakeDataAvailable;
|
|
StartIdx := FUtf8CurrentIdx;
|
|
Result := True;
|
|
for i := 1 to length(S) do
|
|
begin
|
|
if FEndOfStream then
|
|
begin
|
|
Result := False;
|
|
exit;
|
|
end;
|
|
// case-insensitive, so we use LoCase in both sides (LoCase is
|
|
// faster than function LowerCase, since it deals directly with chars).
|
|
if LoCase(S[i]) <> LoCase(FUtf8Buffer[FUtf8CurrentIdx]) then
|
|
begin
|
|
Result := False;
|
|
// revert
|
|
FUtf8CurrentIdx := StartIdx;
|
|
exit;
|
|
end;
|
|
IncCurrentIdxCheck(Count);
|
|
end;
|
|
end;
|
|
|
|
constructor TsdXmlParser.Create(ASource: TStream; AChunkSize: integer);
|
|
begin
|
|
inherited Create;
|
|
FSource := ASource;
|
|
FChunkSize := AChunkSize;
|
|
SetLength(FRawBuffer, FChunkSize);
|
|
|
|
// Read from the stream directly to the raw buffer
|
|
FRawFirstIdx := 0;
|
|
FRawLastIdx := FSource.Read(FRawBuffer[0], FChunkSize);
|
|
FUtf8FirstIdx := 0;
|
|
FUtf8CurrentIdx := 0;
|
|
FLastChar0D := False;
|
|
end;
|
|
|
|
destructor TsdXmlParser.Destroy;
|
|
begin
|
|
SetLength(FRawBuffer, 0);
|
|
SetLength(FUtf8Buffer, 0);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TsdXmlParser.EncodeChunk;
|
|
|
|
// local
|
|
procedure EncodeAnsiChunk;
|
|
var
|
|
RawLen, Utf8Len: integer;
|
|
begin
|
|
RawLen := FRawLastIdx - FRawFirstIdx;
|
|
SetLength(FRawBuffer, FRawFirstIdx + RawLen);
|
|
// Utf8 buffer might be 3x ansi size at max
|
|
SetLength(FUtf8Buffer, FUtf8FirstIdx + 3 * RawLen);
|
|
Utf8Len := sdAnsiToUtf8Buffer(FRawBuffer[FRawFirstIdx], FUtf8Buffer[FUtf8FirstIdx], FCodePage, RawLen, FLastChar0D);
|
|
FUtf8LastIdx := FUtf8FirstIdx + Utf8Len;
|
|
end;
|
|
|
|
// local
|
|
procedure EncodeUtf8Chunk;
|
|
var
|
|
RawLen, Utf8Len: integer;
|
|
begin
|
|
RawLen := FRawLastIdx - FRawFirstIdx;
|
|
// buffers
|
|
SetLength(FRawBuffer, FRawFirstIdx + RawLen);
|
|
SetLength(FUtf8Buffer, FUtf8FirstIdx + RawLen);
|
|
Utf8Len := sdNormaliseBuffer(FRawBuffer[FRawFirstIdx], FUtf8Buffer[FUtf8FirstIdx], RawLen, FLastChar0D);
|
|
FUtf8LastIdx := FUtf8FirstIdx + Utf8Len;
|
|
end;
|
|
|
|
// local
|
|
procedure EncodeUtf16Chunk;
|
|
type
|
|
TWordArray = array of word;
|
|
var
|
|
RawLen, Utf8Len: integer;
|
|
i: integer;
|
|
W: word;
|
|
begin
|
|
RawLen := FRawLastIdx - FRawFirstIdx;
|
|
|
|
// If UTF16 BE (Big Endian), we must swap byte order
|
|
if FEncoding = seUTF16BE then
|
|
begin
|
|
for i := FRawFirstIdx div 2 to FRawLastIdx div 2 - 1 do
|
|
begin
|
|
W := TWordArray(FRawBuffer)[i];
|
|
TWordArray(FRawBuffer)[i] := Swap(W);
|
|
end;
|
|
end;
|
|
|
|
// Utf8 buffer might be 2x utf16 size at max
|
|
SetLength(FRawBuffer, FRawFirstIdx + RawLen);
|
|
SetLength(FUtf8Buffer, FUtf8FirstIdx + (2 * RawLen));
|
|
|
|
// Now convert from UTF16 to UTF8
|
|
Utf8Len := sdWideToUtf8Buffer(FRawBuffer[FRawFirstIdx], FUtf8Buffer[FUtf8FirstIdx], RawLen div 2, FLastChar0D);
|
|
FUtf8LastIdx := FUtf8FirstIdx + Utf8Len;
|
|
end;
|
|
|
|
// main
|
|
begin
|
|
// EOL normalisation is now integrated so no need for a separate NormaliseEOL function.
|
|
|
|
// IMPORTANT OBSERVATION:
|
|
// Normalization could occur between two chunks so boolean FLastChar0D must be
|
|
// transmitted (however, it is usually false). In case the final char of first
|
|
// chunk is $0D, FLastChar0D signals this and if first char of next chunk is $0A,
|
|
// this char is skipped.
|
|
|
|
// call EncodeChunk methods based on encoding
|
|
case FEncoding of
|
|
seAnsi:
|
|
begin
|
|
if (FCodePage = 0) or (FCodePage = 65001) then // UTF8
|
|
begin
|
|
EncodeUtf8Chunk;
|
|
end else
|
|
begin
|
|
EncodeAnsiChunk;
|
|
end;
|
|
end;
|
|
|
|
seUTF8:
|
|
begin
|
|
EncodeUtf8Chunk;
|
|
end;
|
|
|
|
seUTF16BE, seUTF16LE:
|
|
begin
|
|
EncodeUtf16Chunk;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TsdXmlParser.Flush;
|
|
var
|
|
i: integer;
|
|
RawLen, Utf8Len: integer;
|
|
begin
|
|
// Number of bytes to move
|
|
RawLen := FRawLastIdx - FRawFirstIdx;
|
|
Utf8Len := FUtf8LastIdx - FUtf8FirstIdx;
|
|
|
|
if FUtf8CurrentIdx - FUtf8FirstIdx > 0 then
|
|
begin
|
|
// Calcuate base line number and base position
|
|
for i := 0 to FUtf8FirstIdx - 1 do
|
|
begin
|
|
// linefeed
|
|
if FUtf8Buffer[i] = #$0A then
|
|
inc(FBaseLineNumber);
|
|
end;
|
|
inc(FUtf8BasePosition, FUtf8FirstIdx);
|
|
|
|
// moves
|
|
Move(FRawBuffer[FRawFirstIdx], FRawBuffer[0], RawLen);
|
|
Move(FUtf8Buffer[FUtf8FirstIdx], FUtf8Buffer[0], Utf8Len);
|
|
|
|
// update current idx
|
|
dec(FUtf8CurrentIdx, FUtf8FirstIdx);
|
|
|
|
// update first/last indices
|
|
FRawFirstIdx := 0;
|
|
FRawLastIdx := RawLen;
|
|
FUtf8FirstIdx := 0;
|
|
FUtf8LastIdx := Utf8Len;
|
|
end;
|
|
end;
|
|
|
|
function TsdXmlParser.GetLineNumber: int64;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := FBaseLineNumber;
|
|
for i := 0 to FUtf8CurrentIdx - 1 do
|
|
begin
|
|
// linefeed
|
|
if FUtf8Buffer[i] = #$0A then
|
|
inc(Result);
|
|
end;
|
|
end;
|
|
|
|
function TsdXmlParser.GetPosition: int64;
|
|
begin
|
|
Result := FUtf8BasePosition + FUtf8CurrentIdx;
|
|
end;
|
|
|
|
procedure TsdXmlParser.IncCurrentIdxCheck(var BytesAvail: integer);
|
|
// increment FCurrentIdx and check bytes available
|
|
begin
|
|
inc(FUtf8CurrentIdx);
|
|
dec(BytesAvail);
|
|
if BytesAvail <= 0 then
|
|
BytesAvail := MakeDataAvailable
|
|
end;
|
|
|
|
function TsdXmlParser.IsBinaryXml: boolean;
|
|
var
|
|
i: integer;
|
|
Cookie: array[0..3] of AnsiChar;
|
|
begin
|
|
Result := False;
|
|
if FRawLastIdx <= length(cBinaryXmlCookie) then
|
|
exit;
|
|
|
|
// read binary cookie
|
|
Move(FRawBuffer[0], Cookie, 4);
|
|
|
|
for i := 0 to length(cBinaryXmlCookie) - 1 do
|
|
if Cookie[i] <> cBinaryXmlCookie[i] then
|
|
exit;
|
|
|
|
// cookie for binary xml matches
|
|
Result := True;
|
|
end;
|
|
|
|
function TsdXmlParser.LoCase(Ch: AnsiChar): AnsiChar;
|
|
const
|
|
cInterval: integer = Ord('a') - Ord('A');
|
|
begin
|
|
Result := Ch;
|
|
case Result of
|
|
'A'..'Z': inc(Result, cInterval);
|
|
end;
|
|
end;
|
|
|
|
function TsdXmlParser.MakeDataAvailable: integer;
|
|
var
|
|
BytesRead: integer;
|
|
begin
|
|
Result := FUtf8LastIdx - FUtf8CurrentIdx;
|
|
while Result < 1 do
|
|
begin
|
|
// We must make data available
|
|
BytesRead := ReadNextChunk;
|
|
Result := FUtf8LastIdx - FUtf8CurrentIdx;
|
|
|
|
// Still no data available?
|
|
if BytesRead = 0 then
|
|
begin
|
|
FEndOfStream := True;
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TsdXmlParser.MoveBack;
|
|
begin
|
|
assert(FUtf8CurrentIdx > 0);
|
|
dec(FUtf8CurrentIdx);
|
|
end;
|
|
|
|
function TsdXmlParser.NextChar: AnsiChar;
|
|
begin
|
|
MakeDataAvailable;
|
|
if FEndOfStream then
|
|
begin
|
|
Result := #0;
|
|
exit;
|
|
end;
|
|
Result := FUtf8Buffer[FUtf8CurrentIdx];
|
|
inc(FUtf8CurrentIdx);
|
|
end;
|
|
|
|
function TsdXmlParser.ReadNextChunk: integer;
|
|
begin
|
|
SetLength(FRawBuffer, FRawLastIdx + FChunkSize);
|
|
|
|
// Read from the stream directly to our chunk
|
|
// Result is the bytes read
|
|
Result := FSource.Read(FRawBuffer[FRawLastIdx], FChunkSize);
|
|
if Result > 0 then
|
|
begin
|
|
FRawFirstIdx := FRawLastIdx;
|
|
FUtf8FirstIdx := FUtf8LastIdx;
|
|
inc(FRawLastIdx, Result);
|
|
EncodeChunk;
|
|
end;
|
|
end;
|
|
|
|
function TsdXmlParser.ReadQuotedString(AQuote: AnsiChar): Utf8String;
|
|
begin
|
|
// It seems that the xml spec simply does not allow double quotes as in
|
|
// Delphi, so we do not need a complicated algo to do this. We can simply
|
|
// search for the quote again as terminator.
|
|
Result := ReadStringUntilChar(AQuote);
|
|
end;
|
|
|
|
function TsdXmlParser.ReadString(AIndex, ACount: integer): Utf8String;
|
|
begin
|
|
SetLength(Result, ACount);
|
|
if ACount > 0 then
|
|
Move(FUtf8Buffer[AIndex], Result[1], ACount);
|
|
end;
|
|
|
|
function TsdXmlParser.ReadStringUntil(const Terminator: Utf8String): Utf8String;
|
|
var
|
|
Count, MatchLen: integer;
|
|
FirstChar: AnsiChar;
|
|
StartIdx: integer;
|
|
begin
|
|
FirstChar := Terminator[1];
|
|
MatchLen := length(Terminator);
|
|
StartIdx := FUtf8CurrentIdx;
|
|
Count := MakeDataAvailable;
|
|
while not FEndOfStream do
|
|
begin
|
|
if FUtf8Buffer[FUtf8CurrentIdx] = FirstChar then
|
|
begin
|
|
|
|
if CheckString(Terminator) then
|
|
begin
|
|
// We found the terminating string
|
|
Result := ReadString(StartIdx, FUtf8CurrentIdx - StartIdx - MatchLen);
|
|
exit;
|
|
end;
|
|
|
|
end;
|
|
IncCurrentIdxCheck(Count);
|
|
end;
|
|
// when left here stream ended prematurely
|
|
DoDebugOut(Self, wsWarn, Format(sPrematureEnd, [GetPosition]));
|
|
end;
|
|
|
|
function TsdXmlParser.ReadStringUntilChar(AChar: AnsiChar): Utf8String;
|
|
var
|
|
Count: integer;
|
|
StartIdx: integer;
|
|
begin
|
|
Count := MakeDataAvailable;
|
|
|
|
StartIdx := FUtf8CurrentIdx;
|
|
while not FEndOfStream do
|
|
begin
|
|
if FUtf8Buffer[FUtf8CurrentIdx] = AChar then
|
|
begin
|
|
// We found AChar
|
|
Result := ReadString(StartIdx, FUtf8CurrentIdx - StartIdx);
|
|
// Adjust FUtf8CurrentIdx
|
|
inc(FUtf8CurrentIdx);
|
|
exit;
|
|
end;
|
|
IncCurrentIdxCheck(Count);
|
|
end;
|
|
|
|
// Arriving here: end of stream and AChar not reached
|
|
Result := ReadString(StartIdx, FUtf8CurrentIdx - StartIdx);
|
|
end;
|
|
|
|
procedure TsdXmlParser.SetCodePage(const Value: integer);
|
|
begin
|
|
FCodePage := Value;
|
|
// re-encode the chunk (e.g. from default utf-8 codepage to other ansi codepage)
|
|
EncodeChunk;
|
|
end;
|
|
|
|
// TsdXmlParser
|
|
|
|
function TsdXmlParser.NextCharSkipBlanks(var Blanks: Utf8String): AnsiChar;
|
|
var
|
|
Count: integer;
|
|
begin
|
|
Blanks := '';
|
|
Count := MakeDataAvailable;
|
|
while not FEndOfStream do
|
|
begin
|
|
Result := FUtf8Buffer[FUtf8CurrentIdx];
|
|
IncCurrentIdxCheck(Count);
|
|
if not (Result in cXmlBlankChars) then
|
|
exit;
|
|
Blanks := Blanks + Result;
|
|
end;
|
|
Result := #0;
|
|
end;
|
|
|
|
procedure TsdXmlParser.ReadBOM;
|
|
var
|
|
i, j: integer;
|
|
BOM: array[0..3] of byte;
|
|
BomInfoFound: boolean;
|
|
begin
|
|
if FRawLastIdx <= 4 then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, Format(sPrematureEnd, [FRawLastIdx]));
|
|
exit;
|
|
end;
|
|
|
|
// read the BOM if it is there
|
|
Move(FRawBuffer[0], BOM, 4);
|
|
|
|
i := 0;
|
|
BomInfoFound := False;
|
|
while i < cBomInfoListCount do
|
|
begin
|
|
BomInfoFound := True;
|
|
for j := 0 to cBomInfoList[i].Len - 1 do
|
|
begin
|
|
if BOM[j] <> cBomInfoList[i].BOM[j] then
|
|
begin
|
|
BomInfoFound := False;
|
|
break;
|
|
end;
|
|
end;
|
|
if BomInfoFound then
|
|
begin
|
|
FBomInfo := cBomInfoList[i];
|
|
FEncoding := FBomInfo.Encoding;
|
|
break;
|
|
end;
|
|
inc(i);
|
|
end;
|
|
|
|
// BOM info found?
|
|
if BomInfoFound then
|
|
begin
|
|
// check for non-supported encodings
|
|
if not (FEncoding in [seAnsi, seUTF8, seUTF16BE, seUTF16LE]) then
|
|
begin
|
|
DoDebugOut(Self, wsFail, Format(sUnsupportedEncoding,
|
|
[cStringEncodingCharsetNames[FEncoding]]));
|
|
// avoid trying to read exotic encodings such as EBDIC
|
|
exit;
|
|
end;
|
|
|
|
// Rewind based on BOM
|
|
if FBomInfo.HasBom then
|
|
begin
|
|
FRawLastIdx := FChunkSize - FBomInfo.Len;
|
|
Move(FRawBuffer[FBomInfo.Len], FRawBuffer[0], FRawLastIdx);
|
|
SetLength(FRawBuffer, FRawLastIdx);
|
|
DoDebugOut(Self, wsInfo, Format('BOM with encoding %s',
|
|
[cStringEncodingCharsetNames[FEncoding]]));
|
|
end;
|
|
|
|
end else
|
|
begin
|
|
// No BOM, and unknown encoding, e.g. html instead of xml
|
|
// we use UTF8 as default
|
|
DoDebugOut(Self, wsWarn, sUnknownEncoding);
|
|
FEncoding := seUTF8;
|
|
end;
|
|
|
|
// encode the first chunk
|
|
EncodeChunk;
|
|
end;
|
|
|
|
function TsdXmlParser.ReadOpenTag: TsdElementType;
|
|
var
|
|
AnsiCh: AnsiChar;
|
|
begin
|
|
Result := xeError;
|
|
AnsiCh := NextChar;
|
|
if FEndOfStream then
|
|
exit;
|
|
|
|
case AnsiCh of
|
|
'!':
|
|
begin
|
|
AnsiCh := LoCase(NextChar);
|
|
case AnsiCh of
|
|
'[': if CheckString('cdata[') then
|
|
Result := xeCData;
|
|
'd': if CheckString('octype') then
|
|
Result := xeDocType;
|
|
'e':
|
|
begin
|
|
if CheckString('lement') then
|
|
Result := xeDtdElement;
|
|
if CheckString('ntity') then
|
|
Result := xeDtdEntity;
|
|
end;
|
|
'a': if CheckString('ttlist') then
|
|
Result := xeDtdAttList;
|
|
'n': if CheckString('otation') then
|
|
Result := xeDtdNotation;
|
|
'-': if CheckString('-') then
|
|
Result := xeComment;
|
|
else
|
|
begin
|
|
DoDebugOut(Self, wsFail, Format(sIllegalTag, [AnsiCh, GetPosition]));
|
|
exit;
|
|
end;
|
|
end;
|
|
end;
|
|
'?':
|
|
begin
|
|
if CheckString('xml') then
|
|
begin
|
|
if CheckString('-stylesheet') then
|
|
Result := xeStyleSheet
|
|
else
|
|
Result := xeDeclaration;
|
|
end else
|
|
Result := xeInstruction;
|
|
end;
|
|
'/': Result := xeEndTag;
|
|
else
|
|
Result := xeElement;
|
|
MoveBack;
|
|
end;
|
|
end;
|
|
|
|
function TsdXmlParser.ReadStringUntilBlankOrEndTag: Utf8String;
|
|
var
|
|
Count: integer;
|
|
StartIdx: integer;
|
|
begin
|
|
Count := MakeDataAvailable;
|
|
StartIdx := FUtf8CurrentIdx;
|
|
while not FEndOfStream do
|
|
begin
|
|
if FUtf8Buffer[FUtf8CurrentIdx] in cXmlBlankCharsOrEndTag then
|
|
begin
|
|
// We found the termination
|
|
Result := ReadString(StartIdx, FUtf8CurrentIdx - StartIdx);
|
|
exit;
|
|
end;
|
|
IncCurrentIdxCheck(Count);
|
|
end;
|
|
// when left here, stream ended prematurely
|
|
DoDebugOut(Self, wsWarn, Format(sPrematureEnd, [GetPosition]));
|
|
end;
|
|
|
|
{ TsdXmlWriter }
|
|
|
|
constructor TsdXmlWriter.Create(AOwner: TDebugComponent; ASource: TStream; AChunkSize: integer);
|
|
begin
|
|
inherited Create(ASource, AChunkSize);
|
|
FOwner := AOwner;
|
|
end;
|
|
|
|
destructor TsdXmlWriter.Destroy;
|
|
begin
|
|
SetLength(FRawBuffer, 0);
|
|
inherited;
|
|
end;
|
|
|
|
procedure TsdXmlWriter.DoDebugOut(Sender: TObject; WarnStyle: TsdWarnStyle;
|
|
const AMessage: Utf8String);
|
|
begin
|
|
if FOwner is TDebugComponent then
|
|
TDebugComponent(FOwner).DoDebugOut(Sender, WarnStyle, AMessage);
|
|
end;
|
|
|
|
function TsdXmlWriter.Write(const Buffer; Count: Integer): Longint;
|
|
type
|
|
PWord = ^Word;
|
|
var
|
|
i, AnsiCount, WideCount: integer;
|
|
DefaultCharUsed: boolean;
|
|
W: PWord; // pointer to a word
|
|
// local
|
|
procedure AllocRawBuffer(ASize: integer);
|
|
begin
|
|
if FRawBufferSize < ASize then
|
|
begin
|
|
FRawBufferSize := ASize;
|
|
SetLength(FRawBuffer, FRawBufferSize);
|
|
end;
|
|
end;
|
|
// main
|
|
begin
|
|
case FEncoding of
|
|
|
|
seAnsi:
|
|
begin
|
|
AllocRawBuffer(Count);
|
|
AnsiCount := sdUtf8ToAnsiBuffer(Buffer, FRawBuffer[0], FCodepage, Count, DefaultCharUsed);
|
|
Result := inherited Write(FRawBuffer[0], AnsiCount);
|
|
if DefaultCharUsed then
|
|
begin
|
|
DoDebugOut(Self, wsWarn, sDefaultCharUsed);
|
|
end;
|
|
end;
|
|
|
|
seUTF8:
|
|
begin
|
|
Result := inherited Write(Buffer, Count)
|
|
end;
|
|
|
|
seUTF16LE:
|
|
begin
|
|
AllocRawBuffer(2 * Count);
|
|
WideCount := sdUtf8ToWideBuffer(Buffer, FRawBuffer[0], Count);
|
|
Result := inherited Write(FRawBuffer[0], 2 * WideCount);
|
|
end;
|
|
|
|
seUTF16BE:
|
|
begin
|
|
AllocRawBuffer(2 * Count);
|
|
WideCount := sdUtf8ToWideBuffer(Buffer, FRawBuffer[0], Count);
|
|
|
|
// swap the byte order from little endian to big endian
|
|
W := PWord(@FRawBuffer[0]);
|
|
for i := 0 to WideCount - 1 do
|
|
begin
|
|
W^ := Swap(W^);
|
|
inc(W);
|
|
end;
|
|
|
|
Result := inherited Write(FRawBuffer[0], 2 * WideCount);
|
|
end;
|
|
else
|
|
// unsupported encoding
|
|
DoDebugOut(Self, wsFail, sUnsupportedEncoding);
|
|
Result := 0;
|
|
end;
|
|
end;
|
|
|
|
|
|
{ Utility Functions }
|
|
|
|
{$ifdef MSWINDOWS}
|
|
function sdWideToUtf8(const W: UnicodeString): Utf8String;
|
|
var
|
|
WideCount, Utf8Count: integer;
|
|
LastChar0D: boolean;
|
|
begin
|
|
WideCount := length(W);
|
|
LastChar0D := False;
|
|
SetLength(Result, WideCount * 3); // just to be sure
|
|
if WideCount = 0 then
|
|
exit;
|
|
|
|
Utf8Count := sdWideToUtf8Buffer(W[1], Result[1], WideCount, LastChar0D);
|
|
SetLength(Result, Utf8Count);
|
|
end;
|
|
|
|
function sdUtf8ToWide(const U: Utf8String): UnicodeString;
|
|
var
|
|
Utf8Count, WideCount: integer;
|
|
begin
|
|
Utf8Count := length(U);
|
|
SetLength(Result, Utf8Count);
|
|
if Utf8Count = 0 then
|
|
exit;
|
|
|
|
WideCount := sdUtf8ToWideBuffer(U[1], Result[1], Utf8Count);
|
|
SetLength(Result, WideCount);
|
|
end;
|
|
{$else}
|
|
// FPC functions
|
|
function sdWideToUtf8(const W: UnicodeString): Utf8String;
|
|
begin
|
|
Result := W;
|
|
end;
|
|
|
|
function sdUtf8ToWide(const U: Utf8String): UnicodeString;
|
|
begin
|
|
Result := U;
|
|
end;
|
|
{$endif}
|
|
|
|
function sdWideToUtf8Buffer(const WideBuf; var Utf8Buf; WideCount: integer; var LastChar0D: boolean): integer;
|
|
// Convert an Unicode (UTF16 LE) memory block to UTF8. This routine will process
|
|
// Count wide characters (2 bytes size) to Count UTF8 characters (1-3 bytes).
|
|
// Therefore, the block at Dst must be at least 1.5 the size of the source block.
|
|
// The function returns the number of *bytes* written.
|
|
|
|
// update: EOL normalisation is integrated in this version
|
|
var
|
|
W: word;
|
|
ByteCh: byte;
|
|
WideIdx, Utf8Idx: integer;
|
|
AddChar: boolean;
|
|
begin
|
|
WideIdx := 0;
|
|
Utf8Idx := 0;
|
|
while WideIdx < WideCount do
|
|
begin
|
|
W := TWordArray(WideBuf)[WideIdx];
|
|
if W <= $7F then
|
|
begin
|
|
|
|
AddChar := True;
|
|
ByteCh := byte(W);
|
|
|
|
if ByteCh = $0D then
|
|
begin
|
|
ByteCh := $0A;
|
|
LastChar0D := True;
|
|
end else
|
|
begin
|
|
if (ByteCh = $0A) and LastChar0D then
|
|
AddChar := False;
|
|
LastChar0D := False;
|
|
end;
|
|
|
|
if AddChar then
|
|
begin
|
|
TByteArray(Utf8Buf)[Utf8Idx] := ByteCh;
|
|
inc(Utf8Idx);
|
|
end;
|
|
|
|
end else
|
|
begin
|
|
if W > $7FF then
|
|
begin
|
|
TByteArray(Utf8Buf)[Utf8Idx] := byte($E0 or (W shr 12));
|
|
inc(Utf8Idx);
|
|
TByteArray(Utf8Buf)[Utf8Idx] := byte($80 or ((W shr 6) and $3F));
|
|
inc(Utf8Idx);
|
|
TByteArray(Utf8Buf)[Utf8Idx] := byte($80 or (W and $3F));
|
|
inc(Utf8Idx);
|
|
end else
|
|
begin // $7F < W <= $7FF
|
|
TByteArray(Utf8Buf)[Utf8Idx] := byte($C0 or (W shr 6));
|
|
inc(Utf8Idx);
|
|
TByteArray(Utf8Buf)[Utf8Idx] := byte($80 or (W and $3F));
|
|
inc(Utf8Idx);
|
|
end;
|
|
end;
|
|
inc(WideIdx);
|
|
end;
|
|
Result := Utf8Idx;
|
|
end;
|
|
|
|
function sdUtf8ToWideBuffer(const Utf8Buf; var WideBuf; ByteCount: integer): integer;
|
|
// Convert an UTF8 buffer to Unicode (UTF16 LE) buffer. This routine will process
|
|
// Count *bytes* of UTF8 (each character 1-3 bytes) into UTF16 (each char 2 bytes).
|
|
// Therefore, the block at WideBuf must be at least 2 times the size of Count, since
|
|
// many UTF8 characters consist of just one byte, and are mapped to 2 bytes. The
|
|
// function returns the number of *wide chars* written. Note that the Utf8Buf block must
|
|
// have an exact number of UTF8 characters in it, if Count doesn't match then
|
|
// the last character will be converted anyway (going past the block boundary!)
|
|
var
|
|
W: word;
|
|
C: byte;
|
|
WideIdx, Utf8Idx: integer;
|
|
begin
|
|
Utf8Idx := 0;
|
|
WideIdx := 0;
|
|
while Utf8Idx < ByteCount do
|
|
begin
|
|
// 1st byte
|
|
W := TByteArray(Utf8Buf)[Utf8Idx];
|
|
inc(Utf8Idx);
|
|
if W and $80 <> 0 then
|
|
begin
|
|
W := W and $3F;
|
|
if W and $20 <> 0 then
|
|
begin
|
|
// 2nd byte
|
|
C := TByteArray(Utf8Buf)[Utf8Idx];
|
|
inc(Utf8Idx);
|
|
if C and $C0 <> $80 then
|
|
// malformed trail byte or out of range char
|
|
Continue;
|
|
W := word((W shl 6) or (C and $3F));
|
|
end;
|
|
// 2nd or 3rd byte
|
|
C := TByteArray(Utf8Buf)[Utf8Idx];
|
|
inc(Utf8Idx);
|
|
if C and $C0 <> $80 then
|
|
// malformed trail byte
|
|
Continue;
|
|
TWordArray(WideBuf)[WideIdx] := word((W shl 6) or (C and $3F));
|
|
inc(WideIdx);
|
|
end else
|
|
begin
|
|
TWordArray(WideBuf)[WideIdx] := W;
|
|
inc(WideIdx);
|
|
end;
|
|
end;
|
|
Result := WideIdx;
|
|
end;
|
|
|
|
function sdAnsiToUtf8(const A: AnsiString; ACodePage: integer): Utf8String;
|
|
var
|
|
AnsiCount, Utf8Count: integer;
|
|
LastChar0D: boolean;
|
|
begin
|
|
AnsiCount := length(A);
|
|
SetLength(Result, AnsiCount * 3); // just to be sure
|
|
if AnsiCount = 0 then
|
|
exit;
|
|
|
|
LastChar0D := False;
|
|
Utf8Count := sdAnsiToUtf8Buffer(A[1], Result[1], ACodePage, AnsiCount, LastChar0D);
|
|
SetLength(Result, Utf8Count);
|
|
end;
|
|
|
|
function sdAnsiToUtf8Buffer(const AnsiBuf; var Utf8Buf; ACodePage, AnsiCount: integer; var LastChar0D: boolean): integer;
|
|
// ansi to utf8 with EOL normalisation
|
|
var
|
|
AnsiIdx, Utf8Idx: integer;
|
|
AnsiCh: AnsiChar;
|
|
WideCh: WideChar;
|
|
Len: integer;
|
|
AddChar: boolean;
|
|
begin
|
|
AnsiIdx := 0;
|
|
Utf8Idx := 0;
|
|
|
|
while AnsiIdx < AnsiCount do
|
|
begin
|
|
AddChar := True;
|
|
AnsiCh := TAnsiCharArray(AnsiBuf)[AnsiIdx];
|
|
|
|
if ord(AnsiCh) < $80 then
|
|
begin
|
|
// characters smaller than $80
|
|
|
|
if AnsiCh = #$0D then
|
|
begin
|
|
AnsiCh := #$0A;
|
|
LastChar0D := True;
|
|
end else
|
|
begin
|
|
if (AnsiCh = #$0A) and LastChar0D then
|
|
AddChar := False;
|
|
LastChar0D := False;
|
|
end;
|
|
|
|
if AddChar then
|
|
begin
|
|
TAnsiCharArray(Utf8Buf)[Utf8Idx] := AnsiCh;
|
|
inc(Utf8Idx);
|
|
end;
|
|
|
|
end else
|
|
begin
|
|
// characters >= $80: copy to widechar using codepage, then convert to Utf8
|
|
// MultiByteToWideChar is in the Windows unit of Borland Delphi7 and on
|
|
MultiByteToWideChar(ACodePage, 0, @AnsiCh, 1, @WideCh, 1);
|
|
Len := sdWideToUtf8Buffer(WideCh, TAnsiCharArray(Utf8Buf)[Utf8Idx], 1, LastChar0D);
|
|
inc(Utf8Idx, Len);
|
|
end;
|
|
inc(AnsiIdx);
|
|
end;
|
|
Result := Utf8Idx;
|
|
end;
|
|
|
|
function sdNormaliseBuffer(const RawBuf; var NormBuf; Count: integer; var LastChar0D: boolean): integer;
|
|
// raw to normalised buffer (suitable for UTF8 or ansi)
|
|
var
|
|
RawIdx, NormIdx: integer;
|
|
ByteCh: byte;
|
|
AddChar: boolean;
|
|
begin
|
|
RawIdx := 0;
|
|
NormIdx := 0;
|
|
|
|
while RawIdx < Count do
|
|
begin
|
|
AddChar := True;
|
|
ByteCh := TByteArray(RawBuf)[RawIdx];
|
|
|
|
if ByteCh = $0D then
|
|
begin
|
|
ByteCh := $0A;
|
|
LastChar0D := True;
|
|
end else
|
|
begin
|
|
if (ByteCh = $0A) and LastChar0D then
|
|
AddChar := False;
|
|
LastChar0D := False;
|
|
end;
|
|
|
|
if AddChar then
|
|
begin
|
|
TByteArray(NormBuf)[NormIdx] := ByteCh;
|
|
inc(NormIdx);
|
|
end;
|
|
|
|
inc(RawIdx);
|
|
end;
|
|
Result := NormIdx;
|
|
end;
|
|
|
|
function sdUtf8ToAnsi(const U: Utf8String; ACodePage: integer): AnsiString;
|
|
// Convert UTF8 to Ansi string
|
|
var
|
|
Utf8Count, AnsiCount: integer;
|
|
DefaultCharUsed: boolean;
|
|
begin
|
|
Utf8Count := length(U);
|
|
SetLength(Result, Utf8Count);
|
|
if Utf8Count = 0 then
|
|
exit;
|
|
|
|
AnsiCount := sdUtf8ToAnsiBuffer(U[1], Result[1], ACodePage, Utf8Count, DefaultCharUsed);
|
|
SetLength(Result, AnsiCount);
|
|
end;
|
|
|
|
function sdUtf8ToAnsiBuffer(const Utf8Buf; var AnsiBuf; ACodePage, Utf8Count: integer;
|
|
var DefaultCharUsed: boolean): integer;
|
|
var
|
|
AnsiIdx, Utf8Idx: integer;
|
|
Utf8Ch: AnsiChar;
|
|
WideCh: WideChar;
|
|
Len: integer;
|
|
DU: pointer;
|
|
const
|
|
cDefaultChar: AnsiChar = '?';
|
|
begin
|
|
AnsiIdx := 0;
|
|
Utf8Idx := 0;
|
|
while Utf8Idx < Utf8Count do
|
|
begin
|
|
Utf8Ch := TAnsiCharArray(Utf8Buf)[Utf8Idx];
|
|
if ord(Utf8Ch) < $80 then
|
|
begin
|
|
// characters < $80: just copy the single characters
|
|
DefaultCharUsed := False;
|
|
Len := 1;
|
|
TAnsiCharArray(AnsiBuf)[AnsiIdx] := Utf8Ch;
|
|
inc(AnsiIdx);
|
|
end else
|
|
begin
|
|
Len := sdUtf8CharacterLength(TAnsiCharArray(Utf8Buf)[Utf8Idx]);
|
|
sdUtf8ToWideBuffer(TAnsiCharArray(Utf8Buf)[Utf8Idx], WideCh, 1);
|
|
// characters >= $80: copy to widechar using codepage, then convert to Utf8
|
|
// WideCharToMultiByte is in the Windows unit of Borland Delphi 7
|
|
DefaultCharUsed := False;
|
|
DU := @DefaultCharUsed;
|
|
WideCharToMultiByte(ACodePage, 0, @WideCh, 1, @TAnsiCharArray(AnsiBuf)[AnsiIdx], 1, @cDefaultChar, @DU);
|
|
DefaultCharUsed := DU <> nil;
|
|
inc(AnsiIdx);
|
|
end;
|
|
inc(Utf8Idx, Len);
|
|
end;
|
|
Result := AnsiIdx;
|
|
end;
|
|
|
|
function sdEscapeString(const AValue: Utf8String): Utf8String;
|
|
// contributor: Michael Cessna
|
|
var
|
|
i, Len: Integer;
|
|
P: PAnsiChar;
|
|
HasEscapes: boolean;
|
|
ScratchMem: TsdFastMemStream;
|
|
begin
|
|
Result := '';
|
|
Len := Length(AValue);
|
|
if Len = 0 then
|
|
Exit;
|
|
|
|
HasEscapes := False;
|
|
P := PAnsiChar(AValue);
|
|
for i := 0 to Len - 1 do
|
|
begin
|
|
case P^ of
|
|
'"' : HasEscapes := True;
|
|
'''' : HasEscapes := True;
|
|
'&' : HasEscapes := True;
|
|
'<' : HasEscapes := True;
|
|
'>' : HasEscapes := True;
|
|
end;
|
|
Inc(P);
|
|
end;
|
|
if not HasEscapes then
|
|
begin
|
|
Result := AValue;
|
|
Exit;
|
|
end;
|
|
|
|
// ScratchMem is a TsdFastMemStream
|
|
ScratchMem := TsdFastMemStream.Create(Len * 2);
|
|
try
|
|
P := PAnsiChar(AValue);
|
|
for i := 0 to Len - 1 do
|
|
begin
|
|
case P^ of
|
|
'"' : ScratchMem.Write(AnsiString('"'), 6);
|
|
'''' : ScratchMem.Write(AnsiString('''), 6);
|
|
'&' : ScratchMem.Write(AnsiString('&'), 5);
|
|
'<' : ScratchMem.Write(AnsiString('<'), 4);
|
|
'>' : ScratchMem.Write(AnsiString('>'), 4);
|
|
else
|
|
ScratchMem.Write(P^, 1);
|
|
end;
|
|
Inc(P);
|
|
end;
|
|
SetString(Result, PAnsiChar(ScratchMem.Memory), ScratchMem.Position);
|
|
finally
|
|
ScratchMem.Free;
|
|
end;
|
|
end;
|
|
|
|
function sdReplaceString(const AValue: Utf8String; var HasNonStandardReferences: boolean;
|
|
References: array of TXmlNode): Utf8String; overload;
|
|
var
|
|
i, j, k, V, Code, Len: Integer;
|
|
W: Word;
|
|
P, Q: PAnsiChar;
|
|
HasReferences, FoundReference: boolean;
|
|
Reference, Replacement: Utf8String;
|
|
ScratchMem: TsdFastMemStream;
|
|
|
|
//local
|
|
function FindNonStandardReferenceReplacement(AReference: Utf8String): Utf8String;
|
|
var
|
|
i: integer;
|
|
Entity: TsdDtdEntity;
|
|
ReferenceName, ReferenceValue: Utf8String;
|
|
begin
|
|
Result := '';
|
|
if Length(References) = 0 then
|
|
exit;
|
|
for i := 0 to Length(References) - 1 do
|
|
begin
|
|
if References[i] is TsdDtdEntity then
|
|
begin
|
|
Entity := TsdDtdEntity(References[i]);
|
|
ReferenceName := '&' + Entity.Name + ';';
|
|
ReferenceValue := Entity.Value;
|
|
if AReference = ReferenceName then
|
|
begin
|
|
Result := ReferenceValue;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// main
|
|
begin
|
|
Result := '';
|
|
Len := Length(AValue);
|
|
if Len = 0 then
|
|
Exit;
|
|
|
|
HasReferences := False;
|
|
HasNonStandardReferences := False;
|
|
P := PAnsiChar(AValue);
|
|
for i := 0 to Len - 1 do
|
|
begin
|
|
if P^ = '&' then
|
|
HasReferences := True;
|
|
Inc(P);
|
|
end;
|
|
if not HasReferences then
|
|
begin
|
|
Result := AValue;
|
|
Exit;
|
|
end;
|
|
|
|
// ScratchMem is a TsdFastMemStream
|
|
ScratchMem := TsdFastMemStream.Create(Len);
|
|
try
|
|
P := PAnsiChar(AValue);
|
|
i := 0;
|
|
while i < Len do
|
|
begin
|
|
FoundReference := False;
|
|
if P^ = '&' then
|
|
begin
|
|
Q := P;
|
|
inc(Q);
|
|
for j := i + 1 to Len - 1 do
|
|
begin
|
|
if Q^ = '&' then
|
|
begin
|
|
// erronous duplicate quote! just let it be
|
|
FoundReference := False;
|
|
Break;
|
|
end;
|
|
|
|
if Q^ = ';' then
|
|
begin
|
|
// find reference
|
|
Reference := Copy(AValue, i + 1, j - i + 1);
|
|
inc(P, Length(Reference) - 1);
|
|
inc(i, Length(Reference) - 1);
|
|
|
|
// Look up standard reference escapes
|
|
for k := 0 to cEscapePhraseCount - 1 do
|
|
begin
|
|
if Reference = cXmlReplacePhrases[k] then
|
|
begin
|
|
// replacement
|
|
Replacement := cXmlEscapePhrases[k];
|
|
ScratchMem.Write(Replacement[1], Length(Replacement));
|
|
FoundReference := True;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
// Look up hex character reference
|
|
if not FoundReference then
|
|
begin
|
|
if copy(Reference, 1, 2) = '&#' then
|
|
begin
|
|
Reference := copy(Reference, 3, length(Reference) - 3);
|
|
if length(Reference) > 0 then
|
|
begin
|
|
if (Reference[1] = 'x') or (Reference[1] = 'X') then
|
|
// Hex notation
|
|
Reference[1] := '$';
|
|
Val(string(Reference), V, Code);
|
|
if (V >= 0) and (V <= $FFFF) and (Code = 0) then
|
|
begin
|
|
W := V;
|
|
Replacement := sdWideToUtf8(WideChar(W));
|
|
ScratchMem.Write(Replacement[1], Length(Replacement));
|
|
FoundReference := True;
|
|
Break;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
if not FoundReference then
|
|
begin
|
|
|
|
// there might be a non-standard reference, try to replace
|
|
Replacement := FindNonStandardReferenceReplacement(Reference);
|
|
if length(Replacement) = 0 then
|
|
begin
|
|
// replacement not found, so just write the reference
|
|
ScratchMem.Write(Reference[1], Length(Reference));
|
|
end else
|
|
begin
|
|
// write the replacement that was found :)
|
|
ScratchMem.Write(Replacement[1], Length(Replacement));
|
|
end;
|
|
FoundReference := True;
|
|
HasNonStandardReferences := True;
|
|
|
|
end;
|
|
Break;
|
|
end;
|
|
inc(Q);
|
|
end;
|
|
end;
|
|
if not FoundReference then
|
|
ScratchMem.Write(P^, 1);
|
|
Inc(P);
|
|
Inc(i);
|
|
end;
|
|
SetString(Result, PAnsiChar(ScratchMem.Memory), ScratchMem.Position);
|
|
finally
|
|
ScratchMem.Free;
|
|
end;
|
|
end;
|
|
|
|
function sdReplaceString(const AValue: Utf8String; var HasNonStandardReferences: boolean): Utf8String;
|
|
var
|
|
References: array of TXmlNode;
|
|
begin
|
|
SetLength(References, 0);
|
|
sdReplaceString(AValue, HasNonStandardReferences, References);
|
|
end;
|
|
|
|
function sdReplaceString(const AValue: Utf8String): Utf8String; overload;
|
|
var
|
|
HasNonStandardReferences: boolean;
|
|
References: array of TXmlNode;
|
|
begin
|
|
HasNonStandardReferences := False;
|
|
SetLength(References, 0);
|
|
Result := sdReplaceString(AValue, HasNonStandardReferences, References);
|
|
end;
|
|
|
|
function sdCommaToDot(const AValue: Utf8String): Utf8String;
|
|
var
|
|
i: integer;
|
|
begin
|
|
Result := AValue;
|
|
for i := 1 to Length(AValue) do
|
|
if AValue[i] = ',' then
|
|
Result[i] := '.';
|
|
end;
|
|
|
|
function sdTrim(const S: Utf8String): Utf8String;
|
|
var
|
|
I, L: Integer;
|
|
begin
|
|
L := Length(S);
|
|
i := 1;
|
|
while (i <= L) and (S[i] <= ' ') do
|
|
inc(i);
|
|
|
|
if i > L then
|
|
Result := ''
|
|
|
|
else
|
|
begin
|
|
while S[L] <= ' ' do
|
|
dec(L);
|
|
|
|
Result := Copy(S, i, L - i + 1);
|
|
end;
|
|
end;
|
|
|
|
function sdTrim(const S: Utf8String; var IsTrimmed: boolean): Utf8String;
|
|
begin
|
|
Result := sdTrim(S);
|
|
IsTrimmed := Length(Result) < Length(S);
|
|
end;
|
|
|
|
function sdTrim(const S: Utf8String; var PreString, PostString: Utf8String): Utf8String;
|
|
var
|
|
I, L: Integer;
|
|
begin
|
|
L := Length(S);
|
|
i := 1;
|
|
while (i <= L) and (S[i] <= ' ') do
|
|
inc(i);
|
|
|
|
if i > L then
|
|
begin
|
|
PreString := S;
|
|
Result := '';
|
|
PostString := '';
|
|
end
|
|
else
|
|
begin
|
|
while S[L] <= ' ' do
|
|
dec(L);
|
|
|
|
PreString := Copy(S, 1, i - 1);;
|
|
Result := Copy(S, i, L - i + 1);
|
|
PostString := Copy(S, L + 1, Length(S) - L);
|
|
end;
|
|
end;
|
|
|
|
function sdNormaliseEol(const S: Utf8String): Utf8String;
|
|
// compress all eol (CR-LF, LF or CR) to normalised eol (only LF aka #$0A)
|
|
var
|
|
i, j, L, IntervalCount: integer;
|
|
HasCR, HasLF: boolean;
|
|
begin
|
|
L := Length(S);
|
|
HasCR := False;
|
|
HasLF := False;
|
|
IntervalCount := 0;
|
|
i := 1;
|
|
while i <= L do
|
|
begin
|
|
if (S[i] = #$0D) then
|
|
HasCR := True;
|
|
if (S[i] = #$0A) then
|
|
begin
|
|
HasLF := True;
|
|
inc(IntervalCount);
|
|
end;
|
|
inc(i);
|
|
end;
|
|
|
|
// both CR and LF found?
|
|
if HasCR and HasLF then
|
|
begin
|
|
|
|
// we now know interval count, set the correct length
|
|
SetLength(Result, L - IntervalCount);
|
|
i := 1;
|
|
j := 1;
|
|
while i <= L do
|
|
begin
|
|
case S[i] of
|
|
#$0D: Result[j] := #$0A;
|
|
#$0A: dec(j);
|
|
else
|
|
Result[j] := S[i];
|
|
end;
|
|
inc(i);
|
|
inc(j);
|
|
end;
|
|
|
|
end else
|
|
begin
|
|
|
|
// either CR or LF found, but not both.
|
|
// We can assume Result := S with only minor changes
|
|
Result := S;
|
|
|
|
if HasCR then
|
|
begin
|
|
|
|
// HasCR found.. we will replace CR with LF
|
|
i := 1;
|
|
while i <= L do
|
|
begin
|
|
if S[i] = #$0D then
|
|
Result[i] := #$0A;
|
|
inc(i);
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function sdUnNormaliseEol(const S: Utf8String; const EolStyle: TsdEolStyle): Utf8String;
|
|
// expand all normalised eol (LF) to un-normalised eol (CR-LF for Win, CR for Mac)
|
|
var
|
|
i, j, L, IntervalCount: integer;
|
|
begin
|
|
// determine interval count
|
|
L := Length(S);
|
|
IntervalCount := 0;
|
|
i := 1;
|
|
while i <= L do
|
|
begin
|
|
if S[i] = #$0A then
|
|
inc(IntervalCount);
|
|
inc(i);
|
|
end;
|
|
|
|
// no intervals?
|
|
if IntervalCount = 0 then
|
|
begin
|
|
Result := S;
|
|
exit;
|
|
end;
|
|
|
|
// Mac style?
|
|
if EolStyle = esCR then
|
|
begin
|
|
Result := S;
|
|
i := 1;
|
|
while i <= L do
|
|
begin
|
|
if S[i] = #$0A then
|
|
Result[i] := #$0D;
|
|
inc(i);
|
|
end;
|
|
exit;
|
|
end;
|
|
|
|
// Windows style?
|
|
if EolStyle = esCRLF then
|
|
begin
|
|
// we now know interval count, set the correct length
|
|
SetLength(Result, L + IntervalCount);
|
|
i := 1;
|
|
j := 1;
|
|
while i <= L do
|
|
begin
|
|
case S[i] of
|
|
#$0A:
|
|
begin
|
|
Result[j] := #$0D;
|
|
inc(j);
|
|
Result[j] := #$0A;
|
|
end
|
|
else
|
|
Result[j] := S[i];
|
|
end;
|
|
inc(i);
|
|
inc(j);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function sdReadFromStream(S: TStream; CharCount: integer): Utf8String;
|
|
begin
|
|
SetLength(Result, CharCount);
|
|
if CharCount > 0 then
|
|
begin
|
|
S.Read(Result[1], CharCount);
|
|
end;
|
|
end;
|
|
|
|
procedure sdWriteToStream(S: TStream; const Value: Utf8String);
|
|
begin
|
|
if Length(Value) > 0 then
|
|
begin
|
|
S.Write(Value[1], Length(Value));
|
|
end;
|
|
end;
|
|
|
|
function sdReadCardinal(S: TStream): cardinal;
|
|
var
|
|
C: byte;
|
|
Bits: integer;
|
|
begin
|
|
Result := 0;
|
|
Bits := 0;
|
|
repeat
|
|
S.Read(C, 1);
|
|
if C > 0 then
|
|
begin
|
|
inc(Result, (C and $7F) shl Bits);
|
|
inc(Bits, 7);
|
|
if Bits > 32 then
|
|
raise Exception.Create('invalid cardinal reader');
|
|
end;
|
|
until(C and $80) = 0;
|
|
end;
|
|
|
|
procedure sdWriteCardinal(S: TStream; ACardinal: cardinal);
|
|
var
|
|
C: byte;
|
|
begin
|
|
repeat
|
|
if ACardinal <= $7F then
|
|
begin
|
|
C := ACardinal;
|
|
S.Write(C, 1);
|
|
exit;
|
|
end else
|
|
C := (ACardinal and $7F) or $80;
|
|
|
|
S.Write(C, 1);
|
|
ACardinal := ACardinal shr 7;
|
|
until ACardinal = 0;
|
|
end;
|
|
|
|
function sdCharsetToCodePage(ACharset: Utf8String; ADefaultCodepage: integer = 65001): integer;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to cCodePageInfoCount - 1 do
|
|
begin
|
|
if AnsiCompareText(ACharset, cCodePageInfo[i].Name) = 0 then
|
|
begin
|
|
Result := cCodePageInfo[i].Codepage;
|
|
exit;
|
|
end;
|
|
end;
|
|
// Default
|
|
Result := ADefaultCodepage;
|
|
end;
|
|
|
|
function sdCharsetToStringEncoding(ACharset: Utf8String): TsdStringEncoding;
|
|
var
|
|
Codepage: integer;
|
|
begin
|
|
Codepage := sdCharsetToCodePage(ACharset);
|
|
case Codepage of
|
|
1200: Result := seUTF16LE;
|
|
1201: Result := seUTF16BE;
|
|
65001: Result := seUTF8;
|
|
else
|
|
Result := seAnsi;
|
|
end;
|
|
end;
|
|
|
|
function sdCodepageToCharset(ACodepage: integer): Utf8String;
|
|
// find the charset corresponding to windows codepage
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to cCodePageInfoCount - 1 do
|
|
begin
|
|
if cCodepageInfo[i].Codepage = ACodepage then
|
|
begin
|
|
Result := cCodepageInfo[i].Name;
|
|
exit;
|
|
end;
|
|
end;
|
|
// default to 'utf-8'
|
|
Result := 'utf-8';
|
|
end;
|
|
|
|
function Utf8CompareText(const S1, S2: Utf8String): integer;
|
|
begin
|
|
// AnsiCompareText is case-insensitive
|
|
Result := AnsiCompareText(AnsiString(S1), AnsiString(S2));
|
|
end;
|
|
|
|
function GetTimeZoneBias: Integer;
|
|
{$ifdef MSWINDOWS}
|
|
// uses windows unit, func GetTimeZoneInformation
|
|
// contributor: Stefan Glienke
|
|
var
|
|
TimeZoneInfo: TTimeZoneInformation;
|
|
begin
|
|
case GetTimeZoneInformation(TimeZoneInfo) of
|
|
TIME_ZONE_ID_UNKNOWN: Result := TimeZoneInfo.Bias;
|
|
TIME_ZONE_ID_STANDARD: Result := TimeZoneInfo.Bias + TimeZoneInfo.StandardBias;
|
|
TIME_ZONE_ID_DAYLIGHT: Result := TimeZoneInfo.Bias + TimeZoneInfo.DaylightBias;
|
|
else
|
|
Result := 0;
|
|
end;
|
|
end;
|
|
{$else MSWINDOWS}
|
|
begin
|
|
// NH: I dont know the linux equivalent..
|
|
Result := 0;
|
|
end;
|
|
{$endif MSWINDOWS}
|
|
|
|
{ XYZ to string functions }
|
|
|
|
function sdDateTimeToString(ADate: TDateTime; UseDate: boolean = True; UseTime: boolean = True;
|
|
SplitSecondDigits: integer = 0; UseLocalBias: boolean = False): Utf8String;
|
|
// Convert the TDateTime ADate to a string according to the W3C date/time specification
|
|
// as found here: http://www.w3.org/TR/NOTE-datetime
|
|
var
|
|
AYear, AMonth, ADay, AHour, AMin, ASec, AMSec: word;
|
|
ABias: Integer;
|
|
DatePortion, TimePortion, SplitSecondPortion, LocalBiasPortion: Utf8String;
|
|
const
|
|
Neg: array[Boolean] of string = ('+', '-');
|
|
begin
|
|
DatePortion := '';
|
|
TimePortion := '';
|
|
|
|
if UseDate then
|
|
begin
|
|
DecodeDate(ADate, AYear, AMonth, ADay);
|
|
DatePortion := Utf8String(Format('%.4d-%.2d-%.2d', [AYear, AMonth, ADay]));
|
|
// if we also use time, add the 'T' in advance
|
|
if UseTime then
|
|
DatePortion := DatePortion + 'T';
|
|
end;
|
|
if UseTime then
|
|
begin
|
|
DecodeTime(ADate, AHour, AMin, ASec, AMSec);
|
|
if SplitSecondDigits > 0 then
|
|
begin
|
|
SplitSecondPortion := Utf8String(Format('%.3d', [AMSec]));
|
|
if SplitSecondDigits < 3 then
|
|
begin
|
|
SplitSecondPortion := copy(SplitSecondPortion, 1, SplitSecondDigits);
|
|
end;
|
|
SplitSecondPortion := '.' + SplitSecondPortion;
|
|
end else
|
|
begin
|
|
SplitSecondPortion := '';
|
|
end;
|
|
if UseLocalBias then
|
|
begin
|
|
ABias := GetTimeZoneBias;
|
|
LocalBiasPortion := Utf8String(Format('%s%.2d:%.2d',
|
|
[Neg[ABias > 0], Abs(ABias) div MinsPerHour, Abs(ABias) mod MinsPerHour]))
|
|
end else
|
|
begin
|
|
LocalBiasPortion := 'Z';
|
|
end;
|
|
// final time portion
|
|
TimePortion := Utf8String(Format('%.2d:%.2d:%.2d', [AHour, AMin, ASec]))
|
|
+ SplitSecondPortion + LocalBiasPortion;
|
|
end;
|
|
// final result
|
|
Result := DatePortion + TimePortion;
|
|
end;
|
|
|
|
function sdBoolToString(Value: boolean): Utf8String;
|
|
const
|
|
// do NOT localize! This is part of the W3 XML spec
|
|
cBoolValues: array[boolean] of Utf8String = ('false', 'true');
|
|
begin
|
|
Result := cBoolValues[Value];
|
|
end;
|
|
|
|
function sdBoolFromString(Value: Utf8String): boolean;
|
|
begin
|
|
Result := StrToBool(Value);
|
|
end;
|
|
|
|
function sdFloatToString(Value: double; SignificantDigits: integer; AllowScientific: boolean): Utf8String; overload;
|
|
const
|
|
Limits: array[1..9] of integer =
|
|
(10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000);
|
|
var
|
|
Limit, Limitd, PointPos, IntVal, ScPower: integer;
|
|
Body: Utf8String;
|
|
begin
|
|
if (SignificantDigits < 1) or (SignificantDigits > 9) then
|
|
raise Exception.Create(sSignificantDigitsOutOfRange);
|
|
|
|
// Zero
|
|
if Value = 0 then
|
|
begin
|
|
Result := '0';
|
|
exit;
|
|
end;
|
|
|
|
// Sign
|
|
if Value < 0 then
|
|
begin
|
|
Result := '-';
|
|
Value := -Value;
|
|
end else
|
|
Result := '';
|
|
|
|
// Determine point position
|
|
Limit := Limits[SignificantDigits];
|
|
Limitd := Limit div 10;
|
|
PointPos := SignificantDigits;
|
|
while Value < Limitd do
|
|
begin
|
|
Value := Value * 10;
|
|
dec(PointPos);
|
|
end;
|
|
while Value >= Limit do
|
|
begin
|
|
Value := Value * 0.1;
|
|
inc(PointPos);
|
|
end;
|
|
|
|
// Round
|
|
IntVal := round(Value);
|
|
|
|
// Exceptional case which happens when the value rounds up to the limit
|
|
if Intval = Limit then
|
|
begin
|
|
IntVal := IntVal div 10;
|
|
inc(PointPos);
|
|
end;
|
|
|
|
// Strip off any zeros, these reduce significance count
|
|
while (IntVal mod 10 = 0) and (PointPos < SignificantDigits) do
|
|
begin
|
|
dec(SignificantDigits);
|
|
IntVal := IntVal div 10;
|
|
end;
|
|
|
|
// Check for scientific notation
|
|
ScPower := 0;
|
|
if AllowScientific and ((PointPos < -1) or (PointPos > SignificantDigits + 2)) then
|
|
begin
|
|
ScPower := PointPos - 1;
|
|
dec(PointPos, ScPower);
|
|
end;
|
|
|
|
// Body
|
|
Body := IntToStr(IntVal);
|
|
while PointPos > SignificantDigits do
|
|
begin
|
|
Body := Body + '0';
|
|
inc(SignificantDigits);
|
|
end;
|
|
while PointPos < 0 do
|
|
begin
|
|
Body := '0' + Body;
|
|
inc(PointPos);
|
|
end;
|
|
if PointPos = 0 then
|
|
Body := '.' + Body
|
|
else
|
|
if PointPos < SignificantDigits then
|
|
Body := copy(Body, 1, PointPos) + '.' + copy(Body, PointPos + 1, SignificantDigits);
|
|
|
|
// Final result
|
|
if ScPower = 0 then
|
|
Result := Result + Body
|
|
else
|
|
Result := Result + Body + 'E' + IntToStr(ScPower);
|
|
end;
|
|
|
|
function sdFloatToString(Value: double): Utf8String; overload;
|
|
begin
|
|
Result := sdFloatToString(Value, cDefaultFloatSignificantDigits, cDefaultFloatAllowScientific);
|
|
end;
|
|
|
|
function sdFloatFromString(Value: Utf8String): double;
|
|
begin
|
|
{$ifdef D7UP}
|
|
Result := StrToFloat(Value, cXmlFormatSettings);
|
|
{$else D7UP}
|
|
// D5 version
|
|
Result := StrToFloat(Value);
|
|
{$endif D7UP}
|
|
end;
|
|
|
|
function sdIntToString(Value: integer): Utf8String;
|
|
begin
|
|
Result := Utf8String(IntToStr(Value));
|
|
end;
|
|
|
|
function sdIntFromString(Value: Utf8String): integer;
|
|
begin
|
|
Result := StrToInt(Value);
|
|
end;
|
|
|
|
function sdInt64ToString(Value: int64): Utf8String;
|
|
begin
|
|
// int64 can be used with IntToStr
|
|
Result := Utf8String(IntToStr(Value));
|
|
end;
|
|
|
|
function sdInt64FromString(Value: Utf8String): int64;
|
|
begin
|
|
Result := StrToInt64(Value);
|
|
end;
|
|
|
|
{ end XYZ to string functions }
|
|
|
|
{ string to XYZ functions }
|
|
|
|
function sdStringToDateTime(const ADate: Utf8String; UseLocalBias: boolean): TDateTime;
|
|
// Convert the string ADate to a TDateTime according to the W3C date/time specification
|
|
// as found here: http://www.w3.org/TR/NOTE-datetime
|
|
// contributor: Stefan Glienke
|
|
var
|
|
AYear, AMonth, ADay, AHour, AMin, ASec, AMSec: word;
|
|
ALocalBias, ABias: Integer;
|
|
begin
|
|
AYear := StrToInt(copy(ADate, 1, 4));
|
|
AMonth := StrToInt(copy(ADate, 6, 2));
|
|
ADay := StrToInt(copy(ADate, 9, 2));
|
|
if Length(ADate) > 16 then
|
|
begin
|
|
AHour := StrToInt(copy(ADate, 12, 2));
|
|
AMin := StrToInt(copy(ADate, 15, 2));
|
|
ASec := StrToIntDef(copy(ADate, 18, 2), 0); // They might be omitted, so default to 0
|
|
AMSec := StrToIntDef(copy(ADate, 21, 3), 0); // They might be omitted, so default to 0
|
|
end else
|
|
begin
|
|
AHour := 0;
|
|
AMin := 0;
|
|
ASec := 0;
|
|
AMSec := 0;
|
|
end;
|
|
Result :=
|
|
EncodeDate(AYear, AMonth, ADay) +
|
|
EncodeTime(AHour, AMin, ASec, AMSec);
|
|
if UseLocalBias then
|
|
begin
|
|
ALocalBias := GetTimeZoneBias;
|
|
if (Length(ADate) > 24) then
|
|
begin
|
|
ABias := StrToInt(Copy(ADate, 25, 2)) * MinsPerHour +
|
|
StrToInt(Copy(ADate, 28, 2));
|
|
if ADate[24] = '+' then
|
|
ABias := ABias * -1;
|
|
Result := Result + ABias / MinsPerDay;
|
|
end;
|
|
Result := Result - ALocalBias / MinsPerDay;
|
|
end;
|
|
end;
|
|
|
|
function sdStringToDateTimeDef(const ADate: Utf8String; ADefault: TDateTime; UseLocalBias: boolean): TDateTime;
|
|
// Convert the string ADate to a TDateTime according to the W3C date/time specification
|
|
// as found here: http://www.w3.org/TR/NOTE-datetime
|
|
// If there is a conversion error, the default value ADefault is returned.
|
|
begin
|
|
try
|
|
Result := sdStringToDateTime(ADate, UseLocalBias);
|
|
except
|
|
Result := ADefault;
|
|
end;
|
|
end;
|
|
|
|
function EncodeBase64(const Source: RawByteString): Utf8String;
|
|
// Encode binary data in Source as BASE64. The function returns the BASE64 encoded
|
|
// data as string, without any linebreaks.
|
|
begin
|
|
if length(Source) > 0 then
|
|
Result := EncodeBase64Buf(Source[1], length(Source))
|
|
else
|
|
Result := '';
|
|
end;
|
|
|
|
function EncodeBase64Buf(const Buffer; Count: Integer): Utf8String;
|
|
var
|
|
i, j: integer;
|
|
Core: integer;
|
|
FourChar: cardinal;
|
|
S: PByte;
|
|
begin
|
|
// Make sure "Core" is always a multiple of 3, and this multiple
|
|
// gets saved as 4 characters
|
|
Core := (Count + 2) div 3;
|
|
|
|
// Set the length of the string that stores encoded characters
|
|
SetLength(Result, Core * 4);
|
|
S := @Buffer;
|
|
|
|
// Do the loop "Core" times
|
|
for i := 0 to Core - 1 do
|
|
begin
|
|
FourChar := 0;
|
|
for j := 0 to 2 do
|
|
begin
|
|
FourChar := FourChar shl 8 + S^;
|
|
inc(S);
|
|
end;
|
|
for j := 0 to 3 do
|
|
begin
|
|
Result[i * 4 + 4 - j] := cBase64Char[FourChar and $3F];
|
|
FourChar := FourChar shr 6;
|
|
end;
|
|
end;
|
|
|
|
// For comformity to Base64, we must pad the data instead of zero out
|
|
// when the size is not an exact multiple of 3
|
|
case Core * 3 - Count of
|
|
0:;// nothing to do
|
|
1: // pad one char
|
|
Result[Core * 4] := cBase64PadChar;
|
|
2: // pad two chars
|
|
begin
|
|
Result[Core * 4 - 1] := cBase64PadChar;
|
|
Result[Core * 4 ] := cBase64PadChar;
|
|
end;
|
|
end;//case
|
|
end;
|
|
|
|
function DecodeBase64(const Source: Utf8String): RawByteString;
|
|
// Decode BASE64 data in Source into binary data. The function returns the binary
|
|
// data as Utf8String.
|
|
var
|
|
BufData: Utf8String;
|
|
BufSize, BufPos: integer;
|
|
begin
|
|
BufData := sdRemoveControlChars(Source);
|
|
|
|
// Determine length of data
|
|
BufSize := length(BufData) div 4;
|
|
if BufSize * 4 <> length(BufData) then
|
|
raise EFilerError.Create(sErrorCalcStreamLength);
|
|
BufSize := BufSize * 3;
|
|
|
|
// Check padding chars
|
|
BufPos := length(BufData);
|
|
if (BufPos > 0) and (BufData[BufPos] = cBase64PadChar) then
|
|
begin
|
|
dec(BufPos);
|
|
dec(BufSize);
|
|
if (BufPos > 0) and (BufData[BufPos] = cBase64PadChar) then
|
|
dec(BufSize);
|
|
end;
|
|
Setlength(Result, BufSize);
|
|
|
|
// Decode
|
|
if BufSize > 0 then
|
|
DecodeBase64Buf(BufData, Result[1], BufSize);
|
|
end;
|
|
|
|
procedure DecodeBase64Buf(var Source: Utf8String; var Buffer; Count: Integer);
|
|
var
|
|
i, j: integer;
|
|
BufPos, Core: integer;
|
|
FourChar: cardinal;
|
|
D: PByte;
|
|
Map: array[AnsiChar] of byte;
|
|
begin
|
|
// Core * 4 is the number of chars to read - check length
|
|
Core := Length(Source) div 4;
|
|
if Count > Core * 3 then
|
|
raise EFilerError.Create(sMissingDataInBinaryStream);
|
|
|
|
// Prepare map
|
|
for i := 0 to 63 do
|
|
Map[cBase64Char[i]] := i;
|
|
D := @Buffer;
|
|
|
|
// Check for final padding, and replace with "zeros". There can be
|
|
// at max two pad chars ('=')
|
|
BufPos := length(Source);
|
|
if (BufPos > 0) and (Source[BufPos] = cBase64PadChar) then
|
|
begin
|
|
Source[BufPos] := cBase64Char[0];
|
|
dec(BufPos);
|
|
if (BufPos > 0) and (Source[BufPos] = cBase64PadChar) then
|
|
Source[BufPos] := cBase64Char[0];
|
|
end;
|
|
|
|
// Do this "Core" times
|
|
for i := 0 to Core - 1 do
|
|
begin
|
|
FourChar := 0;
|
|
|
|
// Unroll the characters
|
|
for j := 0 to 3 do
|
|
FourChar := FourChar shl 6 + Map[Source[i * 4 + j + 1]];
|
|
|
|
// and unroll the bytes
|
|
for j := 2 downto 0 do
|
|
begin
|
|
// Check overshoot
|
|
if integer(D) - integer(@Buffer) >= Count then
|
|
exit;
|
|
D^ := FourChar shr (j * 8) and $FF;
|
|
inc(D);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function EncodeBinHex(const Source: RawByteString): Utf8String;
|
|
// Encode binary data in Source as BINHEX. The function returns the BINHEX encoded
|
|
// data as UTF8String, without any linebreaks.
|
|
var
|
|
Text: Utf8String;
|
|
begin
|
|
SetLength(Text, Length(Source) * 2);
|
|
BinToHex(PAnsiChar(Source), PAnsiChar(Text), Length(Source));
|
|
Result := Text;
|
|
end;
|
|
|
|
function DecodeBinHex(const Source: Utf8String): RawByteString;
|
|
// Decode BINHEX data in Source into binary data. The function returns the binary
|
|
// data as RawByteString. Use a TStringStream to convert this data to a stream.
|
|
var
|
|
Data: Utf8String;
|
|
Size: integer;
|
|
Buffer: RawByteString;
|
|
begin
|
|
Data := sdRemoveControlChars(Source);
|
|
|
|
// Determine length of data
|
|
Size := length(Data) div 2;
|
|
if Size * 2 <> length(Data) then
|
|
raise EFilerError.Create(sErrorCalcStreamLength);
|
|
|
|
SetLength(Buffer, Size);
|
|
HexToBin(PAnsiChar(Data), PAnsiChar(Buffer), Size);
|
|
Result := Buffer;
|
|
end;
|
|
|
|
procedure DecodeBinhexBuf(var Source: Utf8String; var Buffer; Count: Integer);
|
|
var
|
|
Size: integer;
|
|
begin
|
|
// Determine length of data
|
|
Size := Count div 2;
|
|
if Size * 2 <> Count then
|
|
raise EFilerError.Create(sErrorCalcStreamLength);
|
|
|
|
HexToBin(PAnsiChar(Source), PAnsiChar(Buffer), Count);
|
|
end;
|
|
|
|
function sdRemoveControlChars(const AValue: Utf8String): Utf8String;
|
|
// Remove control characters from Utf8String AValue
|
|
var
|
|
i, j: integer;
|
|
begin
|
|
Setlength(Result, Length(AValue));
|
|
i := 1;
|
|
j := 1;
|
|
while i <= Length(AValue) do
|
|
if AValue[i] in cXmlBlankChars then
|
|
inc(i)
|
|
else
|
|
begin
|
|
Result[j] := AValue[i];
|
|
inc(i);
|
|
inc(j);
|
|
end;
|
|
// Adjust length
|
|
if i <> j then
|
|
SetLength(Result, j - 1);
|
|
end;
|
|
|
|
function sdAddControlChars(const AValue: Utf8String; const ControlChars: Utf8String; Interval: integer): Utf8String;
|
|
// Insert Chars in AValue at each Interval
|
|
var
|
|
i, j, L: integer;
|
|
// local
|
|
procedure InsertControlChars;
|
|
var
|
|
k: integer;
|
|
begin
|
|
for k := 1 to Length(ControlChars) do
|
|
begin
|
|
Result[j] := ControlChars[k];
|
|
inc(j);
|
|
end;
|
|
end;
|
|
// main
|
|
begin
|
|
if (Length(ControlChars) = 0) or (Interval <= 0) then
|
|
begin
|
|
Result := AValue;
|
|
exit;
|
|
end;
|
|
|
|
// Calculate length based on original length and total extra length for control chars
|
|
L := Length(AValue) + ((Length(AValue) - 1) div Interval + 3) * Length(ControlChars);
|
|
SetLength(Result, L);
|
|
|
|
// Copy and insert
|
|
j := 1;
|
|
for i := 1 to Length(AValue) do
|
|
begin
|
|
if (i mod Interval) = 1 then
|
|
// Insert control chars
|
|
InsertControlChars;
|
|
Result[j] := AValue[i];
|
|
inc(j);
|
|
end;
|
|
InsertControlChars;
|
|
|
|
// Adjust length
|
|
dec(j);
|
|
if L > j then
|
|
SetLength(Result, j);
|
|
end;
|
|
|
|
{ former unit sdStringEncodig }
|
|
|
|
function sdUtf8CharacterLength(const Buffer): integer;
|
|
// determine the character length (1..4 bytes) of the Utf8 character
|
|
// in the buffer
|
|
type
|
|
TByteArray = array[0..3] of byte;
|
|
var
|
|
P0, P1, P2, P3: byte;
|
|
begin
|
|
P0 := TByteArray(Buffer)[0];
|
|
Result := 1;
|
|
if P0 < $C0 then // %11000000
|
|
begin
|
|
// regular single byte character
|
|
exit;
|
|
end;
|
|
P1 := TByteArray(Buffer)[1];
|
|
if (P0 and $E0) = $C0 then
|
|
begin
|
|
// could be 2 byte character
|
|
if (P1 and $C0) = $80 then
|
|
begin
|
|
Result := 2;
|
|
end;
|
|
exit;
|
|
end;
|
|
P2 := TByteArray(Buffer)[2];
|
|
if (P0 and $F0) = $E0 then
|
|
begin
|
|
// could be 3 byte character
|
|
if ((P1 and $C0) = $80) and ((P2 and $C0) = $80) then
|
|
begin
|
|
Result := 3;
|
|
end;
|
|
exit;
|
|
end;
|
|
P3 := TByteArray(Buffer)[3];
|
|
if (P0 and $F8) = $F0 then
|
|
begin
|
|
// could be 4 byte character
|
|
// NB 4 byte chars are incompatible with Widechar since
|
|
// they are outside the basic lingual plane
|
|
if ((P1 and $C0) = $80)
|
|
and ((P2 and $C0) = $80)
|
|
and ((P3 and $C0) = $80) then
|
|
begin
|
|
Result := 4;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
{ TsdBinaryXml }
|
|
|
|
constructor TsdBinaryXml.Create(AOwner: TComponent);
|
|
begin
|
|
inherited;
|
|
|
|
end;
|
|
|
|
destructor TsdBinaryXml.Destroy;
|
|
begin
|
|
SetLength(FNewIDs, 0);
|
|
inherited;
|
|
end;
|
|
|
|
function TsdBinaryXml.IncrementFrequency(AID: Cardinal): Cardinal;
|
|
begin
|
|
Result := AID;
|
|
if AID > 0 then
|
|
FDocument.FSymbolTable.IncrementFrequency(AID);
|
|
end;
|
|
|
|
procedure TsdBinaryXml.LoadFromFile(const AFileName: string);
|
|
var
|
|
S: TMemoryStream;
|
|
begin
|
|
S := TMemoryStream.Create;
|
|
try
|
|
S.LoadFromFile(AFileName);
|
|
LoadFromStream(S);
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TsdBinaryXml.LoadFromStream(S: TStream);
|
|
var
|
|
Cookie: Utf8String;
|
|
Version: Cardinal;
|
|
CoderName: Utf8String;
|
|
SIn, SOut: TStream;
|
|
PayloadPos: int64;
|
|
PlainSize: int64;
|
|
LineFeed: byte;
|
|
begin
|
|
Cookie := sdReadFromStream(S, length(cBinaryXmlCookie));
|
|
if Cookie <> cBinaryXmlCookie then
|
|
begin
|
|
DoDebugOut(Self, wsFail, 'invalid binary xml stream');
|
|
exit;
|
|
end;
|
|
|
|
// read binary xml version number
|
|
Version := ReadCardinal(S);
|
|
if cBinaryXmlVersion < Version then
|
|
begin
|
|
// future version: failure
|
|
DoDebugOut(Self, wsFail, 'incompatible binary version');
|
|
exit;
|
|
end else
|
|
begin
|
|
if cBinaryXmlVersion > Version then
|
|
begin
|
|
// former version: issue a warning
|
|
DoDebugOut(Self, wsWarn, Format('loading binary version=%d, current version=%d',
|
|
[Version, cBinaryXmlVersion]));
|
|
end;
|
|
end;
|
|
|
|
// external compression/encryption options
|
|
CoderName := sdReadFromStream(S, 4); // 4-character coder name
|
|
|
|
// to allow visibility of codec at topmost line
|
|
S.Read(LineFeed, 1);
|
|
|
|
PayloadPos := S.Position;
|
|
|
|
if CoderName <> 'none' then
|
|
begin
|
|
SIn := TMemoryStream.Create;
|
|
SOut := TMemoryStream.Create;
|
|
try
|
|
if assigned(OnDecode) then
|
|
begin
|
|
|
|
// plain size was stored beforehand
|
|
PlainSize := sdReadCardinal(S);
|
|
|
|
SIn.CopyFrom(S, S.Size - S.Position);
|
|
SIn.Position := 0;
|
|
CoderName := OnDecode(SIn, SOut, PlainSize);
|
|
|
|
DoDebugOut(Self, wsInfo, format('coder name: %s, compressed size: %d, plain size: %d',
|
|
[CoderName, SIn.Size, SOut.Size]));
|
|
|
|
SOut.Position := 0;
|
|
S.Position := PayloadPos;
|
|
S.CopyFrom(SOut, SOut.Size);
|
|
S.Position := PayloadPos;
|
|
end else
|
|
begin
|
|
DoDebugOut(Self, wsFail, 'OnDecode event unassigned');
|
|
exit;
|
|
end;
|
|
|
|
finally
|
|
SIn.Free;
|
|
SOut.Free;
|
|
end;
|
|
end;
|
|
|
|
// entirely clear xml document
|
|
FDocument.ClearData(False, False, False);
|
|
|
|
|
|
// read (partial) symbol table
|
|
FDocument.SymbolTable.LoadFromStream(S);
|
|
FDocument.DoProgress(S.Position);
|
|
|
|
// read document
|
|
ReadDocument(S);
|
|
|
|
end;
|
|
|
|
function TsdBinaryXml.ReadCardinal(S: TStream): cardinal;
|
|
var
|
|
C: byte;
|
|
Bits: integer;
|
|
begin
|
|
Result := 0;
|
|
Bits := 0;
|
|
repeat
|
|
S.Read(C, 1);
|
|
if C > 0 then
|
|
begin
|
|
inc(Result, (C and $7F) shl Bits);
|
|
inc(Bits, 7);
|
|
if Bits > 32 then
|
|
begin
|
|
DoDebugOut(Self, wsFail, 'invalid cardinal reader');
|
|
Result := 0;
|
|
exit;
|
|
end;
|
|
end;
|
|
until(C and $80) = 0;
|
|
end;
|
|
|
|
procedure TsdBinaryXml.ReadDocument(S: TStream);
|
|
var
|
|
SubCounts: array of integer;
|
|
Parents: array of TXmlNode;
|
|
Node, Parent: TXmlNode;
|
|
NodeCount, SubCount, Level: integer;
|
|
begin
|
|
|
|
Level := 0;
|
|
NodeCount := 0;
|
|
Parent := nil;
|
|
|
|
repeat
|
|
|
|
// Read a new node from the stream, the subcount
|
|
// is initialized/updated in the ReadNode function
|
|
Node := ReadNode(S, Parent, SubCount);
|
|
|
|
if assigned(Node) then
|
|
begin
|
|
//DoDebugOut(Self, wsInfo, format('level=%d nodename=%s nodetype=%s',
|
|
// [Level, Node.Name, cElementTypeNames[Node.ElementType]]));
|
|
|
|
// increment count of all nodes
|
|
inc(NodeCount);
|
|
|
|
// if level is 0 then add the node to NativeXml root node list
|
|
if (Level = 0) then
|
|
begin
|
|
Document.FRootNodes.Add(Node);
|
|
end;
|
|
end;
|
|
|
|
// more subnodes?
|
|
if SubCount > 0 then
|
|
begin
|
|
Parent := Node;
|
|
inc(Level);
|
|
// check dimensioning of subcounts and parents arrays
|
|
if Level >= length(SubCounts) then
|
|
begin
|
|
SetLength(SubCounts, Level + 1);
|
|
SetLength(Parents, Level + 1);
|
|
end;
|
|
Parents[Level] := Node;
|
|
SubCounts[Level] := SubCount;
|
|
end else
|
|
begin
|
|
// subcount = 0, no sub nodes.. determine the level and parent for next node
|
|
|
|
if Level > 0 then
|
|
begin
|
|
dec(SubCounts[Level]);
|
|
while (SubCounts[Level] = 0) and (Level > 0) do
|
|
begin
|
|
dec(Level);
|
|
if Level > 0 then
|
|
dec(SubCounts[Level]);
|
|
end;
|
|
if Level = 0 then
|
|
Parent := nil
|
|
else
|
|
Parent := Parents[Level];
|
|
end;
|
|
|
|
end;
|
|
|
|
// progress each 100 nodes
|
|
if NodeCount mod 100 = 0 then
|
|
begin
|
|
Document.DoProgress(S.Position);
|
|
end;
|
|
|
|
until not assigned(Node);
|
|
|
|
// debug
|
|
DoDebugOut(Self, wsInfo, format('total node count: %d', [NodeCount]));
|
|
end;
|
|
|
|
function TsdBinaryXml.ReadNode(S: TStream; AParent: TXmlNode; var SubCount: integer): TXmlNode;
|
|
var
|
|
NodeType: TsdElementType;
|
|
Table: TsdSymbolTable;
|
|
C: Cardinal;
|
|
|
|
// local
|
|
function ReadSymbol(S: TStream): Cardinal;
|
|
var
|
|
C: Cardinal;
|
|
begin
|
|
C := ReadCardinal(S);
|
|
if C > 0 then
|
|
Result := C - 1 // SymbolID = C - 1
|
|
else
|
|
Result := Table.LoadSymbol(S);
|
|
end;
|
|
|
|
// local
|
|
function NodeCreate(ANodeClass: TsdNodeClass): TXmlNode;
|
|
var
|
|
NodeList: TsdNodeList;
|
|
begin
|
|
Result := ANodeClass.Create(FDocument);
|
|
Result.FParent := AParent;
|
|
if AParent is TsdContainerNode then
|
|
begin
|
|
NodeList := TsdContainerNode(AParent).NodeList;
|
|
NodeList.Add(Result);
|
|
end;
|
|
end;
|
|
|
|
// local
|
|
procedure ReadContainerNode(ANode: TXmlNode);
|
|
begin
|
|
// read persistent properties of containernode
|
|
SubCount := ReadCardinal(S);
|
|
TsdContainerNode(ANode).FDirectNodeCount := ReadCardinal(S);
|
|
TsdContainerNode(ANode).FValueIndex := integer(ReadCardinal(S)) - 1;
|
|
end;
|
|
|
|
// local
|
|
procedure ReadElement(ANode: TXmlNode);
|
|
begin
|
|
// read persistent properties of containernode
|
|
ReadContainerNode(ANode);
|
|
// read persistent properties of element
|
|
TsdElement(ANode).FNameID := ReadSymbol(S);
|
|
TsdElement(ANode).FNodeClosingStyle := TsdNodeClosingStyle(ReadCardinal(S));
|
|
end;
|
|
|
|
// local
|
|
procedure ReadCharData(ANode: TXmlNode);
|
|
begin
|
|
TsdCharData(ANode).FValueID := ReadSymbol(S);
|
|
end;
|
|
|
|
// local
|
|
procedure ReadQuotedText(ANode: TXmlNode);
|
|
begin
|
|
TsdCharData(ANode).FValueID := ReadSymbol(S);
|
|
TsdQuotedText(ANode).FQuoteStyle := TsdQuoteCharStyle(ReadCardinal(S));
|
|
end;
|
|
|
|
// main
|
|
begin
|
|
Result := nil;
|
|
SubCount := 0;
|
|
Table := FDocument.SymbolTable;
|
|
|
|
C := ReadCardinal(S);
|
|
// more than highest element type?
|
|
if integer(C) > ord(xeEndTag) then
|
|
begin
|
|
DoDebugOut(Self, wsFail, Format('unknown element type %d', [C]));
|
|
exit;
|
|
end;
|
|
|
|
NodeType := TsdElementType(C);
|
|
|
|
case NodeType of
|
|
|
|
xeElement, xeDtdElement, xeDtdEntity, xeDtdNotation, xeDtdAttList:
|
|
begin
|
|
if NodeType = xeElement then
|
|
Result := NodeCreate(TsdElement);
|
|
if NodeType = xeDtdElement then
|
|
Result := NodeCreate(TsdDtdElement);
|
|
if NodeType = xeDtdEntity then
|
|
Result := NodeCreate(TsdDtdEntity);
|
|
if NodeType = xeDtdNotation then
|
|
Result := NodeCreate(TsdDtdNotation);
|
|
if NodeType = xeDtdAttlist then
|
|
Result := NodeCreate(TsdDtdAttList);
|
|
ReadElement(Result);
|
|
end;
|
|
|
|
xeAttribute:
|
|
begin
|
|
Result := NodeCreate(TsdAttribute);
|
|
TsdAttribute(Result).FNameID := ReadSymbol(S);
|
|
ReadQuotedText(TsdAttribute(Result).FCoreValue);
|
|
end;
|
|
|
|
xeDeclaration, xeStylesheet:
|
|
begin
|
|
if NodeType = xeDeclaration then
|
|
Result := NodeCreate(TsdDeclaration);
|
|
if NodeType = xeStyleSheet then
|
|
Result := NodeCreate(TsdStyleSheet);
|
|
ReadContainerNode(Result);
|
|
end;
|
|
|
|
xeDocType:
|
|
begin
|
|
Result := NodeCreate(TsdDocType);
|
|
ReadContainerNode(Result);
|
|
TsdDocType(Result).FNameID := ReadSymbol(S);
|
|
ReadCharData(TsdDocType(Result).FExternalID);
|
|
ReadQuotedText(TsdDocType(Result).FSystemLiteral);
|
|
ReadQuotedText(TsdDocType(Result).FPubIDLiteral);
|
|
end;
|
|
|
|
xeCharData, xeComment, xeCData, xeWhiteSpace, xeCondSection, xeInstruction:
|
|
begin
|
|
if NodeType = xeCharData then
|
|
Result := NodeCreate(TsdCharData);
|
|
if NodeType = xeComment then
|
|
Result := NodeCreate(TsdComment);
|
|
if NodeType = xeCData then
|
|
Result := NodeCreate(TsdCData);
|
|
if NodeType = xeWhiteSpace then
|
|
Result := NodeCreate(TsdWhiteSpace);
|
|
if NodeType = xeCondSection then
|
|
Result := NodeCreate(TsdConditionalSection);
|
|
if NodeType = xeInstruction then
|
|
Result := NodeCreate(TsdInstruction);
|
|
ReadCharData(Result);
|
|
end;
|
|
|
|
xeQuotedText:
|
|
begin
|
|
Result := NodeCreate(TsdQuotedText);
|
|
ReadQuotedText(Result);
|
|
end;
|
|
|
|
xeEndTag:
|
|
begin
|
|
// this signals end of document
|
|
Result := nil;
|
|
end;
|
|
else
|
|
begin
|
|
DoDebugOut(Self, wsFail, format('unknown %s', [cElementTypeNames[NodeType]]));
|
|
end;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TsdBinaryXml.SaveToFile(const AFileName: string);
|
|
var
|
|
S: TMemoryStream;
|
|
begin
|
|
S := TMemoryStream.Create;
|
|
try
|
|
SaveToStream(S);
|
|
S.SaveToFile(AFileName);
|
|
finally
|
|
S.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TsdBinaryXml.SaveToStream(S: TStream);
|
|
var
|
|
Table: TsdSymbolTable;
|
|
CoderName: Utf8String;
|
|
CodecPos, StartPos: int64;
|
|
SIn, SOut: TStream;
|
|
const
|
|
NewLine: byte = $0A;
|
|
begin
|
|
// cookie
|
|
sdWriteToStream(S, cBinaryXmlCookie);
|
|
|
|
// write binary xml version
|
|
WriteCardinal(S, cBinaryXmlVersion);
|
|
|
|
// codec position
|
|
CodecPos := S.Position;
|
|
|
|
// write default option "none", can be rewritten in OnDecode
|
|
sdWriteToStream(S, 'none');
|
|
|
|
// to allow visibility of codec at topmost line
|
|
S.Write(NewLine, 1);
|
|
|
|
// start position of payload
|
|
StartPos := S.Position;
|
|
|
|
// sort string table by frequency
|
|
SortByFrequency;
|
|
|
|
// write symbol table
|
|
Table := FDocument.SymbolTable;
|
|
Table.SaveToStream(S, Table.PluralSymbolCount);
|
|
|
|
// write xml structure and single symbols
|
|
WriteDocument(S);
|
|
|
|
if assigned(FOnEncode) then
|
|
begin
|
|
S.Position := StartPos;
|
|
SIn := TMemoryStream.Create;
|
|
SOut := TMemoryStream.Create;
|
|
try
|
|
SIn.CopyFrom(S, S.Size - StartPos);
|
|
SIn.Position := 0;
|
|
CoderName := FOnEncode(SIn, SOut, SIn.Size);
|
|
if length(CoderName) <> 4 then
|
|
begin
|
|
DoDebugOut(Self, wsFail, 'coder name length <> 4');
|
|
exit;
|
|
end;
|
|
S.Position := CodecPos;
|
|
sdWriteToStream(S, CoderName);
|
|
S.Position := StartPos;
|
|
SOut.Position := 0;
|
|
|
|
// first store plain size
|
|
sdWriteCardinal(S, SIn.Size);
|
|
|
|
// then copy output codec stream to S
|
|
S.CopyFrom(SOut, SOut.Size);
|
|
S.Size := S.Position;
|
|
|
|
finally
|
|
SIn.Free;
|
|
SOut.Free;
|
|
end;
|
|
end;
|
|
|
|
end;
|
|
|
|
procedure TsdBinaryXml.SortByFrequency;
|
|
type
|
|
TForEachFunc = function(C: Cardinal): Cardinal of object;
|
|
var
|
|
Node: TXmlNode;
|
|
// local
|
|
procedure ForEachNode(ANode: TXmlNode; ForEachFunc: TForEachFunc);
|
|
var
|
|
NodeType: TsdElementType;
|
|
begin
|
|
NodeType := ANode.ElementType;
|
|
case NodeType of
|
|
xeElement, xeDtdElement, xeDtdEntity, xeDtdNotation, xeDtdAttList:
|
|
TsdElement(ANode).FNameID := ForEachFunc(TsdElement(ANode).FNameID);
|
|
|
|
xeCharData, xeComment, xeCData, xeWhiteSpace, xeCondSection, xeInstruction,
|
|
xeQuotedText:
|
|
TsdCharData(ANode).FValueID := ForEachFunc(TsdCharData(ANode).FValueID);
|
|
|
|
xeAttribute:
|
|
begin
|
|
TsdAttribute(ANode).FNameID := ForEachFunc(TsdAttribute(ANode).FNameID);
|
|
TsdAttribute(ANode).FCoreValue.FValueID := ForEachFunc(TsdAttribute(ANode).FCoreValue.FValueID);
|
|
end;
|
|
|
|
xeDocType:
|
|
begin
|
|
TsdDocType(ANode).FNameID := ForEachFunc(TsdElement(ANode).FNameID);
|
|
TsdDocType(ANode).FExternalID.FValueID := ForEachFunc(TsdDocType(ANode).FExternalID.FValueID);
|
|
TsdDocType(ANode).FSystemLiteral.FValueID := ForEachFunc(TsdDocType(ANode).FSystemLiteral.FValueID);
|
|
TsdDocType(ANode).FPubIDLiteral.FValueID := ForEachFunc(TsdDocType(ANode).FPubIDLiteral.FValueID);
|
|
end;
|
|
end;
|
|
end;
|
|
// main
|
|
begin
|
|
// create NewIDs index
|
|
SetLength(FNewIDs, FDocument.SymbolTable.SymbolCount + 1);
|
|
|
|
// clear frequency first
|
|
FDocument.SymbolTable.ClearFrequency;
|
|
|
|
// increment frequency for each NameID/ValueID in Node
|
|
Node := FDocument.FRootNodes.FindFirst;
|
|
while assigned(Node) do
|
|
begin
|
|
ForEachNode(Node, IncrementFrequency);
|
|
//DoDebugOut(Self, wsInfo, Node.ElementTypeName + ' ' + Node.Name);
|
|
Node := FDocument.FRootNodes.FindNext(Node);
|
|
end;
|
|
|
|
// sort symbol table by frequency
|
|
FDocument.SymbolTable.SortByFrequency(FNewIDs);
|
|
|
|
// update IDs in document
|
|
Node := FDocument.FRootNodes.FindFirst;
|
|
while assigned(Node) do
|
|
begin
|
|
ForEachNode(Node, UpdateID);
|
|
Node := FDocument.FRootNodes.FindNext(Node);
|
|
end;
|
|
end;
|
|
|
|
function TsdBinaryXml.UpdateID(AID: Cardinal): Cardinal;
|
|
begin
|
|
Result := FNewIDs[AID];
|
|
end;
|
|
|
|
procedure TsdBinaryXml.WriteCardinal(S: TStream; ACardinal: cardinal);
|
|
var
|
|
C: byte;
|
|
begin
|
|
repeat
|
|
if ACardinal <= $7F then
|
|
begin
|
|
C := ACardinal;
|
|
S.Write(C, 1);
|
|
exit;
|
|
end else
|
|
C := (ACardinal and $7F) or $80;
|
|
|
|
S.Write(C, 1);
|
|
ACardinal := ACardinal shr 7;
|
|
until ACardinal = 0;
|
|
end;
|
|
|
|
procedure TsdBinaryXml.WriteDocument(S: TStream);
|
|
var
|
|
Count: cardinal;
|
|
Node: TXmlNode;
|
|
// i: TsdElementType;
|
|
begin
|
|
Count := 0;
|
|
|
|
// use FindFirst/FindNext to iterate thru all the nodes
|
|
Node := FDocument.FRootNodes.FindFirst;
|
|
while assigned(Node) do
|
|
begin
|
|
// this node
|
|
WriteNode(S, Node);
|
|
inc(Count);
|
|
|
|
// next node
|
|
Node := FDocument.FRootNodes.FindNext(Node);
|
|
end;
|
|
|
|
{ for i := low(TsdElementType) to high(TsdElementType) do
|
|
begin
|
|
DoDebugOut(Self, wsInfo, format('%s: %d', [cElementTypeNames[i], FElementTypeCount[i]]));
|
|
end;}
|
|
|
|
// write the end tag signal
|
|
WriteCardinal(S, ord(xeEndTag));
|
|
|
|
// debug
|
|
DoDebugOut(Self, wsInfo, format('total node count: %d', [Count]));
|
|
end;
|
|
|
|
procedure TsdBinaryXml.WriteNode(S: TStream; ANode: TXmlNode);
|
|
var
|
|
NodeType: TsdElementType;
|
|
Table: TsdSymbolTable;
|
|
|
|
// local
|
|
procedure WriteSymbol(ASymbolID: cardinal);
|
|
begin
|
|
if ASymbolID <= cardinal(Table.PluralSymbolCount) then
|
|
WriteCardinal(S, ASymbolID + 1) // "ASymbolID + 1" from 1..N
|
|
else
|
|
begin
|
|
WriteCardinal(S, 0); // this signals we do not use table
|
|
Table.SaveSymbol(S, ASymbolID);
|
|
end;
|
|
end;
|
|
// local
|
|
procedure WriteContainerNode(ANode: TXmlNode);
|
|
begin
|
|
// check
|
|
if not (ANode is TsdContainerNode) then
|
|
DoDebugOut(Self, wsFail, 'wrong element type');
|
|
// write persistent properties of containernode
|
|
WriteCardinal(S, TsdContainerNode(ANode).FNodes.Count);
|
|
WriteCardinal(S, TsdContainerNode(ANode).FDirectNodeCount);
|
|
WriteCardinal(S, TsdContainerNode(ANode).FValueIndex + 1);
|
|
end;
|
|
// local
|
|
procedure WriteElementNode(ANode: TXmlNode);
|
|
begin
|
|
// check
|
|
if not (ANode is TsdElement) then
|
|
DoDebugOut(Self, wsFail, 'wrong element type');
|
|
// write persistent properties of containernode
|
|
WriteContainerNode(ANode);
|
|
// write persistent properties of element
|
|
WriteSymbol(TsdElement(ANode).FNameID);
|
|
WriteCardinal(S, ord(TsdElement(ANode).FNodeClosingStyle));
|
|
end;
|
|
// local
|
|
procedure WriteCharData(ANode: TXmlNode);
|
|
begin
|
|
// check
|
|
if not (ANode is TsdCharData) then
|
|
DoDebugOut(Self, wsFail, 'wrong element type');
|
|
WriteSymbol(TsdCharData(ANode).FValueID);
|
|
end;
|
|
// local
|
|
procedure WriteQuotedText(ANode: TXmlNode);
|
|
begin
|
|
// check
|
|
if not (ANode is TsdQuotedText) then
|
|
DoDebugOut(Self, wsFail, 'wrong element type');
|
|
WriteSymbol(TsdCharData(ANode).FValueID);
|
|
WriteCardinal(S, ord(TsdQuotedText(ANode).FQuoteStyle));
|
|
end;
|
|
|
|
// main
|
|
begin
|
|
Table := FDocument.SymbolTable;
|
|
|
|
NodeType := ANode.ElementType;
|
|
inc(FElementTypeCount[NodeType]); // for stats
|
|
|
|
WriteCardinal(S, ord(NodeType));
|
|
|
|
//DoDebugOut(Self, wsInfo, IntToStr(ANode.TreeDepth) + ' ' + cElementTypeNames[NodeType] + ' ' + ANode.Name);
|
|
|
|
case NodeType of
|
|
|
|
xeElement, xeDtdElement, xeDtdEntity, xeDtdNotation, xeDtdAttList:
|
|
begin
|
|
WriteElementNode(ANode);
|
|
end;
|
|
|
|
xeAttribute:
|
|
begin
|
|
WriteSymbol(TsdAttribute(ANode).FNameID);
|
|
WriteQuotedText(TsdAttribute(ANode).FCoreValue);
|
|
end;
|
|
|
|
xeDeclaration, xeStyleSheet:
|
|
begin
|
|
WriteContainerNode(ANode);
|
|
end;
|
|
|
|
xeDocType:
|
|
begin
|
|
WriteContainerNode(ANode);
|
|
WriteSymbol(TsdDoctype(ANode).FNameID);
|
|
WriteCharData(TsdDocType(ANode).FExternalID);
|
|
WriteQuotedText(TsdDocType(ANode).FSystemLiteral);
|
|
WriteQuotedText(TsdDocType(ANode).FPubIDLiteral);
|
|
end;
|
|
|
|
xeCharData, xeComment, xeCData, xeWhiteSpace, xeCondSection, xeInstruction:
|
|
begin
|
|
WriteCharData(ANode);
|
|
end;
|
|
|
|
xeQuotedText:
|
|
begin
|
|
WriteQuotedText(ANode);
|
|
end;
|
|
|
|
xeEndTag:
|
|
begin
|
|
// this signals end of document
|
|
end;
|
|
|
|
else
|
|
begin
|
|
DoDebugOut(Self, wsWarn, format('unknown %s "%s"', [cElementTypeNames[NodeType], ANode.Name]));
|
|
end;
|
|
|
|
end;
|
|
end;
|
|
|
|
{$ifdef D7UP}
|
|
procedure GetXmlFormatSettings;
|
|
var
|
|
TimePrefix, TimePostfix, HourFormat: string;
|
|
begin
|
|
cXmlFormatSettings.CurrencyString := '';
|
|
cXmlFormatSettings.CurrencyFormat := 0;
|
|
cXmlFormatSettings.NegCurrFormat := 0;
|
|
cXmlFormatSettings.ThousandSeparator := ',';
|
|
cXmlFormatSettings.DecimalSeparator := '.';
|
|
cXmlFormatSettings.CurrencyDecimals := 0;
|
|
cXmlFormatSettings.DateSeparator := '/';
|
|
cXmlFormatSettings.ShortDateFormat := 'm/d/yy';
|
|
cXmlFormatSettings.LongDateFormat := 'mmmm d, yyyy';
|
|
cXmlFormatSettings.TimeSeparator := ':';
|
|
cXmlFormatSettings.TimeAMString := 'am';
|
|
cXmlFormatSettings.TimePMString := 'pm';
|
|
TimePrefix := '';
|
|
HourFormat := 'h';
|
|
TimePostfix := ' AMPM';
|
|
cXmlFormatSettings.ShortTimeFormat := TimePrefix + HourFormat + ':mm' + TimePostfix;
|
|
cXmlFormatSettings.LongTimeFormat := TimePrefix + HourFormat + ':mm:ss' + TimePostfix;
|
|
cXmlFormatSettings.ListSeparator := ',';
|
|
end;
|
|
{$else D7UP}
|
|
// D5 stub
|
|
procedure GetXmlFormatSettings;
|
|
begin
|
|
end;
|
|
{$endif D7UP}
|
|
|
|
initialization
|
|
|
|
// NativeXml's xml format settings (with decimal separator = '.')
|
|
GetXmlFormatSettings;
|
|
|
|
end.
|
|
|
|
|