I'm trying to locate the value for <Link role="self">
in the following XML File using a XPath query.
<?xml version="1.0" encoding="utf-8"?>
<Response xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns="http://schemas.microsoft.com/search/local/ws/rest/v1">
<Copyright>Copyright © 2011 Microsoft and its suppliers. All rights reserved. This API cannot be accessed and the content and any results may not be used, reproduced or transmitted in any manner without express written permission from Microsoft Corporation.</Copyright>
<BrandLogoUri>http://spatial.virtualearth.net/Branding/logo_powered_by.png</BrandLogoUri>
<StatusCode>201</StatusCode>
<StatusDescription>Created</StatusDescription>
<AuthenticationResultCode>ValidCredentials</AuthenticationResultCode>
<TraceId>ID|02.00.82.2300|</TraceId>
<ResourceSets>
<ResourceSet>
<EstimatedTotal>1</EstimatedTotal>
<Resources>
<DataflowJob>
<Id>ID</Id>
<Link role="self">https://spatial.virtualearth.net/REST/v1/dataflows/Geocode/ID</Link>
<Status>Pending</Status>
<CreatedDate>2011-03-30T08:03:09.3551157-07:00</CreatedDate>
<CompletedDate xsi:nil="true" />
<TotalEntityCount>0</TotalEntityCount>
<ProcessedEntityCount>0</ProcessedEntityCount>
<FailedEntityCount>0</FailedEntityCount>
</DataflowJob>
</Resources>
</ResourceSet>
</ResourceSets>
</Response>
I was shown an XPath query in a previous post, but I keep getting a unassigned iNode
in the following code.
function TForm1.QueryXMLData(XMLFilename, XMLQuery: string): string;
var
iNode : IDOMNode;
Sel: IDOMNodeSelect;
begin
try
XMLDoc.Active := False;
XMLDoc.FileName := XMLFilename;
XMLDoc.Active := True;
Sel := XMLDoc.DOMDocument as IDomNodeSelect;
Result := '';
iNode := Sel.selectNode('Link[@role = "self"]');
if Assigned(iNode) then
if (not VarisNull(iNode.NodeValue)) then
Result := iNode.NodeValue;
XMLDoc.Active := False;
Except on E: Exception do
begin
MessageDlg(E.ClassName + ': ' + E.Message, mtError, [mbOK], 0);
LogEvent(E.Message);
end;
end;
end;
Any help would be greatly appreciated.
Regards, Pieter
If you want to locate Link anywhere in the document, you’ll have to prefix it with //
; like this:
iNode := Sel.selectNode('//Link[@role = "self"][3]');
This will start searching at the root of the document, and traverse the entire document, looking for a node called Link
matching the specified criteria.
See here for more operators:
http://msdn.microsoft.com/en-us/library/ms256122.aspx
Note that, as Runner suggests, you can also query the full XML path. This will be faster than the //
operator, since it won’t have to blindly search every node.
Edit: Why are you requesting the third matching node (the [3]
bit)? AFAICS, there’s only one; if your real document does have more, and you’re certain you want the third, then it’s OK. Otherwise, remove the [3]
query.
Also, depending on the XML implementation you’re using (vendor and version), you also might have to specify the XML namespace. In MSXML 4 thru 6 (IIRC), you’d have to use
XMLDoc.setProperty('SelectionNamespaces', 'xmlns:ns="http://schemas.microsoft.com/search/local/ws/rest/v1"');
This would mean using that prefix in your queries as well:
iNode := Sel.selectNode('//ns:Link[@role = "self"][3]');
You should write it like this:
iNode := Sel.selectNode('//Link[@role = "self"]');
which will get you the first Link node in the document with attribute role="self" (even if there is more than one).
Or you can go the absolute path:
iNode := Sel.selectNode('/Response/ResourceSets/ResourceSet/Resources/DataflowJob/Link[@role = "self"]');
or even something in between
iNode := Sel.selectNode('//Resources/DataflowJob/Link[@role = "self"]');
In the end I used OmniXML with the following code.
uses
OmniXML, OmniXMLUtils, OmniXMLXPath;
...
function GetResultsURL(Filename: string): string;
var
FXMLDocument: IXMLDocument;
XMLElementList: IXMLNodeList;
XMLNode: IXMLNode;
XMLElement: IXMLElement;
i: integer;
begin
//Create and load the XML document
FXMLDocument := CreateXMLDoc;
FXMLDocument.Load(Filename);
//We are looking for: <Link role="output" name="failed">
XMLElementList := FXMLDocument.GetElementsByTagName('Link');
for i := 0 to Pred(XMLElementList.Length) do
begin
//Check each node and element
XMLNode := XMLElementList.Item[i];
XMLElement := XMLNode as IXMLElement;
if XMLElement.GetAttribute('role') = 'output' then
if Pos('failed', XMLNode.Text) > 0 then
Result := XMLNode.Text;
end;
end;
The XML received looks like this ...
...
<DataflowJob>
<Id>12345</Id>
<Link role="self">https://spatial.virtualearth.net/REST/v1/dataflows/Geocode/12345</Link>
<Link role="output" name="failed">https://spatial.virtualearth.net/REST/v1/dataflows/Geocode/12345/output/failed</Link>
<Status>Completed</Status>
<CreatedDate>2011-04-04T03:57:49.0534147-07:00</CreatedDate>
<CompletedDate>2011-04-04T03:58:43.709725-07:00</CompletedDate>
<TotalEntityCount>1</TotalEntityCount>
<ProcessedEntityCount>1</ProcessedEntityCount>
<FailedEntityCount>1</FailedEntityCount>
</DataflowJob>
...
Martijn mentioned about the Vendor property in a comment to his answer.
The property is in fact called DOMVendor.
Further below is some sample code that shows how that works.
The sample code depends on some helper classes you can find on bo.codeplex.com.
Note that DOMVendor will not tell you what version of MSXML you have, but you can ask it if it has XPath support.
Old MSXML versions (that are still in the field, for instance on plain vanilla Windows 2003 Server installations) won't support XPath
, but support XSLPattern
.
They will happily execute your queries, but sometimes return different results, or barf.
There are some sublte bugs in various sub-versions of MSXML6 too.
You need 6.30., 6.20.1103., 6.20.2003.0 or higher. 6.3 is only available on Windows 7/Windows 2008 Server. The 6.20 flavours on Windows XP and Windows 2003 Server.
Finding out which versions actually work took me a quite some time :-)
This shows you the installed MSXML, in my case msxml6.dll: 6.20.1103.0
:
procedure TMainForm.ShowMsxml6VersionClick(Sender: TObject);
begin
{
Windows 2003 with MSXML 3: msxml3.dll: 8.100.1050.0
windows XP with MSXML 4: msxml4.dll: 4.20.9818.0
Windows XP with MSXML 6 SP1: msxml6.dll: 6.10.1129.0
windows XP with MSXML 6 SP2 (latest):
------------------------
msxml6.dll: 6.20.1103.0
Windows 7 with MSXML 6 SP3:
--------------------------
msxml6.dll: 6.30.7600.16385
}
try
Logger.Log(TmsxmlFactory.msxmlBestFileVersion.ToString());
TmsxmlFactory.AssertCompatibleMsxml6Version();
except
on E: Exception do
begin
Logger.Log('Error');
Logger.Log(E);
end;
end;
end;
This shows the DOMVendor
code, it makes some use of helper classes, you can find that on
procedure TMainForm.FillDomVendorComboBox;
var
DomVendorComboBoxItemsCount: Integer;
Index: Integer;
CurrentDomVendor: TDOMVendor;
DefaultDomVendorIndex: Integer;
CurrentDomVendorDescription: string;
const
NoSelection = -1;
begin
DomVendorComboBox.Clear;
DefaultDomVendorIndex := NoSelection;
for Index := 0 to DOMVendors.Count - 1 do
begin
CurrentDomVendor := DOMVendors.Vendors[Index];
LogDomVendor(CurrentDomVendor);
CurrentDomVendorDescription := CurrentDomVendor.Description;
DomVendorComboBox.Items.Add(CurrentDomVendorDescription);
if DefaultDOMVendor = CurrentDomVendorDescription then
DefaultDomVendorIndex := DomVendorComboBox.Items.Count - 1;
end;
DomVendorComboBoxItemsCount := DomVendorComboBox.Items.Count;
if (DefaultDomVendorIndex = NoSelection) then
begin
if DefaultDOMVendor = NullAsStringValue then
begin
if DomVendorComboBoxItemsCount > 0 then
DefaultDomVendorIndex := 0;
end
else
DefaultDomVendorIndex := DomVendorComboBoxItemsCount - 1;
end;
DomVendorComboBox.ItemIndex := DefaultDomVendorIndex;
end;
procedure TMainForm.LogDomVendor(const CurrentDomVendor: TDOMVendor);
var
CurrentDomVendorDescription: string;
DocumentElement: IDOMElement;
DomDocument: IDOMDocument; // xmldom.IDOMDocument is the plain XML DOM
XmlDocument: IXMLDocument; // XMLIntf.IXMLDocument is the enrichted XML interface to the TComponent wrapper, which has a DOMDocument: IDOMDocument poperty, and allows obtaining XML from different sources (text, file, stream, etc)
XmlDocumentInstance: TXMLDocument; // unit XMLDoc
DOMNodeEx: IDOMNodeEx;
XMLDOMDocument2: IXMLDOMDocument2;
begin
CurrentDomVendorDescription := CurrentDomVendor.Description;
Logger.Log('DOMVendor', CurrentDomVendorDescription);
XmlDocumentInstance := TXMLDocument.Create(nil);
XmlDocumentInstance.DOMVendor := CurrentDomVendor;
XmlDocument := XmlDocumentInstance;
DomDocument := CurrentDomVendor.DOMImplementation.createDocument(NullAsStringValue, NullAsStringValue, nil);
XmlDocument.DOMDocument := DomDocument;
XmlDocument.LoadFromXML('<document/>');
DomDocument := XmlDocument.DOMDocument; // we get another reference here, since we loaded some XML now
DocumentElement := DomDocument.DocumentElement;
if Assigned(DocumentElement) then
begin
DOMNodeEx := DocumentElement as IDOMNodeEx;
Logger.Log(DOMNodeEx.xml);
end;
if IDomNodeHelper.GetXmlDomDocument2(DomDocument, XMLDOMDocument2) then
begin
// XSLPattern versus XPath
// see https://stackoverflow.com/questions/784745/accessing-comments-in-xml-using-xpath
// XSLPattern is 0 based, but XPath is 1 based.
Logger.Log(IDomNodeHelper.SelectionLanguage, string(XMLDOMDocument2.getProperty(IDomNodeHelper.SelectionLanguage)));
Logger.Log(IDomNodeHelper.SelectionNamespaces, string(XMLDOMDocument2.getProperty(IDomNodeHelper.SelectionNamespaces)));
end;
LogDomVendorFeatures(CurrentDomVendor,
['','1.0','2.0', '3.0'],
//http://www.w3.org/TR/DOM-Level-3-Core/introduction.html#ID-Conformance
//http://reference.sitepoint.com/javascript/DOMImplementation/hasFeature
['Core'
,'XML'
,'Events'
,'UIEvents'
,'MouseEvents'
,'TextEvents'
,'KeyboardEvents'
,'MutationEvents'
,'MutationNameEvents'
,'HTMLEvents'
,'LS'
,'LS-Async'
,'Validation'
,'XPath'
]);
end;
procedure TMainForm.LogDomVendorFeatures(const CurrentDomVendor: TDOMVendor; const Versions, Features: array of string);
var
AllVersions: string;
Feature: string;
Line: string;
Supported: Boolean;
SupportedAll: Boolean;
SupportedNone: Boolean;
SupportedVersions: IStringListWrapper;
Version: string;
begin
SupportedVersions := TStringListWrapper.Create();
for Version in Versions do
AddSupportedVersion(Version, SupportedVersions);
AllVersions := Format('All: %s', [SupportedVersions.CommaText]);
for Feature in Features do
begin
SupportedAll := True;
SupportedNone := True;
SupportedVersions.Clear();
for Version in Versions do
begin
Supported := CurrentDomVendor.DOMImplementation.hasFeature(Feature, Version);
if Supported then
AddSupportedVersion(Version, SupportedVersions);
SupportedAll := SupportedAll and Supported;
SupportedNone := SupportedNone and not Supported;
end;
if SupportedNone then
Line := Format('None', [])
else
if SupportedAll then
Line := Format('%s', [AllVersions])
else
Line := Format('%s', [SupportedVersions.CommaText]);
Logger.Log(' ' + Feature, Line);
end;
end;
Delphi XE will show these:
DOMVendor:MSXML
<document/>
SelectionLanguage:XPath
SelectionNamespaces:
Core:None
XML:Any,1.0
Events:None
UIEvents:None
MouseEvents:None
TextEvents:None
KeyboardEvents:None
MutationEvents:None
MutationNameEvents:None
HTMLEvents:None
LS:None
LS-Async:None
Validation:None
XPath:Any,1.0
DOMVendor:ADOM XML v4
?<document></document>
Core:None
XML:None
Events:None
UIEvents:None
MouseEvents:None
TextEvents:None
KeyboardEvents:None
MutationEvents:None
MutationNameEvents:None
HTMLEvents:None
LS:None
LS-Async:None
Validation:None
XPath:None