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
Другие вопросы по тегам