2011-04-01 125 views
3

我正在嘗試使用XPath查詢在以下XML文件中查找<Link role="self">的值。delphi xpath xml查詢

<?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> 

我被一個previous post 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; 

任何幫助將不勝感激。

的問候,彼得

+0

的問題都與默認的命名空間;刪除'xmlns =「http://schemas.microsoft.com/search/local/ws/rest/v1」'然後工作就好了:'// Link [@ role =「self」] [1] /節點()'。我不知道你爲什麼在你的例子中有'[3]',因爲文檔中只有一個'Link'節點;我不喜歡解決方案,刪除默認名稱空間聲明,只是似乎很hacky ... – 2011-04-01 10:54:08

+0

我現在已經從查詢中刪除了[3],因爲這顯然是我的錯誤。 – 2011-04-01 11:25:07

+0

[Delphi/MSXML:XPath查詢失敗]的可能重複(http://stackoverflow.com/questions/1519416/delphi-msxml-xpath-queries-fail) – 2011-04-01 15:28:42

回答

1

最後我用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> 

... 
6

你應該寫這樣的:

iNode := Sel.selectNode('//Link[@role = "self"]'); 

這將讓你與屬性的角色文檔中的第一個鏈接點=「自我」(即使有不止一)。

或者你可以去絕對路徑:

iNode := Sel.selectNode('/Response/ResourceSets/ResourceSet/Resources/DataflowJob/Link[@role = "self"]'); 

甚至東西

+0

確實,完整的XPath將具有更好的性能,因爲它不必在文檔中「盲目」搜索。 – Martijn 2011-04-01 10:56:17

+0

你沒有處理默認的命名空間問題。 – 2011-04-01 10:56:50

+0

@Cosmin Prund,true我只提供了正確的XPath語法。正如我所看到的,Martijn已經有了一個正確包含命名空間的答案。 – Runner 2011-04-01 18:20:45

11

之間

iNode := Sel.selectNode('//Resources/DataflowJob/Link[@role = "self"]'); 
如果你想在文檔中的任何地方找到鏈接,你必須用前綴它 //;像這樣:

iNode := Sel.selectNode('//Link[@role = "self"][3]'); 

這將啓動在文檔的根搜索和遍歷整個文件,尋找被稱爲Link節點符合指定條件。

這裏看到更多的運營商: http://msdn.microsoft.com/en-us/library/ms256122.aspx

需要注意的是,作爲亞軍建議,你也可以查詢完整的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]'); 
+0

有沒有辦法確定XML的實現? – 2011-04-01 10:48:02

+0

IIRC,'TXMLDocument'具有供應商屬性或具有供應商屬性的XMLImplementation屬性。沒有德爾福在這裏atm,所以直到晚些時候我才能看到它。 – Martijn 2011-04-01 10:55:14

+1

+1''setProperty('SelectionNamespaces'' – 2011-04-01 11:07:53

1

Martijnhis answer

該屬性實際上稱爲DOMVendor。

下面是一些示例代碼,顯示了這是如何工作的。
示例代碼取決於您可以在bo.codeplex.com上找到的一些幫助程序類。

請注意,DOMVendor不會告訴你你有什麼版本的MSXML,但你可以問它是否有XPath支持。

舊的MSXML版本(仍然在現場,例如在普通的香草Windows 2003 Server安裝中)將不支持XPath,但支持XSLPattern
他們會很高興地執行您的查詢,但有時會返回不同的結果或barf。

MSXML6的各個子版本中也有some sublte bugs
您需要6.30。 ,6.20.1103。,6.20.2003.0或更高版本。 6.3僅在Windows 7/Windows 2008 Server上可用。 Windows XP和Windows 2003 Server上的6.20版本。
找出which versions actually work我花了相當長的一段時間:-)

這說明您已安裝的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://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; 

德爾福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