Skip to content
The Road to Delphi — Delphi, VCL, VCL Styles, UxTheme and WinAPI

Enabling XPath (selectNode, selectNodes) methods in Vcl and FireMonkey Apps

, , ,

The TXMLDocument class allow you to manipulate XML files in VCL and FireMonkey Apps, but this class doesn’t implements a direct way to call the XPath related methods (selectNode, selectNodes) , so you must write a set of helper functions to call these methods.

Normally you can write something like so

[sourcecode language=”delphi” wraplines=”false”]
function selectSingleNode(ADOMDocument: IDOMDocument; const nodePath: WideString): IDOMNode;
var
LDomNodeSelect : IDomNodeSelect;
begin
if not Assigned(ADOMDocument) or not Supports(ADOMDocument.documentElement, IDomNodeSelect, LDomNodeSelect) then
Exit;
//or just LDomNodeSelect:= (ADOMDocument.documentElement as IDOMNodeSelect);
Result:=LDomNodeSelect.selectNode(nodePath);
end;
[/sourcecode]

[sourcecode language=”delphi” wraplines=”false”]
function SelectNodes(ADOMDocument: IDOMDocument; const nodePath: WideString): IDOMNodeList;
var
LDomNodeSelect : IDomNodeSelect;
begin
if not Assigned(ADOMDocument) or not Supports(ADOMDocument.documentElement, IDomNodeSelect, LDomNodeSelect) then
Exit;
//or just LDomNodeSelect:= (ADOMDocument.documentElement as IDOMNodeSelect);
Result:=LDomNodeSelect.selectNodes(nodePath);
end;
[/sourcecode]

And use like so.

[sourcecode language=”delphi” wraplines=”false”]
var
XmlDoc: IXMLDocument;
LNode : IDOMNode;
i : Integer;
begin
XmlDoc := TXMLDocument.Create(nil);
XmlDoc.Active := True;
XmlDoc.Options := XmlDoc.Options + [doNodeAutoIndent];
XmlDoc.Version := ‘1.0’;
…
…
LNode:=selectSingleNode(XmlDoc.DOMDocument,XPathExpr);
[/sourcecode]

The above code will works fine under Windows using the MSXML provider as Default DOM Vendor, but in a FireMonkey Application which must run in OSX and Windows you must set the Default DOM Vendor to ADOM (OpenXML).

[sourcecode language=”delphi” wraplines=”false”]
DefaultDOMVendor := OpenXML4Factory.Description;
[/sourcecode]

Now if you try to use the above functions (selectSingleNode, SelectNodes) under the ADOM vendor you will get an awfull exception

[sourcecode language=”text” wraplines=”false”]
EOleException Catastrophic failure 8000FFFF
[/sourcecode]

The root of this issue is located in the Tox4DOMNode.selectNode and Tox4DOMNode.selectNodes implementation of these methods, check the next code.

[sourcecode language=”delphi” wraplines=”false”]
function Tox4DOMNode.selectNode(const nodePath: WideString): IDOMNode;
var
xpath: TXpathExpression;
xdomText: TDomText;
begin
Result := nil;
if not Assigned(WrapperDocument) or not Assigned(WrapperDocument.WrapperDOMImpl) then
Exit;

xpath := WrapperDocument.WrapperDOMImpl.FXpath; //here the xpath is set with a nil value because the FXpath was no initialized
xpath.ContextNode := NativeNode; //Here the App crash because xpath is nil
[/sourcecode]

The FXpath field is initialized in the Tox4DOMImplementation.InitParserAgent method which is never call at least which you uses the Tox4DOMImplementation.loadFromStream or Tox4DOMImplementation.loadxml methods. So to fix this issue you must call the Tox4DOMImplementation.InitParserAgent function before to call the selectNode and selectNodes methods.

[sourcecode language=”delphi” wraplines=”false”]
function selectSingleNode(ADOMDocument: IDOMDocument; const nodePath: WideString): IDOMNode;
var
LDomNodeSelect : IDomNodeSelect;
begin
if not Assigned(ADOMDocument) or not Supports(ADOMDocument.documentElement, IDomNodeSelect, LDomNodeSelect) then
Exit;
//or just LDomNodeSelect:= (ADOMDocument.documentElement as IDOMNodeSelect);
if (DefaultDOMVendor = OpenXML4Factory.Description) then
Tox4DOMNode(LDomNodeSelect).WrapperDocument.WrapperDOMImpl.InitParserAgent;
Result:=LDomNodeSelect.selectNode(nodePath);
end;
[/sourcecode]

[sourcecode language=”delphi” wraplines=”false”]
function SelectNodes(ADOMDocument: IDOMDocument; const nodePath: WideString): IDOMNodeList;
var
LDomNodeSelect : IDomNodeSelect;
begin
if not Assigned(ADOMDocument) or not Supports(ADOMDocument.documentElement, IDomNodeSelect, LDomNodeSelect) then
Exit;
//or just LDomNodeSelect:= (ADOMDocument.documentElement as IDOMNodeSelect);
if (DefaultDOMVendor = OpenXML4Factory.Description) then
Tox4DOMNode(LDomNodeSelect).WrapperDocument.WrapperDOMImpl.InitParserAgent;
Result:=LDomNodeSelect.selectNodes(nodePath);
end;
[/sourcecode]

Now with these changes you will able to evaluate XPath expressions in VCL and FireMonkey Apps using the ADOM vendor.

This is a demo console App tested in Windows and OSX (XE2 and XE4)

[sourcecode language=”delphi” wraplines=”false”]
{$APPTYPE CONSOLE}

uses
{$IFDEF MSWINDOWS}
System.Win.ComObj,
Winapi.ActiveX,
{$ENDIF}
System.SysUtils,
Xml.XMLIntf,
Xml.adomxmldom,
Xml.XMLDom,
Xml.XMLDoc;

function selectSingleNode(ADOMDocument: IDOMDocument; const nodePath: WideString): IDOMNode;
var
LDomNodeSelect : IDomNodeSelect;
begin
if not Assigned(ADOMDocument) or not Supports(ADOMDocument.documentElement, IDomNodeSelect, LDomNodeSelect) then
Exit;
//or just LDomNodeSelect:= (ADOMDocument.documentElement as IDOMNodeSelect);
if (DefaultDOMVendor = OpenXML4Factory.Description) then
Tox4DOMNode(LDomNodeSelect).WrapperDocument.WrapperDOMImpl.InitParserAgent;
Result:=LDomNodeSelect.selectNode(nodePath);
end;

function SelectNodes(ADOMDocument: IDOMDocument; const nodePath: WideString): IDOMNodeList;
var
LDomNodeSelect : IDomNodeSelect;
begin
if not Assigned(ADOMDocument) or not Supports(ADOMDocument.documentElement, IDomNodeSelect, LDomNodeSelect) then
Exit;
//or just LDomNodeSelect:= (ADOMDocument.documentElement as IDOMNodeSelect);
if (DefaultDOMVendor = OpenXML4Factory.Description) then
Tox4DOMNode(LDomNodeSelect).WrapperDocument.WrapperDOMImpl.InitParserAgent;
Result:=LDomNodeSelect.selectNodes(nodePath);
end;

procedure TestXPath;
var
XmlDoc: IXMLDocument;
Root, Book, Author, Publisher : IXMLNode;
LNodeList : IDOMNodeList;
LNode : IDOMNode;
i : Integer;
begin
XmlDoc := TXMLDocument.Create(nil);
XmlDoc.Active := True;
XmlDoc.Options := XmlDoc.Options + [doNodeAutoIndent];
XmlDoc.Version := ‘1.0’;

Root := XmlDoc.CreateNode(‘BookStore’);
Root.Attributes[‘url’] := ‘http://www.amazon.com’;
XmlDoc.DocumentElement := Root;

Book := XmlDoc.CreateNode(‘Book’);
Book.Attributes[‘Name’] := ‘Steve Jobs’;
Author := XmlDoc.CreateNode(‘Author’);
Author.Text := ‘Walter Isaacson’;
Publisher := XmlDoc.CreateNode(‘Publisher’);
Publisher.Text := ‘Simon Schuster (October 24, 2011)’;
Root.ChildNodes.Add(Book);
Book.ChildNodes.Add(Author);
Book.ChildNodes.Add(Publisher);

Book := XmlDoc.CreateNode(‘Book’);
Book.Attributes[‘Name’] := ‘Clean Code: A Handbook of Agile Software Craftsmanship’;
Author := XmlDoc.CreateNode(‘Author’);
Author.Text := ‘Robert C. Martin’;
Publisher := XmlDoc.CreateNode(‘Publisher’);
Publisher.Text := ‘Prentice Hall; 1 edition (August 11, 2008)’;
Root.ChildNodes.Add(Book);
Book.ChildNodes.Add(Author);
Book.ChildNodes.Add(Publisher);

Book := XmlDoc.CreateNode(‘Book’);
Book.Attributes[‘Name’] := ‘Paradox Lost’;
Author := XmlDoc.CreateNode(‘Author’);
Author.Text := ‘Kress, Peter’;
Publisher := XmlDoc.CreateNode(‘Publisher’);
Publisher.Text := ‘Prentice Hall; 1 edition (February 2, 2000)’;
Root.ChildNodes.Add(Book);
Book.ChildNodes.Add(Author);
Book.ChildNodes.Add(Publisher);

Writeln(XmlDoc.XML.Text);

Writeln(‘selectSingleNode’);
LNode:=selectSingleNode(XmlDoc.DOMDocument,’/BookStore/Book[2]/Author["Robert C. Martin"]’);
if LNode<>nil then
Writeln(LNode.firstChild.nodeValue);

Writeln;

Writeln(‘SelectNodes’);
LNodeList:=SelectNodes(XmlDoc.DOMDocument,’//BookStore/Book/Author’);
if LNodeList<>nil then
for i := 0 to LNodeList.length-1 do
Writeln(LNodeList[i].firstChild.nodeValue);
end;

begin
try
ReportMemoryLeaksOnShutdown:=True;
DefaultDOMVendor := OpenXML4Factory.Description;
{$IFDEF MSWINDOWS}CoInitialize(nil);{$ENDIF}
try
TestXPath;
finally
{$IFDEF MSWINDOWS}CoUninitialize;{$ENDIF}
end;
except
{$IFDEF MSWINDOWS}
on E:EOleException do
Writeln(Format(‘EOleException %s %x’, [E.Message,E.ErrorCode]));
{$ENDIF}
on E:Exception do
Writeln(E.Classname, ‘:’, E.Message);
end;
Writeln;
Writeln(‘Press Enter to exit’);
Readln;
end.
[/sourcecode]

OSXXPATH

2 responses

  1. Shouldn’t it be

    function selectSingleNode(ADOMDocument: IDOMDocument; const nodePath: WideString): IDOMNode;
    var
    LDomNodeSelect : IDomNodeSelect;
    begin
    Result := nil;
    if not Assigned(ADOMDocument) or not Supports(ADOMDocument.documentElement, IDomNodeSelect, LDomNodeSelect) then
    Exit;

    else Result might be undefined (although Compiler gives no hint or warning here!).
    Same issue in other functions.

  2. […] The TXMLDocument class allow you to manipulate XML files in VCL and FireMonkey Apps, but this class doesn’t implements a direct way to call the XPath related methods (selectNode, selectNodes) , so you must write a set of helper functions to call…  […]

Leave a Reply