XML-запрос delphi xpath
Я пытаюсь найти значение для <Link role="self">
в следующем XML-файле с использованием запроса XPath.
<?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>
Мне показали запрос XPath в предыдущем посте, но я продолжаю получать неназначенные iNode
в следующем коде.
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;
Любая помощь будет принята с благодарностью.
С уважением, Питер
4 ответа
В конце я использовал OmniXML со следующим кодом.
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;
Полученный XML выглядит следующим образом...
...
<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>
...
Если вы хотите найти ссылку в любом месте документа, вам придется поставить перед ней префикс //
; как это:
iNode := Sel.selectNode('//Link[@role = "self"][3]');
Начнется поиск в корне документа и будет проходить весь документ в поисках узла с именем Link
соответствие указанным критериям.
Смотрите здесь, чтобы узнать больше операторов: http://msdn.microsoft.com/en-us/library/ms256122.aspx
Обратите внимание, что, как предлагает Runner, вы также можете запросить полный путь XML. Это будет быстрее, чем //
оператор, так как ему не придется слепо искать каждый узел.
Изменить: почему вы запрашиваете третий соответствующий узел ([3]
немного)? AFAICS, есть только один; если ваш реальный документ имеет больше, и вы уверены, что хотите третий, то все в порядке. В противном случае удалите [3]
запрос.
Кроме того, в зависимости от используемой вами реализации XML (поставщик и версия) вам также может потребоваться указать пространство имен XML. В MSXML с 4 по 6 (IIRC) вам придется использовать
XMLDoc.setProperty('SelectionNamespaces', 'xmlns:ns="http://schemas.microsoft.com/search/local/ws/rest/v1"');
Это также будет означать использование этого префикса в ваших запросах:
iNode := Sel.selectNode('//ns:Link[@role = "self"][3]');
Вы должны написать это так:
iNode := Sel.selectNode('//Link[@role = "self"]');
который даст вам первый узел ссылки в документе с атрибутом role="self" (даже если их несколько).
Или вы можете пойти по абсолютному пути:
iNode := Sel.selectNode('/Response/ResourceSets/ResourceSet/Resources/DataflowJob/Link[@role = "self"]');
или даже что-то среднее
iNode := Sel.selectNode('//Resources/DataflowJob/Link[@role = "self"]');
Мартийн упомянул о собственности Продавца в комментарии к своему ответу.
Собственность фактически называется DOMVendor.
Ниже приведен пример кода, который показывает, как это работает.
Пример кода зависит от некоторых вспомогательных классов, которые вы можете найти на bo.codeplex.com.
Обратите внимание, что DOMVendor не скажет вам, какая у вас версия MSXML, но вы можете спросить ее, поддерживает ли она XPath.
Старые версии MSXML (которые все еще находятся в полевых условиях, например, в обычных установках Windows 2003 Server) не будут поддерживать XPath
, но поддержка XSLPattern
,
Они с радостью выполнят ваши запросы, но иногда будут давать разные результаты, или barf.
В некоторых под-версиях MSXML6 также есть ошибки.
Вам нужно 6.30.6.20.1103., 6.20.2003.0 или выше. 6.3 доступен только в Windows 7/Windows 2008 Server. Разновидности 6.20 в Windows XP и Windows 2003 Server.
Выяснение того, какие версии действительно работают, заняло у меня довольно много времени:-)
Это показывает вам установленный MSXML, в моем случае 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;
Это показывает DOMVendor
код, он использует некоторые вспомогательные классы, вы можете найти это на
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://stackru.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 покажет это:
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