unit wst_delphi_xml;

interface
uses
  SysUtils, Classes, xmldom, XMLIntf;

const
  LineEnding = sLineBreak;

type

  TDOMNode = IDOMNode;
  TDOMNodeList = IDOMNodeList;
  TDOMNamedNodeMap  = IDOMNamedNodeMap;
  TDOMDocument = IDOMDocument;
  TXMLDocument = TDOMDocument;
  TDOMElement = IDOMElement;

  function FindNode(ANode : TDOMNode; const ANodeName : string):TDOMNode;
  function GetNodeItemsCount(const ANode : TDOMNode): Integer;
  function GetNodeListCount(ANodeList : TDOMNodeList) : Integer ;overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  function GetNodeListCount(ANodeList : TDOMNamedNodeMap) : Integer ;overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  procedure ReleaseDomNode(ADomNode : IInterface);overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  procedure ReleaseDomNode(var ADomNode : TXMLDocument);overload;{$IFDEF USE_INLINE}inline;{$ENDIF}

  function CreateDoc() : TXMLDocument ;
  procedure WriteXML(Element: TDOMNode; const AFileName: String);overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  procedure WriteXML(Element: TDOMNode; AStream: TStream); overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String); overload;
  procedure WriteXMLFile(ADoc : TXMLDocument; AStream : TStream);overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  procedure ReadXMLFile(out ADoc : TXMLDocument; AStream : TStream);overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  procedure ReadXMLFile(out ADoc: TXMLDocument; const AFilename: String);overload;
  function NodeToBuffer(ANode : TDOMNode):string ;

  function FilterList(const ALIst : IDOMNodeList; const ANodeName : DOMString):IDOMNodeList;overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  function FilterList(const ANode : TDOMNode; const ANodeName : DOMString):IDOMNodeList;overload;{$IFDEF USE_INLINE}inline;{$ENDIF}
  function SelectSingleNode(
    const AXPathExpression : DOMString;
    const AContextNode     : TDOMNode;
    const AErrorIfMore     : Boolean
  ) : TDOMNode;

resourcestring
  SERR_XpathExpectingOneNode = 'Xpath expression expecting a single node while got %d node : %s.';

implementation
uses XmlDoc;

function FindNode(ANode : TDOMNode; const ANodeName : string):TDOMNode;
var
  i, c : Integer;
  lst : TDOMNodeList;
begin
  Result := nil;
  if ANode.hasChildNodes then begin
    lst := ANode.childNodes;
    c := lst.length;
    for i  := 0 to Pred(c) do begin
      if ( ANodeName = lst.item[i].nodeName ) then begin
        Result := lst[i];
        Break;
      end;
    end;
  end;
end;

procedure WriteXMLFile(doc: TXMLDocument; const AFileName: String);
var
  fs: TFileStream;
begin
  fs := TFileStream.Create(AFileName, fmCreate);
  try
    WriteXMLFile(doc, fs);
  finally
    fs.Free;
  end;
end;

procedure WriteXMLFile(ADoc : TXMLDocument; AStream : TStream);
begin
  (ADoc as IDOMPersist).saveToStream(AStream);
end;

procedure WriteXML(Element: TDOMNode; const AFileName: String);
begin
  WriteXMLFile(TXMLDocument(Element), AFileName);
end;

procedure WriteXML(Element: TDOMNode; AStream: TStream);
begin
  WriteXMLFile(TXMLDocument(Element), AStream);
end;

procedure ReadXMLFile(out ADoc : TXMLDocument; AStream : TStream);
begin
  ADoc := CreateDoc();
  (ADoc as IDOMPersist).loadFromStream(AStream);
end;

procedure ReadXMLFile(out ADoc: TXMLDocument; const AFilename: String);
var
  FileStream: TStream;
begin
  ADoc := nil;
  FileStream := TFileStream.Create(AFilename, fmOpenRead+fmShareDenyWrite);
  try
    ReadXMLFile(ADoc, FileStream);
  finally
    FileStream.Free;
  end;
end;

function GetNodeItemsCount(const ANode : TDOMNode): Integer;
begin
  if ANode.HasChildNodes then begin
    Result := ANode.childNodes.length;
  end else begin
    Result := 0;
  end;
end;

function GetNodeListCount(ANodeList : TDOMNodeList) : Integer ;overload;
begin
  Result := ANodeList.length;
end;

function GetNodeListCount(ANodeList : TDOMNamedNodeMap) : Integer ;overload;
begin
  Result := ANodeList.length;
end;

procedure ReleaseDomNode(ADomNode : IInterface);
begin
end;

procedure ReleaseDomNode(var ADomNode : TXMLDocument);
begin

end;

function CreateDoc() : TXMLDocument ;
var
  locDoc : IXMLDocument;
begin
  locDoc := XmlDoc.TXMLDocument.Create(nil);
  locDoc.Active := True;
  Result := locDoc.DOMDocument;
end;

function NodeToBuffer(ANode : TDOMNode):string ;
var
  locNodeEx : IDOMNodeEx;
begin
  if Supports(ANode,IDOMNodeEx,locNodeEx) then begin
    Result := locNodeEx.xml;
  end else begin
    raise Exception.Create('This Xml library do not provide "IDOMNodeEx" support.');
  end;
end;

type
  TDOMNodeSelectListImp = class(TInterfacedObject,IDOMNodeList)
  private
    FItemName : widestring;
    FInnerList : IDOMNodeList;
    FCount : Integer;
  private
    function internal_get_item(index: Integer): IDOMNode;
  protected
    function get_item(index: Integer): IDOMNode; safecall;
    function get_length: Integer; safecall;
  public
    constructor Create(
      const AInnerList : IDOMNodeList;
      const AItemName  : widestring
    );
  end;

function FilterList(const ALIst : IDOMNodeList; const ANodeName : DOMString):IDOMNodeList ;
begin
  Result := TDOMNodeSelectListImp.Create(ALIst,ANodeName);
end;

function FilterList(const ANode : TDOMNode; const ANodeName : DOMString):IDOMNodeList;
begin
  Result := FilterList(ANode.ChildNodes,ANodeName);
end;

function SelectSingleNode(
  const AXPathExpression : DOMString;
  const AContextNode     : TDOMNode;
  const AErrorIfMore     : Boolean
) : TDOMNode;
var
  locSelect : IDOMNodeSelect;
  ns : TDOMNodeList;
begin
  Result := nil;
  locSelect := AContextNode as IDOMNodeSelect;
  ns := locSelect.selectNodes(AXPathExpression);
  if ( ns <> nil ) and ( ns.length > 0 ) then begin
    if AErrorIfMore and ( ns.length > 1 ) then
      raise Exception.CreateFmt(SERR_XpathExpectingOneNode,[ns.length,AXPathExpression]);
    Result := ns[0];
  end;
end;

{ TDOMNodeSelectListImp }

constructor TDOMNodeSelectListImp.Create(
  const AInnerList: IDOMNodeList;
  const AItemName: widestring
);
begin
  Assert(AInnerList <> nil);
  FInnerList := AInnerList;
  FItemName := AItemName;
  FCount := -1;
end;

function TDOMNodeSelectListImp.get_item(index: Integer): IDOMNode;
begin
  Result := internal_get_item(index);
  if ( Result = nil ) then
    raise Exception.CreateFmt('Invalid item at %d.',[index]);
end;

function TDOMNodeSelectListImp.get_length() : Integer;
begin
  if ( FCount >= 0 ) then begin
    Result := FCount;
  end else begin
    FCount := 0;
    while Assigned(internal_get_item(FCount)) do begin
      Inc(FCount);
    end;
    Result := FCount;
  end;
end;

function TDOMNodeSelectListImp.internal_get_item(index: Integer): IDOMNode;
var
  i : Integer;
  crt : IDOMNode;
begin
  Result := nil;
  if ( FInnerList.length > 0 ) then begin
    i := -1;
    crt := FInnerList.item[0];
    while ( crt <> nil ) do begin
      if ( FItemName = crt.nodeName ) then begin
        Inc(i);
        if ( i = index ) then begin
          Result := crt;
          Break;
        end;
      end;
      crt := crt.nextSibling;
    end;
  end;
end;

end.
