2035 lines
64 KiB
ObjectPascal
2035 lines
64 KiB
ObjectPascal
unit SpkXMLParser;
|
|
|
|
{$mode Delphi}
|
|
{$DEFINE SPKXMLPARSER}
|
|
{$WARN 4055 off : Conversion between ordinals and pointers is not portable}
|
|
|
|
interface
|
|
|
|
{TODO Organize the visibility and virtuality of methods and properties}
|
|
|
|
// Notatki: Stosujê konsekwentnie case-insensitivity
|
|
// Notes: I apply case-insensitivity consistently
|
|
|
|
uses
|
|
SysUtils, Classes, ContNrs, Graphics, Math;
|
|
|
|
//todo: use LineEnding?
|
|
const CRLF=#13#10;
|
|
|
|
type // The type of XML nodes
|
|
TXMLNodeType = (xntNormal, xntControl, xntComment);
|
|
|
|
type // Forward declaration for XML nodes
|
|
TSpkXMLNode = class;
|
|
|
|
TBinaryTreeNode = class;
|
|
|
|
// I'm going to binary tree searches
|
|
TBinaryTreeNode = class(TObject)
|
|
private
|
|
// The left subtree
|
|
FLeft,
|
|
// The right subtree
|
|
FRight,
|
|
// Parent
|
|
FParent : TBinaryTreeNode;
|
|
// Data contained in the node
|
|
FData : array of TSpkXMLNode;
|
|
// The height of the subtree
|
|
FSubtreeSize : integer;
|
|
|
|
protected
|
|
// *** Methods for the tree ***
|
|
|
|
// Setter for the left subtree
|
|
procedure SetLeft(ANode : TBinaryTreeNode);
|
|
// Setter dla prawego poddrzewa
|
|
procedure SetRight(ANode : TBinaryTreeNode);
|
|
|
|
// *** Metody dotycz¹ce danych ***
|
|
|
|
// Getter dla liczby danych zawartych w wêŸle
|
|
function GetCount : integer;
|
|
// Getter dla danych zawartych w wêŸle
|
|
function GetData(index : integer) : TSpkXMLNode;
|
|
public
|
|
// Konstruktor
|
|
constructor create;
|
|
// Destruktor
|
|
destructor Destroy; override;
|
|
|
|
// *** Metody dotycz¹ce drzewa ***
|
|
|
|
// Wymuszenie odœwie¿enia wysokoœci poddrzewa
|
|
procedure RefreshSubtreeSize;
|
|
// Metoda powoduje odpiêcie od obecnego parenta (wywo³ywana tylko przez
|
|
// niego)
|
|
procedure DetachFromParent;
|
|
// Metoda powoduje przypiêcie do nowego parenta (wywo³ywana przez nowego
|
|
// parenta
|
|
procedure AttachToParent(AParent : TBinaryTreeNode);
|
|
// Metoda wywo³ywana przez jedno z dzieci w momencie, gdy jest ono
|
|
// przepinane do innego drzewa
|
|
procedure DetachChild(AChild : TBinaryTreeNode);
|
|
|
|
// *** Metody dotycz¹ce danych ***
|
|
|
|
// Dodaje dane
|
|
procedure Add(AData : TSpkXMLNode);
|
|
// Usuwa dane z listy (nie zwalnia!)
|
|
procedure Remove(AData : TSpkXMLNode);
|
|
// Usuwa dane o zadanym indeksie (nie zwalnia!)
|
|
procedure Delete(index : integer);
|
|
// Usuwa wszystkie dane
|
|
procedure Clear;
|
|
|
|
property Data[index : integer] : TSpkXMLNode read GetData;
|
|
|
|
property Left : TBinaryTreeNode read FLeft write SetLeft;
|
|
property Right : TBinaryTreeNode read FRight write SetRight;
|
|
property Parent : TBinaryTreeNode read FParent;
|
|
property SubtreeSize : integer read FSubtreeSize;
|
|
property Count : integer read GetCount;
|
|
end;
|
|
|
|
// Klasa przechowuj¹ca pojedynczy parametr ga³êzi XMLowej
|
|
TSpkXMLParameter = class(TObject)
|
|
private
|
|
// Nazwa parametru
|
|
FName,
|
|
// WartoϾ parametru
|
|
FValue : string;
|
|
protected
|
|
// Getter dla w³asnoœci ValueAsInteger
|
|
function GetValueAsInteger : integer;
|
|
// Setter dla w³asnoœci ValueAsInteger
|
|
procedure SetValueAsInteger(AValue : integer);
|
|
// Getter dla w³asnoœci ValueAsExtended
|
|
function GetValueAsExtended : extended;
|
|
// Setter dla w³asnoœci ValueAsExtended
|
|
procedure SetValueAsExtended(AValue : extended);
|
|
// Getter dla w³asnoœci ValueAsColor
|
|
function GetValueAsColor : TColor;
|
|
// Setter dla w³asnoœci ValueAsColor
|
|
procedure SetValueAsColor(AValue : TColor);
|
|
// Getter dla w³asnoœci ValueAsBoolean
|
|
function GetValueAsBoolean : boolean;
|
|
// Setter dla w³asnoœci ValueAsBoolean
|
|
procedure SetValueAsBoolean(AValue : boolean);
|
|
public
|
|
// Konstruktor
|
|
constructor create; overload;
|
|
// Konstruktor pozwalaj¹cy nadaæ pocz¹tkowe wartoœci parametrowi
|
|
constructor create(AName : string; AValue : string); overload;
|
|
// Destruktor
|
|
destructor Destroy; override;
|
|
|
|
property Name : string read FName write FName;
|
|
property Value : string read FValue write FValue;
|
|
property ValueAsInteger : integer read GetValueAsInteger write SetValueAsInteger;
|
|
property ValueAsExtended : extended read GetValueAsExtended write SetValueAsExtended;
|
|
property ValueAsColor : TColor read GetValueAsColor write SetValueAsColor;
|
|
property ValueAsBoolean : boolean read GetValueAsBoolean write SetValueAsBoolean;
|
|
end;
|
|
|
|
// Lista parametrów
|
|
TSpkXMLParameters = class(TObject)
|
|
private
|
|
// Wewnêtrzna lista na której przechowywane s¹ parametry ga³êzi
|
|
FList : TObjectList;
|
|
protected
|
|
// Getter dla w³asnoœci ParamByName (szuka parametru po jego nazwie)
|
|
function GetParamByName(index : string; autocreate : boolean) : TSpkXMLParameter;
|
|
// Getter dla w³asnoœci ParamByIndex (zwraca i-ty parametr)
|
|
function GetParamByIndex(index : integer) : TSpkXMLParameter;
|
|
// Zwraca liczbê parametrów
|
|
function GetCount : integer;
|
|
public
|
|
// Konstruktor
|
|
constructor create;
|
|
// Destruktor
|
|
destructor Destroy; override;
|
|
|
|
// Dodaje parametr na listê
|
|
procedure Add(AParameter : TSpkXMLParameter);
|
|
// Wstawia parametr na listê na zadane miejsce
|
|
procedure Insert( AIndex : integer; AParameter : TSpkXMLParameter);
|
|
// Usuwa parametr o podanym indeksie z listy
|
|
procedure Delete(index : integer);
|
|
// Usuwa zadany parametr z listy
|
|
procedure Remove(AParameter : TSpkXMLParameter);
|
|
// Zwraca indeks zadanego parametru
|
|
function IndexOf(AParameter : TSpkXMLParameter) : integer;
|
|
// Czyœci listê parametrów
|
|
procedure Clear;
|
|
|
|
property ParamByName[index : string; autocreate : boolean] : TSpkXMLParameter read GetParamByName; default;
|
|
property ParamByIndex[index : integer] : TSpkXMLParameter read GetParamByIndex;
|
|
|
|
property Count : integer read GetCount;
|
|
end;
|
|
|
|
TSpkBaseXmlNode = class;
|
|
|
|
// Bazowa klasa dla ga³êzi XMLowych, zapewniaj¹ca przechowywanie, operacje
|
|
// i wyszukiwanie podga³êzi.
|
|
TSpkBaseXmlNode = class(TObject)
|
|
private
|
|
FList : TObjectList;
|
|
FTree : TBinaryTreeNode;
|
|
FParent : TSpkBaseXmlNode;
|
|
protected
|
|
// *** Operacje na drzewie AVL ***
|
|
// Dodaje do drzewa ga³¹Ÿ z zadan¹ TSpkXMLNode
|
|
procedure TreeAdd(ANode : TSpkXMLNode);
|
|
// Usuwa z drzewa ga³¹Ÿ z zadan¹ TSpkXMLNode
|
|
procedure TreeDelete(ANode : TSpkXMLNode);
|
|
// Szuka ga³êzi drzewa
|
|
function TreeFind(ANode : TSpkXMLNode) : TBinaryTreeNode;
|
|
// Balansuje wszystkie wêz³y od zadanego do korzenia w³¹cznie.
|
|
procedure Ballance(Leaf : TBinaryTreeNode);
|
|
// Obraca wêze³ w lewo i zwraca wêze³, który znalaz³ siê w miejscu
|
|
// obróconego.
|
|
function RotateLeft(Root : TBinaryTreeNode) : TBinaryTreeNode;
|
|
// Obraca wêze³ w prawo i zwraca wêze³, który znalaz³ siê w miejscu
|
|
// obróconego
|
|
function RotateRight(Root : TBinaryTreeNode) : TBinaryTreeNode;
|
|
|
|
function GetNodeByIndex(index : integer) : TSpkXMLNode;
|
|
function GetNodeByName(index : string; autocreate : boolean) : TSpkXMLNode;
|
|
function GetCount : integer;
|
|
public
|
|
// Konstruktor
|
|
constructor create; virtual;
|
|
// Destruktor
|
|
destructor Destroy; override;
|
|
|
|
// Dodaje podga³¹Ÿ i umieszcza w odpowiednim miejscu w drzewie
|
|
procedure Add(ANode : TSpkXMLNode);
|
|
// Wstawia podga³¹Ÿ w podane miejsce (na drzewie ma to taki sam efekt
|
|
// jak dodanie)
|
|
procedure Insert(AIndex : integer; ANode : TSpkXMLNode);
|
|
// Usuwa podga³¹Ÿ z listy i z drzewa, a nastêpnie zwalnia pamiêæ
|
|
procedure Delete(AIndex : integer);
|
|
// Usuwa podga³¹Ÿ z listy i z drzewa, a nastêpnie zwalnia pamiêæ
|
|
procedure Remove(ANode : TSpkXMLNode);
|
|
// Zwraca indeks podga³êzi
|
|
function IndexOf(ANode : TSpkXMLNode) : integer;
|
|
// Usuwa wszystkie podga³êzie
|
|
procedure Clear; virtual;
|
|
|
|
// Metoda powinna zostaæ wywo³ana przed zmian¹ nazwy przez jedn¹ z podga³êzi
|
|
procedure BeforeChildChangeName(AChild : TSpkXmlNode);
|
|
// Metoda powinna zostaæ wywo³ana po zmianie nazwy przez jedn¹ z podga³êzi
|
|
procedure AfterChildChangeName(AChild : TSpkXMLNode);
|
|
|
|
property NodeByIndex[index : integer] : TSpkXMLNode read GetNodeByIndex;
|
|
property NodeByName[index : string; autocreate : boolean] : TSpkXMLNode read GetNodeByName; default;
|
|
property Count : integer read GetCount;
|
|
property Parent : TSpkBaseXmlNode read FParent write FParent;
|
|
end;
|
|
|
|
// Ga³¹Ÿ XMLa. Dziêki temu, ¿e dziedziczymy po TSpkBaseXMLNode mamy
|
|
// zapewnion¹ obs³ugê podga³êzi, trzeba tylko dodaæ parametry, nazwê i
|
|
// tekst.
|
|
TSpkXMLNode = class(TSpkBaseXMLNode)
|
|
private
|
|
// Nazwa ga³êzi
|
|
FName : string;
|
|
// Tekst ga³êzi
|
|
FText : string;
|
|
// Parametry ga³êzi
|
|
FParameters : TSpkXMLParameters;
|
|
// Rodzaj ga³êzi
|
|
FNodeType : TXMLNodeType;
|
|
protected
|
|
// Setter dla w³asnoœci name (przed i po zmianie nazwy trzeba poinformowaæ
|
|
// parenta, by poprawnie dzia³a³o wyszukiwanie po nazwie
|
|
procedure SetName(Value : string);
|
|
// Getter dla TextAsInteger
|
|
function GetTextAsInteger : integer;
|
|
// Setter dla TextAsInteger
|
|
procedure SetTextAsInteger(value : integer);
|
|
// Getter dla TextAsExtended
|
|
function GetTextAsExtended : extended;
|
|
// Setter dla TextAsExtended
|
|
procedure SetTextAsExtended(value : extended);
|
|
// Getter dla TextAsColor
|
|
function GetTextAsColor : TColor;
|
|
// Setter dla TextAsColor
|
|
procedure SetTextAsColor(value : TColor);
|
|
// Getter dla TextAsBoolean
|
|
function GetTextAsBoolean : boolean;
|
|
// Setter dla TextAsBoolean
|
|
procedure SetTextAsBoolean(value : boolean);
|
|
public
|
|
// Konstruktor
|
|
constructor create(AName : string; ANodeType : TXMLNodeType); reintroduce;
|
|
// Destruktor
|
|
destructor Destroy; override;
|
|
// Czyœci ga³¹Ÿ (tekst, parametry, podga³êzie)
|
|
procedure Clear; override;
|
|
|
|
property Name : string read FName write SetName;
|
|
property Text : string read FText write FText;
|
|
property TextAsInteger : integer read GetTextAsInteger write SetTextAsInteger;
|
|
property TextAsExtended : extended read GetTextAsExtended write SetTextAsExtended;
|
|
property TextAsColor : TColor read GetTextAsColor write SetTextAsColor;
|
|
property TextAsBoolean : boolean read GetTextAsBoolean write SetTextAsBoolean;
|
|
property Parameters : TSpkXMLParameters read FParameters;
|
|
property NodeType : TXMLNodeType read FNodeType;
|
|
end;
|
|
|
|
// Dziêki temu, ¿e dziedziczymy po TSpkBaseXMLNode, mamy zapewnion¹ obs³ugê
|
|
// podga³êzi
|
|
TSpkXMLParser = class(TSpkBaseXMLNode)
|
|
private
|
|
protected
|
|
public
|
|
// Konstruktor
|
|
constructor create; override;
|
|
// Destruktor
|
|
destructor Destroy; override;
|
|
// Przetwarza tekst z XMLem podany jako parametr
|
|
procedure Parse(input : PChar);
|
|
// Generuje XML na podstawie zawartoœci komponentu
|
|
function Generate(UseFormatting : boolean = true) : string;
|
|
// Wczytuje plik XML z dysku
|
|
procedure LoadFromFile(AFile : string);
|
|
// Zapisuje plik XML na dysk
|
|
procedure SaveToFile(AFile : string; UseFormatting : boolean = true);
|
|
// Wczytuje plik XML ze strumienia
|
|
procedure LoadFromStream(AStream : TStream);
|
|
// Zapisuje plik XML do strumienia
|
|
procedure SaveToStream(AStream : TStream; UseFormatting : boolean = true);
|
|
end;
|
|
|
|
implementation
|
|
|
|
{ TBinaryTreeNode }
|
|
|
|
procedure TBinaryTreeNode.SetLeft(ANode : TBinaryTreeNode);
|
|
|
|
begin
|
|
// Odpinamy poprzedni¹ lew¹ ga³¹Ÿ (o ile istnia³a)
|
|
if FLeft<>nil then
|
|
begin
|
|
FLeft.DetachFromParent;
|
|
FLeft:=nil;
|
|
end;
|
|
|
|
// Przypinamy now¹ ga³¹Ÿ
|
|
FLeft:=ANode;
|
|
|
|
// Aktualizujemy jej parenta
|
|
if FLeft<>nil then
|
|
FLeft.AttachToParent(self);
|
|
|
|
// Odœwie¿amy wysokoœæ poddrzewa
|
|
RefreshSubtreeSize;
|
|
end;
|
|
|
|
procedure TBinaryTreeNode.SetRight(ANode : TBinaryTreeNode);
|
|
|
|
begin
|
|
// Odpinamy poprzedni¹ praw¹ ga³¹Ÿ (o ile istnia³a)
|
|
if FRight<>nil then
|
|
begin
|
|
FRight.DetachFromParent;
|
|
FRight:=nil;
|
|
end;
|
|
|
|
// Przypinamy now¹ ga³¹Ÿ
|
|
FRight:=ANode;
|
|
|
|
// Aktualizujemy jej parnenta
|
|
if FRight<>nil then
|
|
FRight.AttachToParent(self);
|
|
|
|
// Odœwie¿amy wysokoœæ poddrzewa
|
|
RefreshSubtreeSize;
|
|
end;
|
|
|
|
function TBinaryTreeNode.GetCount : integer;
|
|
|
|
begin
|
|
result:=length(FData);
|
|
end;
|
|
|
|
function TBinaryTreeNode.GetData(index : integer) : TSpkXMLNode;
|
|
|
|
begin
|
|
if (index<0) or (index>high(FData)) then
|
|
raise exception.create('Nieprawid³owy indeks!');
|
|
|
|
result:=FData[index];
|
|
end;
|
|
|
|
constructor TBinaryTreeNode.create;
|
|
|
|
begin
|
|
inherited create;
|
|
FLeft:=nil;
|
|
FRight:=nil;
|
|
FParent:=nil;
|
|
setlength(FData,0);
|
|
FSubtreeSize:=0;
|
|
end;
|
|
|
|
destructor TBinaryTreeNode.destroy;
|
|
|
|
begin
|
|
// Odpinamy siê od parenta
|
|
if FParent<>nil then
|
|
FParent.DetachChild(self);
|
|
|
|
// Zwalniamy poddrzewa
|
|
if FLeft<>nil then
|
|
FLeft.free;
|
|
if FRight<>nil then
|
|
FRight.free;
|
|
|
|
inherited destroy;
|
|
end;
|
|
|
|
procedure TBinaryTreeNode.RefreshSubtreeSize;
|
|
|
|
function LeftSubtreeSize : integer;
|
|
|
|
begin
|
|
if FLeft=nil then result:=0 else result:=1+FLeft.SubTreeSize;
|
|
end;
|
|
|
|
function RightSubtreeSize : integer;
|
|
|
|
begin
|
|
if FRight=nil then result:=0 else result:=1+FRight.SubTreeSize;
|
|
end;
|
|
|
|
begin
|
|
FSubtreeSize:=max(LeftSubtreeSize,RightSubtreeSize);
|
|
if Parent<>nil then
|
|
Parent.RefreshSubtreeSize;
|
|
end;
|
|
|
|
// According to the assumptions, this method can only be called the current parent.
|
|
procedure TBinaryTreeNode.DetachFromParent;
|
|
begin
|
|
FParent := nil;
|
|
end;
|
|
|
|
// According to the assumptions, this method is called by the new parent
|
|
// of the element. The element must take care to inform the previous parent
|
|
// about the fact that he is removable.
|
|
procedure TBinaryTreeNode.AttachToParent(AParent : TBinaryTreeNode);
|
|
begin
|
|
if AParent<>FParent then
|
|
begin
|
|
if FParent<>nil then
|
|
FParent.DetachChild(self);
|
|
FParent := AParent;
|
|
end;
|
|
end;
|
|
|
|
procedure TBinaryTreeNode.DetachChild(AChild : TBinaryTreeNode);
|
|
|
|
begin
|
|
// Zgodnie z za³o¿eniami, metodê t¹ mo¿e wywo³aæ tylko jeden z podelementów
|
|
// - lewy lub prawy, podczas zmiany parenta.
|
|
if AChild=FLeft then FLeft:=nil;
|
|
if AChild=FRight then FRight:=nil;
|
|
|
|
// Przeliczamy ponownie wysokoϾ poddrzewa
|
|
RefreshSubtreeSize;
|
|
end;
|
|
|
|
procedure TBinaryTreeNode.Add(AData : TSpkXMLNode);
|
|
begin
|
|
{$B-}
|
|
if (Length(FData)=0) or ((Length(FData)>0) and (Uppercase(FData[0].Name)=Uppercase(AData.Name))) then
|
|
begin
|
|
SetLength(FData, Length(FData)+1);
|
|
FData[High(FData)] := AData;
|
|
end else
|
|
raise Exception.Create('A single node stores data with identical names!');
|
|
end;
|
|
|
|
procedure TBinaryTreeNode.Remove(AData : TSpkXMLNode);
|
|
|
|
var i : integer;
|
|
|
|
begin
|
|
i:=0;
|
|
{$B-}
|
|
while (i<=high(FData)) and (FData[i]<>AData) do
|
|
inc(i);
|
|
|
|
if i<high(FData) then
|
|
self.Delete(i);
|
|
end;
|
|
|
|
procedure TBinaryTreeNode.Delete(index : integer);
|
|
|
|
var i : integer;
|
|
|
|
begin
|
|
if (index<0) or (index>high(FData)) then
|
|
raise exception.create('Nieprawid³owy indeks.');
|
|
|
|
if index<high(FData) then
|
|
for i:=index to high(FData)-1 do
|
|
FData[i]:=FData[i+1];
|
|
|
|
setlength(FData,length(FData)-1);
|
|
end;
|
|
|
|
procedure TBinaryTreeNode.Clear;
|
|
|
|
begin
|
|
setlength(FData,0);
|
|
end;
|
|
|
|
{ TSpkXMLParameter }
|
|
|
|
constructor TSpkXMLParameter.create;
|
|
begin
|
|
inherited create;
|
|
FName:='';
|
|
FValue:='';
|
|
end;
|
|
|
|
constructor TSpkXMLParameter.create(AName, AValue: string);
|
|
begin
|
|
inherited create;
|
|
FName:=AName;
|
|
FValue:=AValue;
|
|
end;
|
|
|
|
destructor TSpkXMLParameter.destroy;
|
|
begin
|
|
inherited destroy;
|
|
end;
|
|
|
|
function TSpkXMLParameter.GetValueAsBoolean: boolean;
|
|
begin
|
|
if (uppercase(FValue)='TRUE') or (uppercase(FValue)='T') or
|
|
(uppercase(FValue)='YES') or (uppercase(FValue)='Y') then result:=true else
|
|
if (uppercase(FValue)='FALSE') or (uppercase(FValue)='F') or
|
|
(uppercase(FValue)='NO') or (uppercase(FValue)='N') then result:=false else
|
|
raise exception.create('Cannot convert values.');
|
|
end;
|
|
|
|
function TSpkXMLParameter.GetValueAsColor: TColor;
|
|
|
|
begin
|
|
try
|
|
result:=StrToInt(FValue);
|
|
except
|
|
raise exception.create('Cannot convert values.');
|
|
end;
|
|
end;
|
|
|
|
function TSpkXMLParameter.GetValueAsExtended: extended;
|
|
begin
|
|
try
|
|
result:=StrToFloat(FValue);
|
|
except
|
|
raise exception.create('Cannot convert values.');
|
|
end;
|
|
end;
|
|
|
|
function TSpkXMLParameter.GetValueAsInteger: integer;
|
|
begin
|
|
try
|
|
result:=StrToInt(FValue);
|
|
except
|
|
raise exception.create('Cannot convert values.');
|
|
end;
|
|
end;
|
|
|
|
procedure TSpkXMLParameter.SetValueAsBoolean(AValue: boolean);
|
|
begin
|
|
if AValue then FValue:='True' else FValue:='False';
|
|
end;
|
|
|
|
procedure TSpkXMLParameter.SetValueAsColor(AValue: TColor);
|
|
begin
|
|
FValue:=IntToStr(AValue);
|
|
end;
|
|
|
|
procedure TSpkXMLParameter.SetValueAsExtended(AValue: extended);
|
|
begin
|
|
FValue:=FloatToStr(AValue);
|
|
end;
|
|
|
|
procedure TSpkXMLParameter.SetValueAsInteger(AValue: integer);
|
|
begin
|
|
FValue:=IntToStr(AValue);
|
|
end;
|
|
|
|
{ TSpkXMLParameters }
|
|
|
|
procedure TSpkXMLParameters.Add(AParameter: TSpkXMLParameter);
|
|
begin
|
|
FList.add(AParameter);
|
|
end;
|
|
|
|
procedure TSpkXMLParameters.Insert(AIndex : integer; AParameter : TSpkXMLParameter);
|
|
|
|
begin
|
|
if (AIndex<0) or (AIndex>FList.count-1) then
|
|
raise exception.create('Invalid index.');
|
|
|
|
FList.Insert(AIndex, AParameter);
|
|
end;
|
|
|
|
procedure TSpkXMLParameters.Clear;
|
|
begin
|
|
FList.clear;
|
|
end;
|
|
|
|
constructor TSpkXMLParameters.create;
|
|
begin
|
|
inherited create;
|
|
FList:=TObjectList.create;
|
|
FList.OwnsObjects:=true;
|
|
end;
|
|
|
|
procedure TSpkXMLParameters.Delete(index: integer);
|
|
begin
|
|
if (index<0) or (index>FList.count-1) then
|
|
raise exception.create('Invalid parameter index.');
|
|
|
|
FList.delete(index);
|
|
end;
|
|
|
|
procedure TSpkXMLParameters.Remove(AParameter : TSpkXMLParameter);
|
|
|
|
begin
|
|
FList.Remove(AParameter);
|
|
end;
|
|
|
|
destructor TSpkXMLParameters.destroy;
|
|
begin
|
|
FList.Free;
|
|
inherited destroy;
|
|
end;
|
|
|
|
function TSpkXMLParameters.GetCount: integer;
|
|
begin
|
|
result:=FList.count;
|
|
end;
|
|
|
|
function TSpkXMLParameters.GetParamByIndex(index: integer): TSpkXMLParameter;
|
|
begin
|
|
if (index<0) or (index>Flist.count-1) then
|
|
raise exception.create('Invalid item index.');
|
|
|
|
result:=TSpkXMLParameter(FList[index]);
|
|
end;
|
|
|
|
function TSpkXMLParameters.GetParamByName(index: string;
|
|
autocreate: boolean): TSpkXMLParameter;
|
|
|
|
var i : integer;
|
|
AParameter : TSpkXMLParameter;
|
|
|
|
begin
|
|
// Szukamy elementu
|
|
i:=0;
|
|
while (i<=FList.count-1) and (uppercase(TSpkXMLParameter(FList[i]).Name)<>uppercase(index)) do inc(i);
|
|
|
|
if i<=FList.count-1 then
|
|
result:=TSpkXMLParameter(FList[i]) else
|
|
begin
|
|
if autocreate then
|
|
begin
|
|
AParameter:=TSpkXMLParameter.create(index,'');
|
|
FList.add(AParameter);
|
|
result:=AParameter;
|
|
end else
|
|
result:=nil;
|
|
end;
|
|
end;
|
|
|
|
function TSpkXMLParameters.IndexOf(AParameter: TSpkXMLParameter): integer;
|
|
begin
|
|
result:=FList.IndexOf(AParameter);
|
|
end;
|
|
|
|
{ TSpkBaseXMLNode }
|
|
|
|
procedure TSpkBaseXMLNode.TreeAdd(ANode : TSpkXMLNode);
|
|
|
|
var Tree, Parent : TBinaryTreeNode;
|
|
|
|
begin
|
|
// Szukam miejsca do dodania nowej ga³êzi drzewa
|
|
if Ftree=nil then
|
|
begin
|
|
// Nie mamy czego szukaæ, tworzymy korzeñ
|
|
FTree:=TBinaryTreeNode.create;
|
|
FTree.Add(ANode);
|
|
|
|
// Nie ma potrzeby balansowania drzewa
|
|
end else
|
|
begin
|
|
Tree:=FTree;
|
|
Parent:=nil;
|
|
{$B-}
|
|
while (Tree<>nil) and (uppercase(Tree.Data[0].Name)<>uppercase(ANode.Name)) do
|
|
begin
|
|
Parent:=Tree;
|
|
if uppercase(ANode.Name)<uppercase(Tree.Data[0].Name) then Tree:=Tree.Left else Tree:=Tree.Right;
|
|
end;
|
|
|
|
if Tree<>nil then
|
|
begin
|
|
// Znalaz³em ga³¹Ÿ z takim samym identyfikatorem
|
|
Tree.Add(ANode);
|
|
|
|
// Nie ma potrzeby balansowania drzewa, bo faktycznie nie zosta³a
|
|
// dodana ¿adna ga³¹Ÿ
|
|
end else
|
|
begin
|
|
Tree:=TBinaryTreeNode.create;
|
|
Tree.Add(ANode);
|
|
|
|
if uppercase(ANode.Name)<uppercase(Parent.Data[0].Name) then
|
|
Parent.Left:=Tree else
|
|
Parent.Right:=Tree;
|
|
|
|
// Zosta³a dodana nowa ga³¹Ÿ, wiêc balansujemy drzewo (o ile jest
|
|
// taka potrzeba)
|
|
self.Ballance(Tree);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.TreeDelete(ANode : TSpkXMLNode);
|
|
|
|
procedure InternalTreeDelete(DelNode : TBinaryTreeNode);
|
|
|
|
var DelParent : TBinaryTreeNode;
|
|
Successor : TBinaryTreeNode;
|
|
SuccessorParent : TBinaryTreeNode;
|
|
DeletingRoot : boolean;
|
|
i : integer;
|
|
|
|
begin
|
|
// Najpierw sprawdzamy, czy bêdziemy usuwaæ korzeñ. Jeœli tak, po usuniêciu
|
|
// mo¿e byæ potrzebna aktualizacja korzenia.
|
|
DeletingRoot:=DelNode=FTree;
|
|
|
|
// Kilka przypadków.
|
|
// 0. Mo¿e elementu nie ma w drzewku?
|
|
if DelNode=nil then
|
|
raise exception.create('There is no such element in the AVL tree!') else
|
|
// 1. Jeœli ga³¹Ÿ ta przechowuje wiêcej ni¿ tylko ten element, to usuwamy go
|
|
// z listy i koñczymy dzia³anie.
|
|
if DelNode.Count>1 then
|
|
begin
|
|
i:=0;
|
|
while (i<DelNode.Count) and (DelNode.Data[i]<>ANode) do inc(i);
|
|
|
|
DelNode.Delete(i);
|
|
end else
|
|
// 2. Jeœli jest to liœæ, po prostu usuwamy go.
|
|
if (DelNode.Left=nil) and (DelNode.Right=nil) then
|
|
begin
|
|
DelParent:=DelNode.Parent;
|
|
|
|
// Odpinamy od parenta
|
|
if DelParent<>nil then
|
|
begin
|
|
if DelParent.Left=DelNode then DelParent.Left:=nil;
|
|
if DelParent.Right=DelNode then DelParent.Right:=nil;
|
|
end;
|
|
|
|
// Ga³¹Ÿ automatycznie odpina wszystkie swoje podga³êzie, ale zak³adamy
|
|
// tu, ¿e jest to liœæ.
|
|
DelNode.free;
|
|
|
|
// Jeœli zachodzi taka potrzeba, balansujemy drzewo od ojca usuwanego
|
|
// elementu
|
|
if DelParent<>nil then
|
|
self.Ballance(DelParent);
|
|
|
|
// Jeœli usuwaliœmy root, ustawiamy go na nil (bo by³ to jedyny element)
|
|
if DeletingRoot then FTree:=nil;
|
|
end else
|
|
// 3. Je¿eli element ma tylko jedno dziecko, usuwamy je, poprawiamy powi¹zania
|
|
// i balansujemy drzewo
|
|
if (DelNode.Left=nil) xor (DelNode.Right=nil) then
|
|
begin
|
|
DelParent:=DelNode.Parent;
|
|
|
|
if DelParent=nil then
|
|
begin
|
|
// Usuwamy korzeñ
|
|
if DelNode.Left<>nil then
|
|
begin
|
|
FTree:=DelNode.Left;
|
|
// Mechanizmy drzewa odepn¹ automatycznie ga³¹Ÿ od DelNode, dziêki
|
|
// czemu nie zostanie usuniête ca³e poddrzewo
|
|
end else
|
|
if DelNode.Right<>nil then
|
|
begin
|
|
FTree:=DelNode.Right;
|
|
// Mechanizmy drzewa odepn¹ automatycznie ga³¹Ÿ od DelNode, dziêki
|
|
// czemu nie zostanie usuniête ca³e poddrzewo
|
|
end;
|
|
|
|
// Usuwamy element
|
|
DelNode.Free;
|
|
|
|
// Nie ma potrzeby balansowaæ drzewa, z za³o¿enie poddrzewo jest
|
|
// zbalansowane.
|
|
end else
|
|
if DelParent<>nil then
|
|
begin
|
|
// Cztery przypadki
|
|
if DelParent.Left=DelNode then
|
|
begin
|
|
if DelNode.Left<>nil then
|
|
begin
|
|
DelParent.Left:=DelNode.Left;
|
|
end else
|
|
if DelNode.Right<>nil then
|
|
begin
|
|
DelParent.Left:=DelNode.Right;
|
|
end;
|
|
end else
|
|
if DelParent.Right=DelNode then
|
|
begin
|
|
if DelNode.Left<>nil then
|
|
begin
|
|
DelParent.Right:=DelNode.Left;
|
|
end else
|
|
if DelNode.Right<>nil then
|
|
begin
|
|
DelParent.Right:=DelNode.Right;
|
|
end;
|
|
end;
|
|
|
|
DelNode.Free;
|
|
|
|
self.Ballance(DelParent);
|
|
end;
|
|
end else
|
|
// 4. Zamieniamy zawartoœæ "usuwanego" poddrzewa z jego nastêpnikiem, który
|
|
// ma tylko jedno dziecko, a nastêpnie usuwamy nastêpnik.
|
|
if (DelNode.Left<>nil) and (DelNode.Right<>nil) then
|
|
begin
|
|
// Szukamy nastêpnika
|
|
Successor:=DelNode.Right;
|
|
while Successor.Left<>nil do Successor:=Successor.Left;
|
|
SuccessorParent:=Successor.Parent;
|
|
|
|
// Przepinamy dane z nastêpnika do "usuwanego" elementu
|
|
DelNode.Clear;
|
|
if Successor.Count>0 then
|
|
for i:=0 to Successor.Count-1 do
|
|
begin
|
|
DelNode.Add(Successor.Data[i]);
|
|
end;
|
|
|
|
// Teraz usuwamy nastêpnik
|
|
InternalTreeDelete(Successor);
|
|
|
|
// Odœwie¿amy dane dotycz¹ce poddrzew
|
|
self.Ballance(SuccessorParent);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
InternalTreeDelete(self.TreeFind(ANode));
|
|
end;
|
|
|
|
function TSpkBaseXMLNode.TreeFind(ANode : TSpkXMLNode) : TBinaryTreeNode;
|
|
|
|
var Tree : TBinaryTreeNode;
|
|
i : integer;
|
|
|
|
begin
|
|
Tree:=FTree;
|
|
|
|
while (Tree<>nil) and (uppercase(Tree.Data[0].Name)<>uppercase(ANode.Name)) do
|
|
begin
|
|
if uppercase(ANode.Name)<uppercase(Tree.Data[0].Name) then
|
|
Tree:=Tree.Left else
|
|
Tree:=Tree.Right;
|
|
end;
|
|
|
|
if Tree<>nil then
|
|
begin
|
|
i:=0;
|
|
{$B-}
|
|
while (i<Tree.Count) and (Tree.Data[i]<>ANode) do inc(i);
|
|
if i=Tree.Count then result:=nil else result:=Tree;
|
|
end else result:=nil;
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.Ballance(Leaf : TBinaryTreeNode);
|
|
|
|
function CalcLeft(Node : TBinaryTreeNode) : integer;
|
|
|
|
begin
|
|
if Node.Left=nil then result:=0 else result:=1+Node.Left.SubtreeSize;
|
|
end;
|
|
|
|
function CalcRight(Node : TBinaryTreeNode) : integer;
|
|
|
|
begin
|
|
if Node.Right=nil then result:=0 else result:=1+Node.Right.SubtreeSize;
|
|
end;
|
|
|
|
begin
|
|
if Leaf<>nil then
|
|
begin
|
|
while CalcLeft(Leaf)-CalcRight(Leaf)>=2 do
|
|
Leaf:=RotateRight(Leaf);
|
|
while CalcRight(Leaf)-CalcLeft(Leaf)>=2 do
|
|
Leaf:=RotateLeft(Leaf);
|
|
self.Ballance(Leaf.Parent);
|
|
end;
|
|
end;
|
|
|
|
{ RootParent
|
|
\ / \ /
|
|
1 Root 2
|
|
/ \ / \
|
|
A 2 RotNode ~> 1 C
|
|
/ \ / \
|
|
B C A B
|
|
}
|
|
function TSpkBaseXMLNode.RotateLeft(Root : TBinaryTreeNode) : TBinaryTreeNode;
|
|
|
|
var RootParent : TBinaryTreeNode;
|
|
RotNode : TBinaryTreeNode;
|
|
|
|
begin
|
|
result:=nil;
|
|
if Root.Right=nil then
|
|
raise exception.create('Prawa podga³¹Ÿ jest pusta!');
|
|
|
|
RootParent:=Root.Parent;
|
|
RotNode:=Root.Right;
|
|
|
|
if RootParent<>nil then
|
|
begin
|
|
if Root=RootParent.Left then
|
|
begin
|
|
Root.Right:=RotNode.Left;
|
|
RotNode.Left:=Root;
|
|
RootParent.Left:=RotNode;
|
|
|
|
result:=RotNode;
|
|
end else
|
|
if Root=RootParent.Right then
|
|
begin
|
|
Root.Right:=RotNode.Left;
|
|
RotNode.Left:=Root;
|
|
RootParent.Right:=RotNode;
|
|
|
|
result:=RotNode;
|
|
end;
|
|
end else
|
|
if RootParent=nil then
|
|
begin
|
|
// Obracamy korzeñ
|
|
Root.Right:=RotNode.Left;
|
|
RotNode.Left:=Root;
|
|
FTree:=RotNode;
|
|
|
|
result:=RotNode;
|
|
end;
|
|
end;
|
|
|
|
{ RootParent
|
|
\ / \ /
|
|
Root 1 2
|
|
/ \ / \
|
|
RotNode 2 C ~> A 1
|
|
/ \ / \
|
|
A B B C
|
|
}
|
|
function TSpkBaseXMLNode.RotateRight(Root : TBinaryTreeNode) : TBinaryTreeNode;
|
|
|
|
var RootParent : TBinaryTreeNode;
|
|
RotNode : TBinaryTreeNode;
|
|
|
|
begin
|
|
result:=nil;
|
|
if Root.Left=nil then
|
|
raise exception.create('Lewa podga³¹Ÿ jest pusta!');
|
|
|
|
RootParent:=Root.Parent;
|
|
RotNode:=Root.Left;
|
|
|
|
if RootParent<>nil then
|
|
begin
|
|
if Root=RootParent.Left then
|
|
begin
|
|
Root.Left:=RotNode.Right;
|
|
RotNode.Right:=Root;
|
|
RootParent.Left:=RotNode;
|
|
|
|
result:=RotNode;
|
|
end else
|
|
if Root=RootParent.Right then
|
|
begin
|
|
Root.Left:=RotNode.Right;
|
|
RotNode.Right:=Root;
|
|
RootParent.Right:=RotNode;
|
|
|
|
result:=RotNode;
|
|
end;
|
|
end else
|
|
if RootParent=nil then
|
|
begin
|
|
// Obracamy korzeñ
|
|
Root.Left:=RotNode.Right;
|
|
RotNode.Right:=Root;
|
|
FTree:=RotNode;
|
|
|
|
result:=RotNode;
|
|
end;
|
|
end;
|
|
|
|
function TSpkBaseXMLNode.GetNodeByIndex(index : integer) : TSpkXMLNode;
|
|
|
|
begin
|
|
if (index<0) or (index>FList.count-1) then
|
|
raise exception.create('Invalid index!');
|
|
|
|
result:=TSpkXMLNode(FList[index]);
|
|
end;
|
|
|
|
function TSpkBaseXMLNode.GetNodeByName(index : string; autocreate : boolean) : TSpkXMLNode;
|
|
|
|
var Tree : TBinaryTreeNode;
|
|
XmlNode : TSpkXMLNode;
|
|
|
|
begin
|
|
Tree:=FTree;
|
|
{$B-}
|
|
while (Tree<>nil) and (uppercase(Tree.Data[0].Name)<>uppercase(index)) do
|
|
begin
|
|
if uppercase(index)<uppercase(Tree.Data[0].Name) then
|
|
Tree:=Tree.Left else
|
|
Tree:=Tree.Right;
|
|
end;
|
|
|
|
if Tree<>nil then result:=Tree.Data[0] else
|
|
begin
|
|
if not(autocreate) then
|
|
result:=nil else
|
|
begin
|
|
XmlNode:=TSpkXMLNode.create(index,xntNormal);
|
|
TreeAdd(XmlNode);
|
|
FList.add(XmlNode);
|
|
result:=XmlNode;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TSpkBaseXMLNode.GetCount : integer;
|
|
|
|
begin
|
|
result:=FList.Count;
|
|
end;
|
|
|
|
constructor TSpkBaseXMLNode.create;
|
|
|
|
begin
|
|
inherited create;
|
|
FList:=TObjectList.create;
|
|
FList.OwnsObjects:=true;
|
|
FTree:=nil;
|
|
FParent:=nil;
|
|
end;
|
|
|
|
destructor TSpkBaseXMLNode.destroy;
|
|
|
|
begin
|
|
// Drzewko zadba o rekurencyjne wyczyszczenie
|
|
FTree.free;
|
|
|
|
// Lista zadba o zwolnienie podga³êzi
|
|
FList.free;
|
|
|
|
inherited destroy;
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.Add(ANode : TSpkXMLNode);
|
|
begin
|
|
if ANode = self then
|
|
raise Exception.Create('Cannot add Self to list!');
|
|
if ANode.NodeType = xntNormal then
|
|
TreeAdd(ANode);
|
|
FList.add(ANode);
|
|
ANode.Parent := self;
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.Insert(AIndex : integer; ANode : TSpkXMLNode);
|
|
|
|
begin
|
|
if (AIndex<0) or (AIndex>FList.count-1) then
|
|
raise exception.create('Invalid index!');
|
|
|
|
FList.Insert(AIndex, ANode);
|
|
TreeAdd(ANode);
|
|
ANode.Parent:=self;
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.Delete(AIndex : integer);
|
|
|
|
begin
|
|
if (AIndex<0) or (AIndex>FList.count-1) then
|
|
raise exception.create('Invalid index');
|
|
|
|
TreeDelete(TSpkXMLNode(FList[AIndex]));
|
|
|
|
// Poniewa¿ FList.OwnsObjects, automatycznie zwolni usuwany element.
|
|
FList.delete(AIndex);
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.Remove(ANode : TSpkXMLNode);
|
|
|
|
begin
|
|
TreeDelete(ANode);
|
|
|
|
// Poniewa¿ FList.OwnsObjects, automatycznie zwolni usuwany element.
|
|
FList.Remove(ANode);
|
|
end;
|
|
|
|
function TSpkBaseXMLNode.IndexOf(ANode : TSpkXMLNode) : integer;
|
|
|
|
begin
|
|
result:=FList.IndexOf(ANode);
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.Clear;
|
|
|
|
begin
|
|
FTree.Free;
|
|
FTree:=nil;
|
|
|
|
// Poniewa¿ FList.OwnsObjects, automatycznie zwolni usuwany element.
|
|
FList.clear;
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.BeforeChildChangeName(AChild : TSpkXmlNode);
|
|
|
|
begin
|
|
TreeDelete(AChild);
|
|
end;
|
|
|
|
procedure TSpkBaseXMLNode.AfterChildChangeName(AChild : TSpkXMLNode);
|
|
|
|
begin
|
|
TreeAdd(AChild);
|
|
end;
|
|
|
|
{ TSpkXMLNode }
|
|
|
|
procedure TSpkXMLNode.SetName(Value : string);
|
|
|
|
begin
|
|
if Parent<>nil then
|
|
Parent.BeforeChildChangeName(self);
|
|
|
|
FName:=Value;
|
|
|
|
if Parent<>nil then
|
|
Parent.AfterChildChangeName(self);
|
|
end;
|
|
|
|
function TSpkXMLNode.GetTextAsInteger : integer;
|
|
|
|
begin
|
|
try
|
|
result:=StrToInt(FText);
|
|
except
|
|
raise exception.create('Cannot convert values.');
|
|
end;
|
|
end;
|
|
|
|
procedure TSpkXMLNode.SetTextAsInteger(value : integer);
|
|
|
|
begin
|
|
FText:=IntToStr(value);
|
|
end;
|
|
|
|
function TSpkXMLNode.GetTextAsExtended : extended;
|
|
|
|
begin
|
|
try
|
|
result:=StrToFloat(FText);
|
|
except
|
|
raise exception.create('Cannot convert values.');
|
|
end;
|
|
end;
|
|
|
|
procedure TSpkXMLNode.SetTextAsExtended(value : extended);
|
|
|
|
begin
|
|
FText:=FloatToStr(value);
|
|
end;
|
|
|
|
function TSpkXMLNode.GetTextAsColor : TColor;
|
|
|
|
begin
|
|
try
|
|
result:=StrToInt(FText);
|
|
except
|
|
raise exception.create('Cannot convert values.');
|
|
end;
|
|
end;
|
|
|
|
procedure TSpkXMLNode.SetTextAsColor(value : TColor);
|
|
|
|
begin
|
|
FText:=IntToStr(value);
|
|
end;
|
|
|
|
function TSpkXMLNode.GetTextAsBoolean : boolean;
|
|
|
|
begin
|
|
if (uppercase(FText)='TRUE') or (uppercase(FText)='T') or
|
|
(uppercase(FText)='YES') or (uppercase(FText)='Y') then result:=true else
|
|
if (uppercase(FText)='FALSE') or (uppercase(FText)='F') or
|
|
(uppercase(FText)='NO') or (uppercase(FText)='N') then result:=false else
|
|
raise exception.create('Cannot convert values.');
|
|
end;
|
|
|
|
procedure TSpkXMLNode.SetTextAsBoolean(value : boolean);
|
|
|
|
begin
|
|
if value then FText:='True' else FText:='False';
|
|
end;
|
|
|
|
constructor TSpkXMLNode.create(AName : string; ANodeType : TXMLNodeType);
|
|
|
|
begin
|
|
inherited create;
|
|
FName:=AName;
|
|
FText:='';
|
|
FNodeType:=ANodeType;
|
|
FParameters:=TSpkXMLParameters.create;
|
|
end;
|
|
|
|
destructor TSpkXMLNode.destroy;
|
|
|
|
begin
|
|
FParameters.free;
|
|
inherited destroy;
|
|
end;
|
|
|
|
procedure TSpkXMLNode.Clear;
|
|
|
|
begin
|
|
inherited Clear;
|
|
FParameters.Clear;
|
|
FText:='';
|
|
end;
|
|
|
|
{ TSpkXMLParser }
|
|
|
|
constructor TSpkXMLParser.create;
|
|
|
|
begin
|
|
inherited create;
|
|
end;
|
|
|
|
destructor TSpkXMLParser.destroy;
|
|
|
|
begin
|
|
inherited destroy;
|
|
end;
|
|
|
|
procedure TSpkXMLParser.Parse(input : PChar);
|
|
|
|
type // Operacja, któr¹ aktualnie wykonuje parser.
|
|
TParseOperation = (poNodes, //< Przetwarzanie (pod)ga³êzi
|
|
poTagInterior, //< Przetwarzanie wnêtrza zwyk³ego tagu (< > lub < />)
|
|
poTagText, //< Tekst taga, który przetwarzamy
|
|
poControlInterior, //< Przetwarzanie kontrolnego taga (<? ?>)
|
|
poCommentInterior, //< Przetwarzanie komentarza (<!-- -->)
|
|
poClosingInterior //< Przetwarzanie taga domykaj¹cego.
|
|
);
|
|
|
|
var // Stos przetwarzanych ga³êzi (niejawna rekurencja)
|
|
NodeStack : TObjectStack;
|
|
// Aktualna operacja. Podczas wychodzenia z operacji przetwarzaj¹cych
|
|
// tagi, domyœlnymi operacjami s¹ poSubNodes b¹dŸ poOuter.
|
|
CurrentOperation : TParseOperation;
|
|
// WskaŸnik na pocz¹tek tokena
|
|
TokenStart : PChar;
|
|
// Przetwarzana ga³¹Ÿ XMLa
|
|
Node : TSpkXMLNode;
|
|
// Pomocnicze ci¹gi znaków
|
|
s,s1 : string;
|
|
// Pozycja w pliku - linia i znak
|
|
ParseLine, ParseChar : integer;
|
|
|
|
// Funkcja inkrementuje wskaŸnik wejœcia, pilnuj¹c jednoczeœnie, by uaktualniæ
|
|
// pozycjê w pliku
|
|
procedure increment(var input : PChar; count : integer = 1);
|
|
|
|
var i : integer;
|
|
|
|
begin
|
|
for i:=1 to count do
|
|
begin
|
|
if input^=#10 then
|
|
begin
|
|
inc(ParseLine);
|
|
ParseChar:=1;
|
|
end else
|
|
if input^<>#13 then
|
|
begin
|
|
inc(ParseChar);
|
|
end;
|
|
inc(input);
|
|
end;
|
|
end;
|
|
|
|
// Funkcja przetwarza tekst (wraz z <![CDATA[ ... ]]>) a¿ do napotkanego
|
|
// delimitera. Dodatkowo zamienia encje na zwyk³e znaki.
|
|
// Niestety, natura poni¿szej funkcji powoduje, ¿e muszê doklejaæ znaki
|
|
// do ci¹gu, trac¹c na wydajnoœci.
|
|
// DoTrim powoduje, ¿e wycinane s¹ pocz¹tkowe i koñcowe bia³e znaki (chyba,
|
|
// ¿e zosta³y wpisane jako encje albo w sekcji CDATA)
|
|
function ParseText(var input : PChar; TextDelimiter : char; DoTrim : boolean = false) : string;
|
|
|
|
var Finish : boolean;
|
|
Entity : string;
|
|
i : integer;
|
|
WhiteChars : string;
|
|
|
|
// Funkcja robi dok³adnie to, na co wygl¹da ;]
|
|
function HexToInt(s : string) : integer;
|
|
|
|
var i : integer;
|
|
|
|
begin
|
|
result:=0;
|
|
for i:=1 to length(s) do
|
|
begin
|
|
result:=result*16;
|
|
if s[i] in ['0'..'9'] then result:=result+ord(s[i])-ord('0') else
|
|
if UpCase(s[i]) in ['A'..'F'] then result:=result+ord(s[i])-ord('A')+10 else
|
|
raise exception.create('Nieprawid³owa liczba heksadecymalna!');
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
result:='';
|
|
|
|
// Wycinamy pocz¹tkowe bia³e znaki
|
|
if DoTrim then
|
|
while input^ in [#32,#9,#13,#10] do increment(input);
|
|
|
|
while (input^<>TextDelimiter) or ((input^='<') and (StrLComp(input,'<![CDATA[',9)=0)) do
|
|
begin
|
|
{$B-}
|
|
|
|
// Nie mo¿e wyst¹piæ tu koniec pliku
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of file.') else
|
|
|
|
// Jeœli napotkaliœmy nawias k¹towy, mo¿e to byæ sekcja CDATA
|
|
if (input^='<') and (StrLComp(input,'<![CDATA[',9)=0) then
|
|
begin
|
|
// Wczytujemy blok CDATA a¿ do znacznika zamkniêcia "]]>"
|
|
// Pomijamy tag rozpoczynaj¹cy CDATA
|
|
increment(input,9);
|
|
|
|
Finish:=false;
|
|
repeat
|
|
{$B-}
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of file.');
|
|
if (input^=']') and (StrLComp(input,']]>',3)=0) then Finish:=true else
|
|
begin
|
|
result:=result+input^;
|
|
increment(input);
|
|
end;
|
|
until Finish;
|
|
|
|
// Pomijamy tag zamykaj¹cy CDATA
|
|
increment(input,3);
|
|
end else
|
|
|
|
// Obs³uga encji - np.
|
|
if input^='&' then
|
|
begin
|
|
// Encja
|
|
// Pomijamy znak ampersanda
|
|
increment(input);
|
|
|
|
Entity:='';
|
|
while input^<>';' do
|
|
begin
|
|
if input^=#0 then
|
|
raise Exception.Create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of file - entity not finished.');
|
|
Entity:=Entity+input^;
|
|
increment(input);
|
|
end;
|
|
|
|
// Pomijamy znak œrednika
|
|
increment(input);
|
|
|
|
// Analizujemy encjê
|
|
Entity:=uppercase(entity);
|
|
if Entity='AMP' then result:=result+'&' else
|
|
if Entity='LT' then result:=result+'<' else
|
|
if Entity='GT' then result:=result+'>' else
|
|
if Entity='QUOT' then result:=result+'"' else
|
|
if Entity='NBSP' then result:=result+' ' else
|
|
if copy(Entity,1,2)='#x' then
|
|
begin
|
|
// Kod ASCII zapisany heksadecymalnie
|
|
i:=HexToInt(copy(Entity,2,length(Entity)-1));
|
|
if not(i in [0..255]) then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid hexadecimal value of the entity (allowed: 0..255)');
|
|
result:=result+chr(i);
|
|
end else
|
|
if Entity[1]='#' then
|
|
begin
|
|
i:=StrToInt(copy(Entity,2,length(Entity)-1));
|
|
if not(i in [0..255]) then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid entity decimal value (acceptable: 0..255)');
|
|
result:=result+chr(i);
|
|
end else
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid (not supported) entity!');
|
|
end else
|
|
if (DoTrim) and (input^ in [#32,#9,#10,#13]) then
|
|
begin
|
|
// Zbieramy bia³e znaki a¿ do pierwszego niebia³ego; je¿eli bêdzie
|
|
// nim delimiter, bia³a sekwencja zostanie pominiêta.
|
|
WhiteChars:='';
|
|
repeat
|
|
WhiteChars:=input^;
|
|
increment(input);
|
|
until not(input^ in [#32,#9,#10,#13]);
|
|
|
|
// Sprawdzamy, czy dodaæ sekwencjê bia³ych znaków (ostro¿nie z CDATA!)
|
|
if (input^<>TextDelimiter) or ((input^='<') and (StrLComp(input,'<![CDATA[',9)=0)) then
|
|
result:=result+WhiteChars;
|
|
end else
|
|
// Zwyk³y znak (nie bêd¹cy delimiterem!)
|
|
if input^<>TextDelimiter then
|
|
begin
|
|
result:=result+input^;
|
|
increment(input);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
// Czyœcimy wszystkie ga³êzie
|
|
self.Clear;
|
|
|
|
// Na wszelki wypadek...
|
|
if input^=#0 then exit;
|
|
|
|
// Zerujemy parsowan¹ pozycjê
|
|
ParseLine:=1;
|
|
ParseChar:=1;
|
|
|
|
// Inicjujemy stos ga³êzi
|
|
NodeStack:=TObjectStack.Create;
|
|
CurrentOperation:=poNodes;
|
|
|
|
try
|
|
|
|
while input^<>#0 do
|
|
case CurrentOperation of
|
|
poNodes : begin
|
|
// Pomijamy bia³e znaki
|
|
while input^ in [#32,#9,#10,#13] do increment(input);
|
|
|
|
// Wejœcie mo¿e siê tu koñczyæ tylko wtedy, gdy jesteœmy
|
|
// maksymalnie na zewn¹trz
|
|
if (input^=#0) and (NodeStack.count>0) then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of file.');
|
|
|
|
if (input^<>#0) and (input^<>'<') then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid character when processing the file.');
|
|
|
|
if input^<>#0 then
|
|
if StrLComp(input,'<?',2)=0 then
|
|
CurrentOperation:=poControlInterior else
|
|
if StrLComp(input,'<!--',4)=0 then
|
|
CurrentOperation:=poCommentInterior else
|
|
if StrLComp(input,'</',2)=0 then
|
|
CurrentOperation:=poClosingInterior else
|
|
if StrLComp(input,'<',1)=0 then
|
|
CurrentOperation:=poTagInterior else
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid character when processing the file.');
|
|
end;
|
|
|
|
poTagInterior,
|
|
poControlInterior : begin
|
|
Node:=nil;
|
|
try
|
|
|
|
if CurrentOperation=poTagInterior then
|
|
begin
|
|
Node:=TSpkXMLNode.create('',xntNormal);
|
|
|
|
// Pomijamy znak otwarcia taga
|
|
increment(input);
|
|
end else
|
|
begin
|
|
Node:=TSpkXMLNode.create('',xntControl);
|
|
|
|
// Pomijamy znaki otwarcia taga
|
|
increment(input,2);
|
|
end;
|
|
|
|
// Plik nie mo¿e siê tu koñczyæ
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of file');
|
|
|
|
// Oczekujemy nazwy taga, która jest postaci
|
|
// [a-zA-Z]([a-zA-Z0-9_]|([\-:][a-zA-Z0-9_]))*
|
|
if not (input^ in ['a'..'z','A'..'Z']) then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid tag name!');
|
|
|
|
TokenStart:=input;
|
|
repeat
|
|
increment(input);
|
|
if input^ in ['-',':'] then
|
|
begin
|
|
increment(input);
|
|
if not(input^ in ['a'..'z','A'..'Z','0'..'9','_']) then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid tag name!');
|
|
increment(input);
|
|
end;
|
|
until not(input^ in ['a'..'z','A'..'Z','0'..'9','_']);
|
|
|
|
SetLength(s, PtrUInt(input)-PtrUInt(TokenStart));
|
|
StrLCopy(PChar(s),TokenStart, PtrUInt(input)-PtrUInt(TokenStart));
|
|
Node.Name:=s;
|
|
|
|
// Plik nie mo¿e siê tu koñczyæ.
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of the file!');
|
|
|
|
// Teraz bêdziemy wczytywaæ parametry (o ile takowe s¹).
|
|
repeat
|
|
// Wymagamy bia³ego znaku przed ka¿dym parametrem.
|
|
if input^ in [#32,#9,#10,#13] then
|
|
begin
|
|
// Zjadamy bia³e znaki
|
|
while input^ in [#32,#9,#10,#13] do increment(input);
|
|
|
|
// Plik nie mo¿e siê tu koñczyæ.
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of the file!');
|
|
|
|
// Je¿eli po bia³ych znakach jest litera,
|
|
// zaczynamy wczytywaæ parametr
|
|
if input^ in ['a'..'z','A'..'Z'] then
|
|
begin
|
|
// Przetwarzamy parametr
|
|
TokenStart:=input;
|
|
|
|
repeat
|
|
increment(input)
|
|
until not(input^ in ['a'..'z','A'..'Z','0'..'9','_']);
|
|
|
|
SetLength(s, {%H-}PtrUInt(input)-{%H-}PtrUInt(TokenStart));
|
|
StrLCopy(PChar(s), TokenStart, {%H-}PtrUInt(input)-{%H-}PtrUInt(TokenStart));
|
|
|
|
// Pomijamy bia³e znaki
|
|
while input^ in [#32,#9,#13,#10] do increment(input);
|
|
|
|
// Plik nie mo¿e siê tu koñczyæ
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of the file!');
|
|
|
|
// Oczekujemy znaku '='
|
|
if input^<>'=' then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Expected equality sign (probably invalid parameter name)');
|
|
|
|
increment(input);
|
|
|
|
// Pomijamy bia³e znaki
|
|
while input^ in [#32,#9,#13,#10] do increment(input);
|
|
|
|
// Plik nie mo¿e siê tu koñczyæ
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of the file!');
|
|
|
|
// Oczekujemy ' lub "
|
|
if input^='''' then
|
|
begin
|
|
// Pomijamy znak apostrofu
|
|
increment(input);
|
|
s1:=ParseText(input,'''',false);
|
|
// Pomijamy koñcz¹cy znak apostrofu
|
|
increment(input);
|
|
end else
|
|
if input^='"' then
|
|
begin
|
|
// Pomijamy znak cudzys³owu
|
|
increment(input);
|
|
s1:=ParseText(input,'"',false);
|
|
// Pomijamy koñcz¹cy znak cudzys³owu
|
|
increment(input);
|
|
end else
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+ '): Invalid character, expected " or "');
|
|
|
|
// Dodajemy parametr o nazwie s i zawartoœci s1
|
|
Node.Parameters[s,true].Value:=s1;
|
|
end;
|
|
end;
|
|
|
|
// Pêtla koñczy siê, gdy na wejœciu nie ma ju¿
|
|
// bia³ego znaku, który jest wymagany przed i
|
|
// pomiêdzy parametrami. Sekwencja bia³ych znaków
|
|
// po ostatnim parametrze zostanie pominiêta wewn¹trz
|
|
// pêtli.
|
|
until not(input^ in [#32,#9,#10,#13]);
|
|
|
|
// Plik nie mo¿e siê tu koñczyæ.
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of the file!');
|
|
|
|
if CurrentOperation=poControlInterior then
|
|
begin
|
|
if StrLComp(input,'?>',2)<>0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Incorrect closing of the control tag (should be:?>)');
|
|
|
|
// Pomijamy znaki zamkniêcia taga kontrolnego
|
|
increment(input,2);
|
|
|
|
if NodeStack.count>0 then
|
|
TSpkXMLNode(NodeStack.Peek).Add(Node) else
|
|
Self.Add(Node);
|
|
|
|
CurrentOperation:=poNodes;
|
|
end else
|
|
if CurrentOperation=poTagInterior then
|
|
begin
|
|
if StrLComp(input,'/>',2)=0 then
|
|
begin
|
|
// Pomijamy znaki zamkniêcia taga
|
|
increment(input,2);
|
|
|
|
if NodeStack.count>0 then
|
|
TSpkXMLNode(NodeStack.Peek).add(Node) else
|
|
Self.add(Node);
|
|
|
|
CurrentOperation:=poNodes;
|
|
end else
|
|
if StrLComp(input,'>',1)=0 then
|
|
begin
|
|
// Pomijamy znak zamkniêcia taga
|
|
increment(input);
|
|
|
|
NodeStack.Push(Node);
|
|
|
|
CurrentOperation:=poTagText;
|
|
end else
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Incorrect closing of the XML tag (should be:> or />)');
|
|
end;
|
|
|
|
except
|
|
// Jeœli coœ pójdzie nie tak, ga³¹Ÿ wisi w pamiêci i
|
|
// nie jest wrzucona na stos, trzeba j¹ zwolniæ.
|
|
|
|
// Notatka jest taka, ¿e wszystkie wyj¹tki, które
|
|
// mog¹ siê pojawiæ, s¹ *przed* wrzuceniem taga na
|
|
// stos lub do ga³êzi na szczycie stosu.
|
|
if Node<>nil then Node.Free;
|
|
raise;
|
|
end;
|
|
|
|
end;
|
|
|
|
poCommentInterior : begin
|
|
Node:=nil;
|
|
|
|
try
|
|
|
|
Node:=TSpkXMLNode.create('',xntComment);
|
|
|
|
// Pomijamy znaki otwarcia taga
|
|
increment(input,4);
|
|
|
|
// Wczytujemy komentarz
|
|
TokenStart:=input;
|
|
repeat
|
|
repeat
|
|
increment(input);
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of the file!');
|
|
until input^='-';
|
|
until StrLComp(input,'-->',3)=0;
|
|
|
|
setlength(s, PtrUInt(input)-PtrUInt(TokenStart));
|
|
StrLCopy(PChar(s),TokenStart, PtrUInt(input)-PtrUInt(TokenStart));
|
|
Node.Text:=s;
|
|
|
|
// Pomijamy znaki zakoñczenia komentarza
|
|
increment(input,3);
|
|
|
|
if NodeStack.count>0 then
|
|
TSpkXMLNode(NodeStack.Peek).add(Node) else
|
|
Self.add(Node);
|
|
|
|
except
|
|
// Zarz¹dzanie pamiêci¹ - zobacz poprzedni przypadek
|
|
if Node<>nil then Node.free;
|
|
raise
|
|
end;
|
|
|
|
CurrentOperation:=poNodes;
|
|
end;
|
|
|
|
poClosingInterior : begin
|
|
// Pomijamy znaki otwieraj¹ce zamykaj¹cy tag
|
|
increment(input,2);
|
|
|
|
// Plik nie mo¿e siê tu koñczyæ
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of the file!');
|
|
|
|
// Wczytujemy nazwê zamykanego taga postaci
|
|
// [a-zA-Z]([a-zA-Z0-9_]|([\-:][a-zA-Z0-9_]))*
|
|
if not(input^ in ['a'..'z','A'..'Z']) then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid tag name!');
|
|
|
|
TokenStart:=input;
|
|
repeat
|
|
increment(input);
|
|
if input^ in ['-',':'] then
|
|
begin
|
|
increment(input);
|
|
if not(input^ in ['a'..'z','A'..'Z','0'..'9','_']) then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Invalid tag name!');
|
|
increment(input);
|
|
end;
|
|
until not(input^ in ['a'..'z','A'..'Z','0'..'9','_']);
|
|
|
|
SetLength(s, PtrUInt(input)-PtrUInt(TokenStart));
|
|
StrLCopy(PChar(s),TokenStart, PtrUInt(input)-PtrUInt(TokenStart));
|
|
|
|
// Pomijamy zbêdne znaki bia³e
|
|
while input^ in [#32,#9,#10,#13] do increment(input);
|
|
|
|
// Plik nie mo¿e siê tu koñczyæ
|
|
if input^=#0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of the file!');
|
|
|
|
// Oczekujemy znaku '>'
|
|
if input^<>'>' then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Expected tag closing (>)');
|
|
|
|
// Pomijamy znak zamkniêcia taga
|
|
increment(input);
|
|
|
|
// Sprawdzamy, czy uppercase nazwa taga na stosie i
|
|
// wczytana pasuj¹ do siebie
|
|
if NodeStack.Count=0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): The opening tag is not closed!');
|
|
|
|
if uppercase(s)<>uppercase(TSpkXMLNode(NodeStack.Peek).Name) then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): The closing tag (' + s + ') does not match the opening tag ('+TSpkXMLNode(NodeStack.Peek).Name+') !');
|
|
|
|
// Wszystko OK, zdejmujemy tag ze stosu i dodajemy go do taga pod nim
|
|
Node:=TSpkXMLNode(NodeStack.Pop);
|
|
|
|
if NodeStack.count>0 then
|
|
TSpkXMLNode(NodeStack.Peek).add(Node) else
|
|
Self.add(Node);
|
|
|
|
CurrentOperation:=poNodes;
|
|
end;
|
|
|
|
poTagText : begin
|
|
// Wczytujemy tekst i przypisujemy go do taga znajduj¹cego
|
|
// siê na szczycie stosu
|
|
s:=ParseText(input,'<',true);
|
|
|
|
if NodeStack.Count=0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): The text can only be inside tags!');
|
|
|
|
TSpkXMLNode(NodeStack.Peek).Text:=s;
|
|
|
|
CurrentOperation:=poNodes;
|
|
end;
|
|
end;
|
|
|
|
// Jeœli na stosie pozosta³y jakieœ ga³êzie - oznacza to b³¹d (nie zosta³y
|
|
// domkniête)
|
|
|
|
if NodeStack.Count>0 then
|
|
raise exception.create('Error in XML syntax (line '+IntToStr(ParseLine)+', character '+IntToStr(ParseChar)+'): Unexpected end of file (there are unclosed tags, the first of them is '+TSpkXMLNode(NodeStack.Peek).Name+')');
|
|
|
|
// Wszystko w porz¹dku, XML zosta³ wczytany.
|
|
finally
|
|
|
|
// Czyœcimy nie przetworzone ga³êzie
|
|
while NodeStack.Count>0 do
|
|
NodeStack.Pop.Free;
|
|
NodeStack.Free;
|
|
|
|
end;
|
|
|
|
end;
|
|
|
|
function TSpkXMLParser.Generate(UseFormatting : boolean) : string;
|
|
|
|
function InternalGenerate(RootNode : TSpkXMLNode; indent : integer; UseFormatting : boolean) : string;
|
|
|
|
var i : integer;
|
|
|
|
function MkIndent(i : integer) : string;
|
|
|
|
begin
|
|
result:='';
|
|
if indent<=0 then exit;
|
|
setlength(result,i);
|
|
if i>0 then
|
|
FillChar(result[1],i,32);
|
|
end;
|
|
|
|
function MkText(AText : string; CheckWhitespace : boolean = false) : string;
|
|
|
|
var s : string;
|
|
prefix,postfix : string;
|
|
|
|
begin
|
|
s:=AText;
|
|
s:=StringReplace(s,'&','&',[rfReplaceAll]);
|
|
s:=StringReplace(s,'<','<',[rfReplaceAll]);
|
|
s:=StringReplace(s,'>','>',[rfReplaceAll]);
|
|
s:=StringReplace(s,'"','"',[rfReplaceAll]);
|
|
s:=StringReplace(s,'''',''',[rfReplaceAll]);
|
|
|
|
prefix:='';
|
|
postfix:='';
|
|
|
|
if CheckWhitespace then
|
|
begin
|
|
// Jeœli pierwszy znak jest bia³y, zamieñ go na encjê
|
|
if s[1]=#32 then
|
|
begin
|
|
System.delete(s,1,1);
|
|
prefix:=' ';
|
|
end else
|
|
if s[1]=#9 then
|
|
begin
|
|
System.delete(s,1,1);
|
|
prefix:='	';
|
|
end else
|
|
if s[1]=#10 then
|
|
begin
|
|
System.delete(s,1,1);
|
|
prefix:=' ';
|
|
{$B-}
|
|
if (length(s)>0) and (s[1]=#13) then
|
|
begin
|
|
System.delete(s,1,1);
|
|
prefix:=prefix+' ';
|
|
end;
|
|
end else
|
|
if s[1]=#13 then
|
|
begin
|
|
System.delete(s,1,1);
|
|
prefix:=' ';
|
|
{$B-}
|
|
if (length(s)>0) and (s[1]=#10) then
|
|
begin
|
|
System.delete(s,1,1);
|
|
prefix:=prefix+' ';
|
|
end;
|
|
end;
|
|
|
|
// Jeœli ostatni znak jest bia³y, zamieñ go na encjê
|
|
if length(s)>0 then
|
|
begin
|
|
if s[length(s)]=#32 then
|
|
begin
|
|
System.delete(s,length(s),1);
|
|
postfix:=' ';
|
|
end else
|
|
if s[length(s)]=#9 then
|
|
begin
|
|
System.delete(s,length(s),1);
|
|
postfix:=' ';
|
|
end else
|
|
if s[length(s)]=#10 then
|
|
begin
|
|
System.Delete(s,length(s),1);
|
|
postfix:=' ';
|
|
if (length(s)>0) and (s[length(s)]=#13) then
|
|
begin
|
|
System.Delete(s,length(s),1);
|
|
postfix:=' '+postfix;
|
|
end;
|
|
end else
|
|
if s[length(s)]=#13 then
|
|
begin
|
|
System.Delete(s,length(s),1);
|
|
postfix:=' ';
|
|
if (length(s)>0) and (s[length(s)]=#10) then
|
|
begin
|
|
System.Delete(s,length(s),1);
|
|
postfix:=' '+postfix;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
result:=prefix+s+postfix;
|
|
end;
|
|
|
|
begin
|
|
result:='';
|
|
if RootNode=nil then
|
|
begin
|
|
if FList.count>0 then
|
|
for i:=0 to FList.count-1 do
|
|
result:=result+InternalGenerate(TSpkXMLNode(FList[i]),0,UseFormatting);
|
|
end else
|
|
begin
|
|
// Generowanie XMLa dla pojedynczej ga³êzi
|
|
case RootNode.NodeType of
|
|
xntNormal : begin
|
|
if UseFormatting then
|
|
result:=MkIndent(indent)+'<'+RootNode.name else
|
|
result:='<'+RootNode.name;
|
|
|
|
if RootNode.Parameters.count>0 then
|
|
for i:=0 to RootNode.Parameters.count-1 do
|
|
result:=result+' '+RootNode.Parameters.ParamByIndex[i].name+'="'+MkText(RootNode.Parameters.ParamByIndex[i].value,false)+'"';
|
|
|
|
if (RootNode.Count=0) and (RootNode.Text='') then
|
|
begin
|
|
if UseFormatting then
|
|
result:=result+'/>'+CRLF else
|
|
result:=result+'/>';
|
|
end else
|
|
if (RootNode.Count=0) and (RootNode.Text<>'') then
|
|
begin
|
|
result:=result+'>';
|
|
result:=result+MkText(RootNode.Text,true);
|
|
if UseFormatting then
|
|
result:=result+'</'+RootNode.Name+'>'+CRLF else
|
|
result:=result+'</'+RootNode.Name+'>';
|
|
end else
|
|
if (RootNode.Count>0) and (RootNode.Text='') then
|
|
begin
|
|
if UseFormatting then
|
|
result:=result+'>'+CRLF else
|
|
result:=result+'>';
|
|
for i:=0 to RootNode.count-1 do
|
|
result:=result+InternalGenerate(RootNode.NodeByIndex[i],indent+2,UseFormatting);
|
|
|
|
if UseFormatting then
|
|
result:=result+MkIndent(indent)+'</'+RootNode.name+'>'+CRLF else
|
|
result:=result+'</'+RootNode.name+'>';
|
|
end else
|
|
if (RootNode.Count>0) and (RootNode.Text<>'') then
|
|
begin
|
|
result:=result+'>';
|
|
if UseFormatting then
|
|
result:=result+MkText(RootNode.Text,true)+CRLF else
|
|
result:=result+MkText(RootNode.Text,true);
|
|
|
|
for i:=0 to RootNode.count-1 do
|
|
result:=result+InternalGenerate(RootNode.NodeByIndex[i],indent+2,UseFormatting);
|
|
|
|
if UseFormatting then
|
|
result:=result+MkIndent(indent)+'</'+RootNode.Name+'>'+CRLF else
|
|
result:=result+'</'+RootNode.Name+'>';
|
|
end;
|
|
end;
|
|
xntControl : begin
|
|
if UseFormatting then
|
|
result:=MkIndent(indent)+'<?'+RootNode.Name else
|
|
result:='<?'+RootNode.Name;
|
|
if RootNode.Parameters.count>0 then
|
|
for i:=0 to RootNode.Parameters.count-1 do
|
|
result:=result+' '+RootNode.Parameters.ParamByIndex[i].name+'="'+MkText(RootNode.Parameters.ParamByIndex[i].value,false)+'"';
|
|
|
|
if UseFormatting then
|
|
result:=result+'?>'+CRLF else
|
|
result:=result+'?>';
|
|
end;
|
|
xntComment : begin
|
|
if UseFormatting then
|
|
result:=MkIndent(indent)+'<!--'+RootNode.text+'-->'+CRLF else
|
|
result:='<!--'+RootNode.text+'-->';
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
result:=InternalGenerate(nil,0,UseFormatting);
|
|
end;
|
|
|
|
procedure TSpkXMLParser.LoadFromFile(AFile : string);
|
|
var
|
|
sl : TStringList;
|
|
begin
|
|
sl:=TStringList.create;
|
|
try
|
|
sl.LoadFromFile(AFile);
|
|
if length(sl.text)>0 then
|
|
self.Parse(PChar(sl.text));
|
|
finally
|
|
sl.free;
|
|
end;
|
|
end;
|
|
|
|
procedure TSpkXMLParser.SaveToFile(AFile : string; UseFormatting : boolean);
|
|
var
|
|
sl: TStringList;
|
|
begin
|
|
sl:=TStringList.create;
|
|
try
|
|
sl.text:=self.Generate(UseFormatting);
|
|
sl.savetofile(AFile);
|
|
finally
|
|
sl.free;
|
|
end;
|
|
end;
|
|
|
|
procedure TSpkXMLParser.LoadFromStream(AStream : TStream);
|
|
var
|
|
sl: TStringList;
|
|
begin
|
|
sl:=TStringList.create;
|
|
try
|
|
sl.LoadFromStream(AStream);
|
|
self.Parse(PChar(sl.text));
|
|
finally
|
|
sl.free;
|
|
end;
|
|
end;
|
|
|
|
procedure TSpkXMLParser.SaveToStream(AStream : TStream; UseFormatting : boolean);
|
|
var
|
|
sl: TStringList;
|
|
begin
|
|
sl:=TStringList.create;
|
|
try
|
|
sl.text:=self.Generate(UseFormatting);
|
|
sl.savetostream(AStream);
|
|
finally
|
|
sl.free;
|
|
end;
|
|
end;
|
|
|
|
end.
|