Hi Marcos,

> OK, if you can I appreciate that.
> Some functions of fcl-web returns a new object... I have to be careful
> with memory leaks.

See: addressbook.pp.
Mind you, my code isn't web related, it just handles an xml-file.

Bart
unit AddressBook;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, DOM, XMLWrite, XMLRead;


type
  TPersonField = (pfInvalid, pfName, pfFirstName, pfPrefix, pfAddress, pfZIP, pfCity,
                  pfPhonePrivate, pfPhoneWork, pfGSM, pfFax, pfEmail, pfWeb, pfRemark, pfMarked);
  TInfoField = (ifInvalid, ifAppVersion, ifCopyRight, ifWarning);
  TAppNode = (anAddressBook, anInfo, anPersonContainer, anPerson);

const
  PersonFields: Array[TPersonField] of DOMString = (#0#1,'Naam','Voornaam','Voorv','Adres','Postcode','Plaats',
                                                    'TelPrive','TelWerk','GSM','Fax','Email','Web','Opmerking','Markeer');
  InfoFields: Array[TInfoField] of DOMString = (#0#1,'Applicatie','CopyRight','Waarschuwing');
  AppNodes: Array[TAppNode] of DOMString = ('AdresBoek','Info','Personen','Persoon');


type

  { TPerson }

  TPerson = class
  private
    FName: String;
    FFirstName: String;
    FPrefix: String;
    FAddress: String;
    FZIP: String;
    FCity: String;
    FPhonePrivate: String;
    FPhoneWork: String;
    FGSM: STring;
    FFax: String;
    FEmail: String;
    FWeb: String;
    FMarked: Boolean;
    FRemark: String;
    FOnChange: TNotifyEvent;
    procedure SetAddress(const AValue: String);
    procedure SetCity(const AValue: String);
    procedure SetEmail(const AValue: String);
    procedure SetFax(const AValue: String);
    procedure SetFirstName(const AValue: String);
    procedure SetGSM(const AValue: String);
    procedure SetMarked(const AValue: Boolean);
    procedure SetName(const AValue: String);
    procedure SetOnChange(const AValue: TNotifyEvent);
    procedure SetPhonePrivate(const AValue: String);
    procedure SetPhoneWork(const AValue: String);
    procedure SetPrefix(const AValue: String);
    procedure SetRemark(const AValue: String);
    procedure SetWeb(const AValue: String);
    procedure SetZIP(const AValue: String);
  protected
    procedure PersonChanged;
  public
    procedure Assign(APerson: TPerson);
    procedure Clear;
    function Matches(APerson: TPerson): Boolean;
    function IsEmpty: Boolean;
    constructor Create;
    property Name: String read FName write SetName;
    property Prefix: String read FPrefix write SetPrefix;
    property FirstName: String read FFirstName write SetFirstName;
    property Address: String read FAddress write SetAddress;
    property ZIP: String read FZIP write SetZIP;
    property City: String read FCity write SetCity;
    property PhonePrivate: String read FPhonePrivate write SetPhonePrivate;
    property PhoneWork: String read FPhoneWork write SetPhoneWork;
    property GSM: String read FGSM write SetGSM;
    property Fax: String read FFax write SetFax;
    property Email: String read FEmail write SetEmail;
    property Web: String read FWeb write SetWeb;
    property Remark: String read FRemark write SetRemark;
    property Marked: Boolean read FMarked write SetMarked;
    property OnChange: TNotifyEvent read FOnChange write SetOnChange;
  end;


const
  SNoImporter = 'No importer assigned';
  SNoExporter = 'No exporter assigned';
  SInternalListError = 'Fatal internal list error';
  SUtf8Notification: String = 'LET OP: Dit bestand moet in UTF-8 codering opgeslagen worden!';


type

  EAddressBook = class(EStreamError);
  EInternalListError = class(EAddressBook);
  ENoImporter = class(EAddressBook);
  ENoExporter = class(EAddressBook);
  EAddressBookReadError = class(EAddressBook);
  EAddressBookWriteError = class(EAddressBook);



  //Forward declarations
  TCustomAddressBookExporter = Class;
  TCustomAddressBookImporter = Class;



  { TAddressBook }

  TAddressBook = class
  private
    FCopyright: String;
    //FCurrentIndex: Integer;
    FList: TList;
    FImporter: TCustomAddressBookImporter;
    FExporter: TCustomAddressBookExporter;
    FModified: Boolean;
    FVersion: String;
    FFileName: String;
    function GetPersonCount: Integer;
    function GetPerson(const i: Integer): TPerson;
    procedure SetCopyright(const AValue: String);
    procedure SetPerson(const i: Integer; const APerson: TPerson);
    //procedure SetCurrentIndex(const AValue: Integer);
    procedure SetExporter(const AValue: TCustomAddressBookExporter);
    procedure SetImporter(const AValue: TCustomAddressBookImporter);
    procedure SetModified(const AValue: Boolean);
    procedure SetVersion(const AValue: String);
    procedure XmlToList(XMLDoc: TXMlDocument);
    procedure OnPersonChanged(Sender: TObject);
  protected
  public
    function AddPerson(out Index: Integer): Boolean;
    procedure DeletePerson(const Index: Integer);
    procedure Sort(const Ascending: Boolean = True);
    function FindNamePartial(const Value: String; const StartIndex: Integer): Integer;
    function FindText(const Value: String; const StartIndex: Integer): Integer;
    procedure LoadFromFile(const Fn: String);
    procedure SaveToFile(const Fn: String);
    procedure ExportToFile(const Fn: String; const MarkedOnly: Boolean = False);
    procedure ImportFromFile(const Fn: String);
    procedure Clear;

    constructor Create;
    destructor Destroy; override;

    //Public properties
    property Copyright: String read FCopyright write SetCopyright;
    property Version: String read FVersion write SetVersion;
    property Persons[i: Integer]: TPerson read GetPerson write SetPerson; default;
    property Count: Integer read GetPersonCount;
    property Exporter: TCustomAddressBookExporter read FExporter write SetExporter;
    property Importer: TCustomAddressBookImporter read FImporter write SetImporter;
    property Modified: Boolean read FModified write SetModified;
    //FileName is set to a real value,if LoadFromFile or SaveToFile succeeds,
    //it is cleared on failure (load/save), or on import
    property FileName: String read FFileName;
  end;

  { TCustomAddressBookExporter }

  TCustomAddressBookExporter = Class
  public
    procedure ExportToFile(const Fn: String; ABook: TAddressBook; const MarkedOnly: Boolean = False); virtual; abstract;
  end;

  { TCustomAddressBookImporter }

  TCustomAddressBookImporter = Class
  public
    procedure ImportFromFile(const Fn: String; ABook: TAddressBook); virtual; abstract;
  end;



implementation

{$ifdef windows}
uses win9xwsmanager;  //needed for UTF8UpperCase on Win9x systems
{$endif}

{ TPerson }

procedure TPerson.SetName(const AValue: String);
begin
  if FName=AValue then exit;
  FName:=AValue;
  PersonChanged;
end;

procedure TPerson.SetAddress(const AValue: String);
begin
  if FAddress=AValue then exit;
  FAddress:=AValue;
  PersonChanged;
end;

procedure TPerson.SetCity(const AValue: String);
begin
  if FCity=AValue then exit;
  FCity:=AValue;
  PersonChanged;
end;

procedure TPerson.SetEmail(const AValue: String);
begin
  if FEmail=AValue then exit;
  FEmail:=AValue;
  PersonChanged;
end;

procedure TPerson.SetFax(const AValue: String);
begin
  if FFax=AValue then exit;
  FFax:=AValue;
  PersonChanged;
end;

procedure TPerson.SetFirstName(const AValue: String);
begin
  if FFirstName=AValue then exit;
  FFirstName:=AValue;
  PersonChanged;
end;

procedure TPerson.SetGSM(const AValue: String);
begin
  if FGSM=AValue then exit;
  FGSM:=AValue;
  PersonChanged;
end;

procedure TPerson.SetMarked(const AValue: Boolean);
begin
  if FMarked=AValue then exit;
  FMarked:=AValue;
  PersonChanged;
end;


procedure TPerson.SetPhonePrivate(const AValue: String);
begin
  if FPhonePrivate=AValue then exit;
  FPhonePrivate:=AValue;
  PersonChanged;
end;

procedure TPerson.SetPhoneWork(const AValue: String);
begin
  if FPhoneWork=AValue then exit;
  FPhoneWork:=AValue;
  PersonChanged;
end;

procedure TPerson.SetPrefix(const AValue: String);
begin
  if FPrefix=AValue then exit;
  FPrefix:=AValue;
  PersonChanged;
end;

procedure TPerson.SetRemark(const AValue: String);
begin
  if FRemark=AValue then exit;
  FRemark:=AValue;
  PersonChanged;
end;

procedure TPerson.SetWeb(const AValue: String);
begin
  if FWeb=AValue then exit;
  FWeb:=AValue;
  PersonChanged;
end;

procedure TPerson.SetZIP(const AValue: String);
begin
  if FZIP=AValue then exit;
  FZIP:=AValue;
  PersonChanged;
end;

procedure TPerson.SetOnChange(const AValue: TNotifyEvent);
begin
  if FOnChange=AValue then exit;
  FOnChange:=AValue;
end;


procedure TPerson.PersonChanged;
begin
  if Assigned(FOnChange) then FOnChange(Self);
end;

procedure TPerson.Assign(APerson: TPerson);
begin
  if Assigned(APerson) and (not Self.Matches(APerson)) then
  begin
    FName := APerson.Name;
    FFirstName := APerson.FirstName;
    FPrefix := APerson.Prefix;
    FAddress := APerson.Address;
    FZIP := APerson.ZIP;
    FCity := APerson.City;
    FPhonePrivate := APerson.PhonePrivate;
    FPhoneWork := APerson.PhoneWork;
    FGSM := APerson.GSM;
    FFax := APerson.Fax;
    FEmail := APerson.Email;
    FWeb := APerson.Web;
    FRemark := APerson.Remark;
    FMarked := APerson.Marked;
    PersonChanged;
  end
  else Clear;
end;

procedure TPerson.Clear;
begin
  FName := '';
  FName := '';
  FFirstName := '';
  FPrefix := '';
  FAddress := '';
  FZIP := '';
  FCity := '';
  FPhonePrivate := '';
  FPhoneWork := '';
  FGSM := '';
  FFax := '';
  FEmail := '';
  FWeb := '';
  FRemark := '';
  FMarked := False;
  PersonChanged;
end;

function TPerson.Matches(APerson: TPerson): Boolean;
begin
  Result :=
  ( (FName = APerson.Name) and
    (FFirstName = APerson.FirstName) and
    (FPrefix = APerson.Prefix) and
    (FAddress = APerson.Address) and
    (FZip = APerson.Zip) and
    (FCity = APerson.City) and
    (FPhonePrivate = APerson.PhonePrivate) and
    (FPhoneWork = APerson.PhoneWork) and
    (FGSM = APerson.GSM) and
    (FFax = APerson.Fax) and
    (FEmail = APerson.Email) and
    (FWeb = APerson.Web) and
    (FRemark = APerson.Remark) and
    (FMarked = APerson.Marked) );
end;

function TPerson.IsEmpty: Boolean;
begin
  Result :=
    (FName = '') and
    (FFirstName = '') and
    (FPrefix = '') and
    (FAddress = '') and
    (FZIP = '') and
    (FCity = '') and
    (FPhonePrivate = '') and
    (FPhoneWork = '') and
    (FGSM = '') and
    (FFax = '') and
    (FEmail = '') and
    (FWeb = '') and
    (FRemark = '') and
    (FMarked = False);
end;

constructor TPerson.Create;
begin
  FName := '';
  FFirstName := '';
  FPrefix := '';
  FAddress := '';
  FZIP := '';
  FCity := '';
  FPhonePrivate := '';
  FPhoneWork := '';
  FGSM := '';
  FFax := '';
  FEmail := '';
  FWeb := '';
  FRemark := '';
  FMarked := False;
  FOnChange := nil;
end;


{ TAdressBook }

//Helper functions

function DomStrToBool(AValue: DomString): Boolean;
begin
  if (AValue = '1') then Result := True
  else Result := False;
end;

function DomBoolStr(const Value: Boolean): DomString;
begin
  if Value then
    Result := '1'
  else
    Result := '0';
end;

function Pad(const Value, Width: Integer): String;
begin
  Result := IntToStr(Value);
  While Length(Result) < Width do Result := '0' + Result;
end;

//Original code from Lazarus LCLProc unit
function UTF8UpperCase(const s: String): String;
begin
  Result := UTF8Encode(WideUpperCase(UTF8Decode(s)));
end;

function SortBookDescending(Person1, Person2: Pointer): Integer;
begin
  Result := 0 - AnsiCompareText(TPerson(Person1).Name, TPerson(Person2).Name);
end;

function SortBookAscending(Person1, Person2: Pointer): Integer;
begin
  Result := AnsiCompareText(TPerson(Person1).Name, TPerson(Person2).Name);
end;

function TAddressBook.GetPersonCount: Integer;
begin
  Result := FList.Count;
end;

function TAddressBook.GetPerson(const i: Integer): TPerson;
begin
  if FList.Count > i then Result := TPerson(FList.Items[i]) else Result := nil;
end;

procedure TAddressBook.SetCopyright(const AValue: String);
begin
  if FCopyright =AValue then exit;
  FCopyright :=AValue;
end;

{
procedure TAddressBook.SetCurrentIndex(const AValue: Integer);
begin
  if FCurrentIndex=AValue then exit;
  FCurrentIndex:=AValue;
end;
}

procedure TAddressBook.SetExporter(const AValue: TCustomAddressBookExporter);
begin
  if FExporter=AValue then exit;
  FExporter:=AValue;
end;

procedure TAddressBook.SetImporter(const AValue: TCustomAddressBookImporter);
begin
  if FImporter=AValue then exit;
  FImporter:=AValue;
end;

procedure TAddressBook.SetModified(const AValue: Boolean);
begin
  if FModified=AValue then exit;
  FModified:=AValue;
end;

procedure TAddressBook.SetVersion(const AValue: String);
begin
  if FVersion =AValue then exit;
  FVersion :=AValue;
end;

procedure TAddressBook.SetPerson(const i: Integer; const APerson: TPerson);
var
  AFoo: TPerson;
begin
  //writeln('TAddressBook.SetPerson: i = ',i);
  //if not Assigned(FList.Items[i]) then FList.Items[i] := TPerson.Create;
  AFoo := TPerson(FList.Items[i]);
  if not AFoo.Matches(APerson) then
  begin
    AFoo.Assign(APerson);
    //FModified := True;
  end;
end;

function TAddressBook.AddPerson(out Index: Integer): Boolean;
begin
  Index := FList.Add(TPerson.Create);
  Result := (Index >= 0);
  if Result then TPerson(FList[Index]).OnChange := @OnPersonChanged;
  FModified := True;
end;

procedure TAddressBook.DeletePerson(const Index: Integer);
var
  APerson: TPerson;
begin
  if Index < FList.Count then
  begin
    FModified := True;
    APerson := TPerson(FList.Items[Index]);
    APerson.Free;
    APerson := nil;
    FList.Delete(Index);
  end;
end;

procedure TAddressBook.Sort(const Ascending: Boolean = True);
begin
  if (FList.Count > 1) then
  begin
    FModified := True;
    if Ascending then
      FList.Sort(@SortBookAscending)
    else
      FList.Sort(@SortBookDescending)
  end;
end;


function TAddressBook.FindNamePartial(const Value: String; const StartIndex: Integer): Integer;
//Find a name that starts with Value, case insensitive
var
  i: Integer;
begin
  Result := -1; //Assume failure
  for i := StartIndex to Count - 1 do
  begin
    if (Pos(UTF8UpperCase(Value), UTF8UpperCase(Persons[i].Name)) = 1) then
    begin
      Result := i;
      exit;
    end;
  end;
  for i := 0 to StartIndex - 1 do
  begin
    if (Pos(UTF8UpperCase(Value), UTF8UpperCase(Persons[i].Name)) = 1) then
    begin
      Result := i;
      exit;
    end;
  end;
end;



function TAddressBook.FindText(const Value: String; const StartIndex: Integer): Integer;
//Find text in any field, case insensitive
var
  i: Integer;
begin
  Result := -1; //Assume failure
  for i := StartIndex to Count - 1 do
  begin
//    writeln('FindText: UC(Value) = ',Utf8UpperCase(Value),' i = ',i,' UC(Persons[',i,'].Name) = ',
//             Utf8UpperCase(Persons[i].Name),' Pos = ',Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Name)));
    //There is no need to use Utf8Pos(), since we are only interested in Pos() > 0, not the "real" position
    if (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Name)) > 0)         or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].FirstName)) > 0)    or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Prefix)) > 0)       or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].ZIP)) > 0)          or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].City)) > 0)         or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].PhonePrivate)) > 0) or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].PhoneWork)) > 0)    or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].GSM)) > 0)          or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Fax)) > 0)          or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Email)) > 0)        or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Web)) > 0)          or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Remark)) > 0) then
    begin
      Result := i;
      exit;
    end;
  end;
  for i := 0 to StartIndex - 1 do
  begin
    if (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Name)) > 0)         or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].FirstName)) > 0)    or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Prefix)) > 0)       or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].ZIP)) > 0)          or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].City)) > 0)         or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].PhonePrivate)) > 0) or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].PhoneWork)) > 0)    or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].GSM)) > 0)          or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Fax)) > 0)          or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Email)) > 0)        or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Web)) > 0)          or
       (Pos(Utf8UpperCase(Value), Utf8UpperCase(Persons[i].Remark)) > 0) then
    begin
      Result := i;
      exit;
    end;
  end;
end;




procedure TAddressBook.XmlToList(XMLDoc: TXMlDocument);
var
  iNode: TDOMNode;
  APerson: TPerson;
  FoundPerson: Boolean;

  function IsRoot(Node: TDomNode): Boolean;
  begin
    Result := (Node.ParentNode = XMLDoc);
  end;

  function IsAddressBook(Node: TDomNode; const Level: Integer): Boolean;
  begin
    Result :=  (Level = 0) and (Node.CompareName('AdresBoek') = 0);
    //if not result then DbgOut(' -B ') else DbgOut(' +B ');
  end;

  function IsPersonContainer(Node: TDomNode; const Level: Integer): Boolean;
  begin
    Result := (Level = 1) and (Node.CompareName('Adressen') = 0) and IsAddressBook(Node.ParentNode, Level - 1);
    //if not result then DbgOut(' -C ') else DbgOut(' +C ');
  end;

  function IsPerson(Node: TDomNode; const Level: Integer): Boolean;
  begin
    Result := (Level = 2) and (Node.CompareName('Adres') = 0) and IsPersonContainer(Node.ParentNode, Level - 1);
    //if not result then DbgOut(' -P ') else DbgOut(' +P ');
  end;

  function IsValidPersonField(Node: TDomNode; Level: Integer; out APersonField: TPersonField): Boolean;
  var
    FieldIndex: TPersonField;
  begin
    APersonField := pfInvalid;
    Result := (Level = 4) and (Node.NodeType = TEXT_NODE)
              and IsPerson(Node.ParentNode.ParentNode, Level - 2);
    if Result then
    begin
      for FieldIndex := Low(TPersonField) to High(TPersonField) do
      begin
        if (FieldIndex <> pfInvalid) and (Node.ParentNode.CompareName(PersonFields[FieldIndex]) = 0) then
        begin
          APersonField := FieldIndex;
          Exit;
        end;
      end;
      Result := False;
    end;
  end;

  procedure ProcessNode(Node: TDOMNode; {const Ident: String;} const Level: Integer);
  var
    cNode: TDOMNode;
    //S: String;
    APersonField: TPersonField;
    dummyIndex: Integer;
  begin
    if Node = nil then Exit; // Stops if reached a leaf

    //S := '['+node.NodeName + ' L=' + IntToStr(Level);

    if IsPerson(Node, Level) then
    begin
      FoundPerson := True;
      //New Person node: first store last one (if this is not the first Person node)
      //writeln('New person: current count = ',FList.Count);
      if (FList.Count > 0) then TPerson(FList.Items[Count-1]).Assign(Aperson);
      //then add a new person to the list
      APerson.Clear;
      if not AddPerson(dummyIndex) then Raise EInternalListError.Create('Fail on TAddressBook.AddPerson: Internal List Error');
    end;
    //S := S + '] ' + Node.NodeValue;
    //writeln(Ident + S); //'['+S+node.NodeName+'] '+Node.NodeValue{Node.Attributes[0].NodeValue});


    if (Node.NodeType = TEXT_NODE) then
    begin
      if IsValidPersonField(Node, Level, APersonField) then
      begin
        case APersonField of
          pfName :        APerson.Name := Node.NodeValue;
          pfFirstName:    Aperson.FirstName := Node.NodeValue;
          pfPrefix:       Aperson.Prefix := Node.NodeValue;
          pfAddress:      Aperson.Address := Node.NodeValue;
          pfZIP:          Aperson.ZIP := Node.NodeValue;
          pfCity:         Aperson.City := Node.NodeValue;
          pfPhonePrivate: Aperson.PhonePrivate := Node.NodeValue;
          pfPhoneWork:    Aperson.PhoneWork := Node.NodeValue;
          pfGSM:          Aperson.GSM := Node.NodeValue;
          pfFax:          Aperson.Fax := Node.NodeValue;
          pfEmail:        Aperson.Email := Node.NodeValue;
          pfWeb:          Aperson.Web := Node.NodeValue;
          pfRemark:       Aperson.Remark := AdjustLineBreaks(Node.NodeValue);
          pfMarked:       Aperson.Marked := DomStrToBool(Node.NodeValue);
        end;
      end;
    end;

    cNode := Node.FirstChild;

    // Processes all child nodes
    while cNode <> nil do
    begin
      ProcessNode(cNode, {Ident + '  ',} Level + 1);
      cNode := cNode.NextSibling;
    end;
    //writeln(Ident+'[/'+node.NodeName+'] ');
  end;

begin
  APerson := TPerson.Create;
  FoundPerson := False;
  try
    iNode := XMLDoc{.DocumentElement}.FirstChild;
    while iNode <> nil do
    begin
      ProcessNode(iNode, {'',} 0); // Recursive
      iNode := iNode.NextSibling;
    end;
    //The last person found has not been updated in the list in ProcessNode
    if FoundPerson and (Count > 0) then Persons[Count - 1].Assign(APerson);
  finally
    APerson.Free;
  end;
end;

procedure TAddressBook.OnPersonChanged(Sender: TObject);
begin
  //writeln('TAddressBook.OnPersonChanged');
  FModified := True;
end;


procedure TAddressBook.LoadFromFile(const Fn: String);
var
  Doc: TXMlDocument;
begin
  //writeln('TBook.LoadFromFile');
  FFileName := ''; //Assume failure
  Clear;
  //writeln(' LFF: after Clear');
  try
    try
      //writeln('  LFF: ReadXML');
      ReadXMLFile(Doc, Fn);
      //writeln('  LFF: XMLToList');
      XmlToList(Doc);
      FModified := False;
      FFileName := Fn;
    except
      on E: EXMLReadError do
      begin
        Raise EAddressBookReadError.Create(E.Message);
        //writeln('LoadFromFile Error [',e.classname,']: ',e.message);
      end;
    end;
  finally
    Doc.free;
  end;
end;




procedure TAddressBook.SaveToFile(const Fn: String);
var Doc: TXMLDocument;
    ndRoot, ndVersion, ndCopyRight, ndWarning, ndAdressContainer, ndInfo,
    ndAdress, ndName, ndFirstName, ndPrefix, ndAddress, ndZIP,
    ndCity, ndPhonePrivate, ndFax, ndGSM, ndEmail, ndWeb,
    ndRemark, ndMarked, ndPhoneWork: TDOMElement;
    APerson: TPerson;
    i: Integer;
begin
  //In case of error FFileName must be cleared!
  FFileName := '';
  Doc := TXMLDocument.Create;
  Try

    ndRoot := Doc.CreateElement(AppNodes[anAddressBook]);
    Doc.AppendChild(ndRoot);


    //Info section
    ndInfo := Doc.CreateElement(AppNodes[anInfo]);
    ndRoot.AppendChild(ndInfo);
    //Create TextNodes for Info section
    ndVersion := Doc.CreateElement(InfoFields[ifAppVersion]);
    ndVersion.AppendChild(Doc.CreateTextNode(FVersion));
    ndInfo.AppendChild(ndVersion);
    ndCopyRight := Doc.CreateElement(InfoFields[ifCopyRight]);
    ndCopyRight.AppendChild(Doc.CreateTextNode(FCopyRight));
    ndInfo.AppendChild(ndCopyRight);
    ndWarning := Doc.CreateElement(InfoFields[ifWarning]);
    ndWarning.AppendChild(Doc.CreateTextNode(SUtf8Notification));
    ndInfo.AppendChild(ndWarning);



    //Persons container
    ndAdressContainer := Doc.CreateElement(AppNodes[anPersonContainer]);
    ndRoot.AppendChild(ndAdressContainer);


    for i := 0 to Self.Count - 1 do
    begin
      APerson := TPerson(FList.Items[i]);
      // first child of Persons container
      ndAdress := Doc.CreateElement(AppNodes[anPerson]);
      TDOMElement(ndAdress).SetAttribute('Index', Pad(i, 3));       // create atributes to parent node

      //Create TextNodes for Person
      ndName := Doc.CreateElement(PersonFields[pfName]);
      ndName.AppendChild(Doc.CreateTextNode(APerson.Name));
      ndAdress.AppendChild(ndName);

      ndFirstName := Doc.CreateElement(PersonFields[pfFirstName]);
      ndFirstName.AppendChild(Doc.CreateTextNode(APerson.FirstName));
      ndAdress.AppendChild(ndFirstName);

      ndPrefix := Doc.CreateElement(PersonFields[pfPrefix]);
      ndPrefix.AppendChild(Doc.CreateTextNode(APerson.Prefix));
      ndAdress.AppendChild(ndPrefix);

      ndAddress := Doc.CreateElement(PersonFields[pfAddress]);
      ndAddress.AppendChild(Doc.CreateTextNode(APerson.Address));
      ndAdress.AppendChild(ndAddress);

      ndZIP := Doc.CreateElement(PersonFields[pfZIP]);
      ndZIP.AppendChild(Doc.CreateTextNode(APerson.ZIP));
      ndAdress.AppendChild(ndZIP);

      ndCity := Doc.CreateElement(PersonFields[pfCity]);
      ndCity.AppendChild(Doc.CreateTextNode(APerson.City));
      ndAdress.AppendChild(ndCity);


      ndPhonePrivate := Doc.CreateElement(PersonFields[pfPhonePrivate]);
      ndPhonePrivate.AppendChild(Doc.CreateTextNode(APerson.PhonePrivate));
      ndAdress.AppendChild(ndPhonePrivate);

      ndPhoneWork := Doc.CreateElement(PersonFields[pfPhoneWork]);
      ndPhoneWork.AppendChild(Doc.CreateTextNode(APerson.PhoneWork));
      ndAdress.AppendChild(ndPhoneWork);

      ndGSM := Doc.CreateElement(PersonFields[pfGSM]);
      ndGSM.AppendChild(Doc.CreateTextNode(APerson.GSM));
      ndAdress.AppendChild(ndGSM);

      ndFax := Doc.CreateElement(PersonFields[pfFax]);
      ndFax.AppendChild(Doc.CreateTextNode(APerson.Fax));
      ndAdress.AppendChild(ndFax);

      ndEmail := Doc.CreateElement(PersonFields[pfEmail]);
      ndEmail.AppendChild(Doc.CreateTextNode(APerson.Email));
      ndAdress.AppendChild(ndEmail);

      ndWeb := Doc.CreateElement(PersonFields[pfWeb]);
      ndWeb.AppendChild(Doc.CreateTextNode(APerson.Web));
      ndAdress.AppendChild(ndWeb);

      ndRemark := Doc.CreateElement(PersonFields[pfRemark]);
      ndRemark.AppendChild(Doc.CreateTextNode(APerson.Remark));
      ndAdress.AppendChild(ndRemark);

      ndMarked := Doc.CreateElement(PersonFields[pfMarked]);
      ndMarked.AppendChild(Doc.CreateTextNode(DomBoolStr(APerson.Marked)));
      ndAdress.AppendChild(ndMarked);

      {
      nd? := Doc.CreateElement(PersonFields[pf?]);
      nd?.AppendChild(Doc.CreateTextNode(APerson.?));
      ndAdress.AppendChild(nd?);
      }


      ndAdressContainer.AppendChild(ndAdress);
    end;
    try
      WriteXmlFile(Doc, Fn);
      FModified := False;
      FFileName := Fn;
    except
      on E: EStreamError do Raise EAddressBookWriteError.Create(E.Message);
    end;
  finally
    Doc.free;
  end;
end;

procedure TAddressBook.ExportToFile(const Fn: String; const MarkedOnly: Boolean = False);
begin
  if Assigned(FExporter) then FExporter.ExportToFile(Fn, Self, MarkedOnly)
  else Raise ENoExporter.Create(SNoExporter);
end;

procedure TAddressBook.ImportFromFile(const Fn: String);
begin
  if Assigned(FImporter) then
  begin
    FFileName := '';
    FImporter.ImportFromFile(Fn, Self);
  end
  else Raise ENoImporter.Create(SNoImporter);
end;

constructor TAddressBook.Create;
begin
  FList := TList.Create;
  FExporter := nil;
  FExporter := nil;
  //FCurrentIndex := -1;
  FModified := False;
  FCopyRight := 'Copyright 2011 by Flying Sheep Inc. & Bart Broersma';
  FVersion := 'Versie 3.0.0 RC1';
end;

destructor TAddressBook.Destroy;
begin
  Clear;
  FList.Free;
  inherited Destroy;
end;

procedure TAddressBook.Clear;
var
  AFoo: TPerson;
  i: Integer;
begin
  //writeln('TAddressBook.Clear: FList.Count = ',FList.Count);
  if (FList.Count > 0) then FModified := True;
  for i := FList.Count - 1 downto 0 do
  begin
    //write('Freeing Person ',i,' ');
    AFoo := TPerson(FList.Items[i]);
    AFoo.Free;
    AFoo := nil;
    //writeln('OK');
  end;
  FList.Clear;
end;


end.

_______________________________________________
fpc-pascal maillist  -  fpc-pascal@lists.freepascal.org
http://lists.freepascal.org/mailman/listinfo/fpc-pascal

Reply via email to