From 8e56c66be7bc143e5d96b832f8c3f1d0f444b7d4 Mon Sep 17 00:00:00 2001 From: blikblum Date: Sat, 18 Jun 2011 15:51:22 +0000 Subject: [PATCH] * Initial import of SpkXml git-svn-id: https://svn.code.sf.net/p/lazarus-ccr/svn@1704 8e941d3f-bd1b-0410-a28a-d453659cc2b4 --- components/spktoolbar/SpkXML/SpkXMLIni.pas | 561 +++++ components/spktoolbar/SpkXML/SpkXMLParser.pas | 2051 +++++++++++++++++ components/spktoolbar/SpkXML/SpkXMLTools.pas | 116 + 3 files changed, 2728 insertions(+) create mode 100644 components/spktoolbar/SpkXML/SpkXMLIni.pas create mode 100644 components/spktoolbar/SpkXML/SpkXMLParser.pas create mode 100644 components/spktoolbar/SpkXML/SpkXMLTools.pas diff --git a/components/spktoolbar/SpkXML/SpkXMLIni.pas b/components/spktoolbar/SpkXML/SpkXMLIni.pas new file mode 100644 index 000000000..e1edf4ef1 --- /dev/null +++ b/components/spktoolbar/SpkXML/SpkXMLIni.pas @@ -0,0 +1,561 @@ +unit SpkXMLIni; + +{$DEFINE SPKXMLINI} + +interface + +uses SpkXMLParser, classes, sysutils; + +type TSpkXMLIni = class(TObject) + private + FParser : TSpkXMLParser; + FAutoConvert : boolean; + protected + public + constructor Create; overload; + constructor Create(filename : string); overload; + destructor Destroy; override; + procedure LoadFromFile(filename : string); + procedure SaveToFile(filename : string); + procedure SaveToStream(AStream : TStream); + procedure LoadFromStream(AStream : TStream); + procedure Clear; + procedure DeleteKey(const Section, Ident: string); + procedure EraseSection(const Section: string); + function ReadString(const Section, Ident, Default: string): string; + procedure WriteString(const Section, Ident, Value: string); + function ReadBool (const Section, Ident: String; Default: Boolean): Boolean; + function ReadDate (const Section, Ident: string; Default: TDateTime): TDateTime; + function ReadDateTime (const Section, Ident: String; Default: TDateTime): TDateTime; + function ReadFloat (const Section, Ident: String; Default: Double): Double; + function ReadInteger(const Section, Ident: String; Default: Longint): Longint; + function ReadTime (const Section, Ident: String; Default: TDateTime): TDateTime; + function SectionExists (const Section: String): Boolean; + procedure WriteBool(const Section, Ident: String; Value: Boolean); + procedure WriteDate(const Section, Ident: String; Value: TDateTime); + procedure WriteDateTime(const Section, Ident: String; Value: TDateTime); + procedure WriteFloat(const Section, Ident: String; Value: Double); + procedure WriteInteger(const Section, Ident: String; Value: Longint); + procedure WriteTime(const Section, Ident: String; Value: TDateTime); + function ValueExists(const section, ident : string) : boolean; + procedure WriteStrings(const Section, Ident : String; Value : TStrings); + procedure ReadStrings(const Section, Ident : String; Target : TStrings); + procedure ReadSection (const Section: string; Strings: TStrings); + procedure ReadSections(Strings: TStrings); + procedure ReadSectionValues(const Section: string; Strings: TStrings); + + property AutoConvert : boolean read FAutoConvert write FAutoConvert; + end; + +implementation + +{ TSpkXMLIni } + +constructor TSpkXMLIni.create; + +begin + inherited create; + FParser:=TSpkXMLParser.create; + FAutoConvert:=true; +end; + +constructor TSpkXMLIni.create(filename : string); + +begin +inherited create; +self.LoadFromFile(filename); +end; + +destructor TSpkXMLIni.destroy; + +begin + FParser.free; + inherited; +end; + +procedure TSpkXMLIni.LoadFromFile(filename : string); + +begin +try +FParser.LoadFromFile(filename); +except +self.clear; +end; +end; + +procedure TSpkXMLIni.LoadFromStream(AStream: TStream); +begin +FParser.LoadFromStream(AStream); +end; + +procedure TSpkXMLIni.SaveToFile(filename : string); + +begin +FParser.SaveToFile(filename); +end; + +procedure TSpkXMLIni.SaveToStream(AStream: TStream); +begin +FParser.SaveToStream(AStream); +end; + +procedure TSpkXMLIni.Clear; + +begin +FParser.Clear; +end; + +procedure TSpkXMLIni.DeleteKey(const Section, Ident: string); + +var node : TSpkXMLNode; + subnode : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node<>nil then + begin + subnode:=node.NodeByName[Ident,false]; + if subnode<>nil then + begin + node.delete(node.IndexOf(subnode)); + end; + end; +end; + +procedure TSpkXMLIni.EraseSection(const Section: string); + +var node : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node<>nil then + Fparser.Delete(FParser.IndexOf(node)); +end; + +function TSpkXMLIni.ReadString(const Section, Ident, Default: string): string; + +var node, subnode : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node=nil then result:=Default else + begin + subnode:=node.NodeByName[Ident,false]; + if subnode=nil then result:=Default else + begin + if subnode.Parameters.ParamByName['type',false]<>nil then + begin + if uppercase(subnode.Parameters.ParamByName['type',false].Value)='STRING' then + result:=subnode.text else + begin + if FAutoConvert then + try + result:=subnode.text; + except + result:=Default; + end else raise exception.create('Invalid object type!'); + end; + end else result:=subnode.Text; + end; + end; +end; + +procedure TSpkXMLIni.WriteString(const Section, Ident, Value: string); + +begin +self.DeleteKey(Section,Ident); +FParser.NodeByName[Section,true].NodeByName[Ident,true].Parameters.ParamByName['type',true].value:='string'; +FParser.NodeByName[Section,true].NodeByName[Ident,true].Text:=Value; +end; + +function TSpkXMLIni.ReadBool (const Section, Ident: String; Default: Boolean): Boolean; + +var node, subnode : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node=nil then result:=Default else + begin + subnode:=node.NodeByName[Ident,false]; + if subnode=nil then result:=Default else + begin + if subnode.Parameters.ParamByName['type',false]<>nil then + begin + if uppercase(subnode.Parameters.ParamByName['type',false].Value)='BOOLEAN' then + begin + if (uppercase(subnode.text)='TRUE') or (subnode.text='1') then result:=true else result:=false; + end else + begin + if FAutoConvert then + try + if (uppercase(subnode.text)='TRUE') or (subnode.text='1') then result:=true else result:=false; + except + result:=Default; + end else raise exception.create('Invalid object type!'); + end; + end else + try + if (uppercase(subnode.text)='TRUE') or (subnode.text='1') then result:=true else result:=false; + except + result:=Default; + end; + end; + end; +end; + +function TSpkXMLIni.ReadDate (const Section, Ident: string; Default: TDateTime): TDateTime; + +var node, subnode : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node=nil then result:=Default else + begin + subnode:=node.NodeByName[Ident,false]; + if subnode=nil then result:=Default else + begin + if subnode.Parameters.ParamByName['type',false]<>nil then + begin + if uppercase(subnode.Parameters.ParamByName['type',false].Value)='DATE' then + begin + try + result:=StrToDate(subnode.text); + except + result:=Default; + end; + end else + begin + if FAutoConvert then + try + result:=StrToDate(subnode.text); + except + result:=Default; + end else raise exception.create('Invalid object type!'); + end; + end else + try + result:=StrToDate(subnode.text); + except + result:=Default; + end; + end; + end; +end; + +function TSpkXMLIni.ReadDateTime (const Section, Ident: String; Default: TDateTime): TDateTime; + +var node, subnode : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node=nil then result:=Default else + begin + subnode:=node.NodeByName[Ident,false]; + if subnode=nil then result:=Default else + begin + if subnode.Parameters.ParamByName['type',false]<>nil then + begin + if uppercase(subnode.Parameters.ParamByName['type',false].Value)='DATETIME' then + begin + try + result:=StrToDateTime(subnode.text); + except + result:=Default; + end; + end else + begin + if FAutoConvert then + try + result:=StrToDateTime(subnode.text); + except + result:=Default; + end else raise exception.create('Invalid object type!'); + end; + end else + try + result:=StrToDateTime(subnode.text); + except + result:=Default; + end; + end; + end; +end; + +function TSpkXMLIni.ReadFloat (const Section, Ident: String; Default: Double): Double; + +var node, subnode : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node=nil then result:=Default else + begin + subnode:=node.NodeByName[Ident,false]; + if subnode=nil then result:=Default else + begin + if subnode.Parameters.ParamByName['type',false]<>nil then + begin + if uppercase(subnode.Parameters.ParamByName['type',false].Value)='FLOAT' then + begin + try + result:=StrToFloat(subnode.text); + except + result:=Default; + end; + end else + begin + if FAutoConvert then + try + result:=StrToFloat(subnode.text); + except + result:=Default; + end else raise exception.create('Invalid object type!'); + end; + end else + try + result:=StrToFloat(subnode.text); + except + result:=Default; + end; + end; + end; +end; + +function TSpkXMLIni.ReadInteger(const Section, Ident: String; Default: Longint): Longint; + +var node, subnode : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node=nil then result:=Default else + begin + subnode:=node.NodeByName[Ident,false]; + if subnode=nil then result:=Default else + begin + if subnode.Parameters.ParamByName['type',false]<>nil then + begin + if uppercase(subnode.Parameters.ParamByName['type',false].Value)='FLOAT' then + begin + try + result:=StrToInt(subnode.text); + except + result:=Default; + end; + end else + begin + if FAutoConvert then + try + result:=StrToInt(subnode.text); + except + result:=Default; + end else raise exception.create('Invalid object type!'); + end; + end else + try + result:=StrToInt(subnode.text); + except + result:=Default; + end; + end; + end; +end; + +function TSpkXMLIni.ReadTime (const Section, Ident: String; Default: TDateTime): TDateTime; + +var node, subnode : TSpkXMLNode; + +begin +node:=FParser.NodeByName[Section,false]; +if node=nil then result:=Default else + begin + subnode:=node.NodeByName[Ident,false]; + if subnode=nil then result:=Default else + begin + if subnode.Parameters.ParamByName['type',false]<>nil then + begin + if uppercase(subnode.Parameters.ParamByName['type',false].Value)='TIME' then + begin + try + result:=StrToTime(subnode.text); + except + result:=Default; + end; + end else + begin + if FAutoConvert then + try + result:=StrToTime(subnode.text); + except + result:=Default; + end else raise exception.create('Invalid object type!'); + end; + end else + try + result:=StrToTime(subnode.text); + except + result:=Default; + end; + end; + end; +end; + +function TSpkXMLIni.SectionExists (const Section: String): Boolean; + +begin +result:=FParser.NodeByName[Section,false]<>nil; +end; + +procedure TSpkXMLIni.WriteBool(const Section, Ident: String; Value: Boolean); + +begin +self.DeleteKey(Section,Ident); +FParser.NodeByName[Section,true].NodeByName[Ident,true].Parameters.ParamByName['type',true].Value:='boolean'; +if Value then FParser.NodeByName[Section,true].NodeByName[Ident,true].Text:='true' else + FParser.NodeByName[Section,true].NodeByName[Ident,true].text:='false'; +end; + +procedure TSpkXMLIni.WriteDate(const Section, Ident: String; Value: TDateTime); + +begin +self.DeleteKey(Section,Ident); +FParser.NodeByName[Section,true].NodeByName[Ident,true].Parameters.ParamByName['type',true].Value:='date'; +FParser.NodeByName[Section,true].NodeByName[Ident,true].Text:=DateToStr(Value); +end; + +procedure TSpkXMLIni.WriteDateTime(const Section, Ident: String; Value: TDateTime); + +begin +self.DeleteKey(Section,Ident); +FParser.NodeByName[Section,true].NodeByName[Ident,true].Parameters.ParamByName['type',true].Value:='datetime'; +FParser.NodeByName[Section,true].NodeByName[Ident,true].Text:=DateTimeToStr(Value); +end; + +procedure TSpkXMLIni.WriteFloat(const Section, Ident: String; Value: Double); + +begin +self.DeleteKey(Section,Ident); +FParser.NodeByName[Section,true].NodeByName[Ident,true].Parameters.ParamByName['type',true].Value:='float'; +FParser.NodeByName[Section,true].NodeByName[Ident,true].Text:=FloatToStr(Value); +end; + +procedure TSpkXMLIni.WriteInteger(const Section, Ident: String; Value: Longint); + +begin +self.DeleteKey(Section,Ident); +FParser.NodeByName[Section,true].NodeByName[Ident,true].Parameters.ParamByName['type',true].Value:='integer'; +FParser.NodeByName[Section,true].NodeByName[Ident,true].Text:=IntToStr(Value); +end; + +procedure TSpkXMLIni.WriteTime(const Section, Ident: String; Value: TDateTime); + +begin +self.DeleteKey(Section,Ident); +FParser.NodeByName[Section,true].NodeByName[Ident,true].Parameters.ParamByName['type',true].Value:='time'; +FParser.NodeByName[Section,true].NodeByName[Ident,true].Text:=TimeToStr(Value); +end; + +function TSpkXMLIni.ValueExists(const section, ident : string) : boolean; + +begin +result:=FParser.NodeByName[section,false]<>nil; +if result then + result:=result and (FParser.NodeByName[section,false].NodeByName[ident,false]<>nil); +end; + +procedure TSpkXMLIni.WriteStrings(const Section, Ident : String; Value : TStrings); + +var node,subnode : TSpkXMLNode; + i : integer; + +begin +self.DeleteKey(Section,Ident); +node:=FParser.NodeByName[Section,true]; +subnode:=node.NodeByName[ident,true]; +subnode.Parameters.ParamByName['type',true].value:='strings'; +subnode.parameters.parambyname['count',true].value:=IntToStr(Value.count); +for i:=0 to value.count-1 do + begin + subnode.NodeByName['line'+IntToStr(i),true].text:=Value[i]; + end; +end; + +procedure TSpkXMLIni.ReadStrings(const Section, Ident : String; Target : TStrings); + +var node, subnode, line : TSpkXMLNode; + i,count : integer; + +begin +target.clear; + +node:=FParser.NodeByName[Section,false]; +if node=nil then exit; + +subnode:=node.NodeByName[ident,false]; +if subnode=nil then exit; + +if subnode.Parameters.ParamByName['type',false]=nil then exit; +if uppercase(subnode.Parameters.ParamByName['type',false].value)<>'STRINGS' then exit; + +if subnode.parameters.parambyname['count',false]=nil then exit; + +try +count:=StrToInt(subnode.parameters.parambyname['count',false].Value); +except +exit +end; + +for i:=0 to count-1 do + begin + line:=subnode.NodeByName['line'+IntToStr(i),false]; + if line=nil then + begin + target.Clear; + exit; + end; + target.Add(line.Text); + end; +end; + +procedure TSpkXMLIni.ReadSection(const Section: string; Strings: TStrings); + +var i : integer; + node : TSpkXMLNode; + +begin +if FParser.NodeByName[Section,false]=nil then exit; +node:=FParser.NodeByName[Section,false]; +if node.Count=0 then exit; +for i:=0 to node.Count-1 do + Strings.Add(node.NodeByIndex[i].Name); +end; + +procedure TSpkXMLIni.ReadSections(Strings: TStrings); + +var i : integer; + +begin +if FParser.count=0 then exit; +for i:=0 to FParser.count-1 do + Strings.add(FParser.NodeByIndex[i].Name); +end; + +procedure TSpkXMLIni.ReadSectionValues(const Section: string; Strings: TStrings); + +var i : integer; + node : TSpkXMLNode; + +begin +if FParser.NodeByName[Section,false]=nil then exit; +node:=FParser.NodeByName[Section,false]; +if node.Count=0 then exit; +for i:=0 to node.count-1 do + begin + {$I-} + if (node.NodeByIndex[i].Parameters.ParamByName['type',false]<>nil) and + (uppercase(node.NodeByIndex[i].Parameters.ParamByName['type',false].Value)='STRINGS') then + Strings.add('[TStrings]') + else + Strings.add(node.NodeByIndex[i].Text); + end; +end; + +end. diff --git a/components/spktoolbar/SpkXML/SpkXMLParser.pas b/components/spktoolbar/SpkXML/SpkXMLParser.pas new file mode 100644 index 000000000..b9b27aaf6 --- /dev/null +++ b/components/spktoolbar/SpkXML/SpkXMLParser.pas @@ -0,0 +1,2051 @@ +unit SpkXMLParser; + +{$DEFINE SPKXMLPARSER} + +interface + +{TODO Uporządkować widoczność i wirtualność metod i własności} + +// Notatki: Stosuję konsekwentnie case-insensitivity + +uses SysUtils, Classes, ContNrs, Graphics, Math; + +const CRLF=#13#10; + +type // Rodzaj gałęzi XML + TXMLNodeType = (xntNormal, xntControl, xntComment); + +type // Forward dla klasy gałęzi XML + TSpkXMLNode = class; + + TBinaryTreeNode = class; + + // Gałąź drzewa binarnych przeszukiwań + TBinaryTreeNode = class(TObject) + private + // Lewe poddrzewo + FLeft, + // Prawe poddrzewo + FRight, + // Rodzic + FParent : TBinaryTreeNode; + // Dane zawarte w węźle + FData : array of TSpkXMLNode; + // Wysokość poddrzewa + FSubtreeSize : integer; + protected + // *** Metody dotyczące drzewa *** + + // Setter dla lewego poddrzewa + 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; + +procedure TBinaryTreeNode.DetachFromParent; + +begin +// Zgodnie z założeniami, metodę tą może zawołać tylko obecny parent. +FParent:=nil; +end; + +procedure TBinaryTreeNode.AttachToParent(AParent : TBinaryTreeNode); + +begin +// Zgodnie z założeniami, tą metodą wywołuje nowy parent elementu. Element +// musi zadbać o to, by poinformować poprzedniego parenta o tym, że jest on +// odpinany. +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('Pojedyncza gałąź przechowuje dane o jednakowych nazwach!'); +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 ihigh(FData)) then + raise exception.create('Nieprawidłowy indeks.'); + +if indexFList.count-1) then + raise exception.create('Nieprawidłowy indeks.'); + +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('Nieprawidłowy indeks parametru.'); + +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('Nieprawidłowy indeks elementu.'); + +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)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)1 then + begin + i:=0; + while (iANode) 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)nil then + begin + i:=0; + {$B-} + while (iANode) 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('Nieprawidłowy indeks!'); + +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)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('Nie mogę dodać siebie do własnej listy!'); +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('Nieprawidłowy indeks!'); + +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('Nieprawidłowy indeks!'); + +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('Nie mogę przekonwertować wartości.'); +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('Nie mogę przekonwertować wartości.'); +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('Nie mogę przekonwertować wartości.'); +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('Nie mogę przekonwertować wartości.'); +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 ) 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,'" + // Pomijamy tag rozpoczynający CDATA + increment(input,9); + + Finish:=false; + repeat + {$B-} + if input^=#0 then + raise exception.create('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku.'); + 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku - nie dokończona encja.'); + 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowa wartość heksadecymalna encji (dopuszczalne: 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowa wartość dziesiętna encji (dopuszczalne: 0..255)'); + result:=result+chr(i); + end else + raise exception.create('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowa (nie obsługiwana) encja!'); + 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,'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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku.'); + + if (input^<>#0) and (input^<>'<') then + raise exception.create('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowy znak podczas przetwarzania pliku.'); + + if input^<>#0 then + if StrLComp(input,''=' then + raise exception.create('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Oczekiwany znak równości (prawdopodobnie nieprawidłowa nazwa parametru)'); + + 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku!'); + + // 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowy znak, oczekiwano '' lub "'); + + // 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku!'); + + if CurrentOperation=poControlInterior then + begin + if StrLComp(input,'?>',2)<>0 then + raise exception.create('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowe domknięcie taga kontrolnego (powinno być: ?>)'); + + // 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowe domknięcie taga XML (powinno być: > lub />)'); + 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku!'); + until input^='-'; + until StrLComp(input,'-->',3)=0; + + setlength(s,integer(input)-integer(TokenStart)); + StrLCopy(PChar(s),TokenStart,integer(input)-integer(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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku!'); + + // 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowa nazwa taga!'); + + 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieprawidłowa nazwa taga!'); + increment(input); + end; + until not(input^ in ['a'..'z','A'..'Z','0'..'9','_']); + + setlength(s,integer(input)-integer(TokenStart)); + StrLCopy(PChar(s),TokenStart,integer(input)-integer(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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku!'); + + // Oczekujemy znaku '>' + if input^<>'>' then + raise exception.create('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Oczekiwany znak zamknięcia taga (>)'); + + // 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Brakuje taga otwierającego do zamykającego!'); + + if uppercase(s)<>uppercase(TSpkXMLNode(NodeStack.Peek).Name) then + raise exception.create('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Tag zamykający ('+s+') nie pasuje do taga otwierającego ('+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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Tekst może występować tylko wewnątrz tagów!'); + + 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('Błąd w składni XML (linia '+IntToStr(ParseLine)+', znak '+IntToStr(ParseChar)+') : Nieoczekiwany koniec pliku (istnieją nie domknięte tagi, pierwszy z nich: '+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+''+CRLF else + result:=result+''; + 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)+''+CRLF else + result:=result+''; + 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)+''+CRLF else + result:=result+''; + end; + end; + xntControl : begin + if UseFormatting then + result:=MkIndent(indent)+'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)+''+CRLF else + result:=''; + end; + end; + end; + end; + +begin +result:=InternalGenerate(nil,0,UseFormatting); +end; + +procedure TSpkXMLParser.LoadFromFile(AFile : string); + +var sl : TStringList; + +begin +sl:=nil; +try +sl:=TStringList.create; +sl.LoadFromFile(AFile); + +if length(sl.text)>0 then + self.Parse(PChar(sl.text)); + +finally +if sl<>nil then sl.free; +end; +end; + +procedure TSpkXMLParser.SaveToFile(AFile : string; UseFormatting : boolean); + +var sl : TStringList; + +begin +sl:=nil; +try +sl:=TStringList.create; + +sl.text:=self.Generate(UseFormatting); + +sl.savetofile(AFile); + +finally +if sl<>nil then sl.free; +end; +end; + +procedure TSpkXMLParser.LoadFromStream(AStream : TStream); + +var sl : TStringList; + +begin +sl:=nil; +try +sl:=TStringList.create; +sl.LoadFromStream(AStream); + +self.Parse(PChar(sl.text)); + +finally +if sl<>nil then sl.free; +end; +end; + +procedure TSpkXMLParser.SaveToStream(AStream : TStream; UseFormatting : boolean); + +var sl : TStringList; + +begin +sl:=nil; +try +sl:=TStringList.create; + +sl.text:=self.Generate(UseFormatting); + +sl.savetostream(AStream); + +finally +if sl<>nil then sl.free; +end; +end; + +end. diff --git a/components/spktoolbar/SpkXML/SpkXMLTools.pas b/components/spktoolbar/SpkXML/SpkXMLTools.pas new file mode 100644 index 000000000..4e439fbd3 --- /dev/null +++ b/components/spktoolbar/SpkXML/SpkXMLTools.pas @@ -0,0 +1,116 @@ +unit SpkXMLTools; + +interface + +uses Windows, Graphics, SysUtils, + SpkXMLParser; + +type TSpkXMLTools = class + private + protected + public + class procedure Save(Node : TSpkXMLNode; Font : TFont); overload; + class procedure Load(Node : TSpkXMLNode; Font : TFont); overload; + end; + +implementation + +{ TXMLTools } + +class procedure TSpkXMLTools.Load(Node: TSpkXMLNode; Font: TFont); + +var Subnode, Subnode2 : TSpkXMLNode; + +begin +if not(assigned(Node)) then + raise exception.create('TSpkXMLTools.Load: Nieprawidłowa gałąź XML!'); +if not(assigned(Font)) then + raise exception.create('TSpkXMLTools.Load: Brak obiektu czcionki do wczytania!'); + +Subnode:=Node['Charset',false]; +if assigned(Subnode) then + Font.Charset:=TFontCharset(Subnode.TextAsInteger); + +Subnode:=Node['Color',false]; +if assigned(Subnode) then + Font.Color:=Subnode.TextAsInteger; + +Subnode:=Node['Name',false]; +if assigned(Subnode) then + Font.Name:=Subnode.Text; + +Subnode:=Node['Orientation',false]; +if assigned(Subnode) then + Font.Orientation:=Subnode.TextAsInteger; + +Subnode:=Node['Pitch',false]; +if assigned(Subnode) then + Font.Pitch:=TFontPitch(Subnode.TextAsInteger); + +Subnode:=Node['Size',false]; +if assigned(Subnode) then + Font.Size:=Subnode.TextAsInteger; + +Subnode:=Node['Style',false]; +if assigned(Subnode) then + begin + Subnode2:=Subnode['Bold',false]; + if assigned(Subnode2) then + if Subnode2.TextAsBoolean then + Font.Style:=Font.Style + [fsBold] else + Font.Style:=Font.Style - [fsBold]; + + Subnode2:=Subnode['Italic',false]; + if assigned(Subnode2) then + if Subnode2.TextAsBoolean then + Font.Style:=Font.Style + [fsItalic] else + Font.Style:=Font.Style - [fsItalic]; + + Subnode2:=Subnode['Underline',false]; + if assigned(Subnode2) then + if Subnode2.TextAsBoolean then + Font.Style:=Font.Style + [fsUnderline] else + Font.Style:=Font.Style - [fsUnderline]; + end; +end; + +class procedure TSpkXMLTools.Save(Node: TSpkXMLNode; Font: TFont); + +var Subnode, Subnode2 : TSpkXMLNode; + +begin +if not(assigned(Node)) then + raise exception.create('TSpkXMLTools.Save: Nieprawidłowa gałąź XML!'); +if not(assigned(Font)) then + raise exception.create('TSpkXMLTools.Save: Brak obiektu czcionki do zapisania!'); + +Subnode:=Node['Charset',true]; +Subnode.TextAsInteger:=Font.Charset; + +Subnode:=Node['Color',true]; +Subnode.TextAsInteger:=Font.Color; + +Subnode:=Node['Name',true]; +Subnode.Text:=Font.Name; + +Subnode:=Node['Orientation',true]; +Subnode.TextAsInteger:=Font.Orientation; + +Subnode:=Node['Pitch',true]; +Subnode.TextAsInteger:=ord(Font.Pitch); + +Subnode:=Node['Size',true]; +Subnode.TextAsInteger:=Font.Size; + +Subnode:=Node['Style',true]; +Subnode2:=Subnode['Bold',true]; +Subnode2.TextAsBoolean:=fsBold in Font.Style; + +Subnode2:=Subnode['Italic',true]; +Subnode2.TextAsBoolean:=fsItalic in Font.Style; + +Subnode2:=Subnode['Underline',true]; +Subnode2.TextAsBoolean:=fsUnderline in Font.Style; +end; + +end.