2017-10-18 64 views
1

Microsofts System.Xml.XPath nuget-package,可用於.NET 4.6,聲稱支持XPath 1.0和2.0。 De documentation說descibes命名空間:在System.Xml.XPath中啓用XPath2查詢(XPathException:無效令牌)

System.Xml.XPath命名空間包含定義用於瀏覽和編輯XML信息項的遊標模型的類作爲XQuery 1.0和XPath 2.0數據模型的實例。

升級Visual Studio後,升級和我的所有項目到框架版本4.6我仍然無法獲得最簡單的XPath-2.0 for-expression工作。根據specification,他們應該工作。

我無法想象微軟聲稱支持它實際上不支持的東西,所以顯然我做錯了什麼。 如何正確使用XPath2查詢?

[TestMethod] 
public void TestXPath2() 
{ 
    // The System.Xml.XPath namespace contains the classes that define a cursor model for navigating and editing XML information items as instances of the 
    // XQuery 1.0 and XPath 2.0 Data Model. 

    var expression = "for $x in /Root/Foo/Bar return $x"; 
    var compiledExpression = System.Xml.XPath.XPathExpression.Compile(expression); 
    // throws XPathException: "for ... has an invalid token" 
} 

P.S. 其實我是想什麼,是爲了得到這樣的工作:

[TestMethod] 
    public void TestLibraryForCustomer1() 
    { 
     string xmlFromMessage = @"<Library> 
      <Writer ID=""writer1""><Name>Shakespeare</Name></Writer> 
      <Writer ID=""writer2""><Name>Tolkien</Name></Writer> 
      <Book><WriterRef REFID=""writer1"" /><Title>King Lear</Title></Book> 
      <Book><WriterRef REFID=""writer2"" /><Title>The Hobbit</Title></Book> 
      <Book><WriterRef REFID=""writer2"" /><Title>Lord of the Rings</Title></Book> 
      </Library>"; 

     var titleXPathFromConfigurationFile = "./Title"; 
     var writerXPathFromConfigurationFile = "for $curr in . return /Library/Writer[@ID=$curr/WriterRef/@REFID]/Name"; 

     var library = ExtractBooks(xmlFromMessage, titleXPathFromConfigurationFile, writerXPathFromConfigurationFile).ToDictionary(b => b.Key, b => b.Value); 

     Assert.AreEqual("Shakespeare", library["King Lear"]); 
     Assert.AreEqual("Tolkien", library["The Hobbit"]); 
     Assert.AreEqual("Tolkien", library["Lord of the Rings"]); 
    } 

    [TestMethod] 
    public void TestLibraryForCustomer2() 
    { 
     string xmlFromMessage = @"<Library> 
       <Writer ID=""writer1""> 
        <Name>Shakespeare</Name> 
        <Book><Title>Sonnet 18</Title></Book> 
       </Writer> 
       <Writer ID=""writer2""> 
        <Name>Tolkien</Name> 
        <Book><Title>The Hobbit</Title></Book> 
        <Book><Title>Lord of the Rings</Title></Book> 
       </Writer> 
      </Library>"; 

     var titleXPathFromConfigurationFile = "./Title"; 
     var writerXPathFromConfigurationFile = "../Name"; 

     var library = ExtractBooks(xmlFromMessage, titleXPathFromConfigurationFile, writerXPathFromConfigurationFile).ToDictionary(b => b.Key, b => b.Value); 

     Assert.AreEqual("Shakespeare", library["Sonnet 18"]); 
     Assert.AreEqual("Tolkien", library["The Hobbit"]); 
     Assert.AreEqual("Tolkien", library["Lord of the Rings"]); 
    } 


    public IEnumerable<KeyValuePair<string,string>> ExtractBooks(string xml, string titleXPath, string writerXPath) 
    { 
     var library = XDocument.Parse(xml); 
     foreach(var book in library.Descendants().Where(d => d.Name == "Book")) 
     { 
      var title = book.XPathSelectElement(titleXPath).Value; 
      var writer = book.XPathSelectElement(writerXPath).Value; 
      yield return new KeyValuePair<string, string>(title, writer); 
     } 
    } 
+2

「中的XQuery 1.0和XPath 2.0數據模型」是有關標準的參考 - 這並不意味着xpath 2導航被支持。我無法立即在文檔中找到它只解釋了xpath 1的位置。0支持;如果我能的話,我會把它放在一個答案 – AakashM

回答

1

我懷疑的XPath 2.0查詢語言真正支持。

但是對於像您的要求那樣簡單的事情,XPath 1.0單行就足夠了。

string xml = @"<Root> 
    <Foo ID=""foo1"">One</Foo> 
    <Foo ID=""foo2"">Two</Foo> 
    <Bar><FooRef REFID=""foo2"" /></Bar> 
    </Root>"; 
var doc = XDocument.Parse(xml); 
var matchingFoo = doc.XPathSelectElement("/Root/Foo[@ID = //Bar/FooRef/@REFID]"); 

Assert.AreEqual("Two", matchingFoo.value); 

對於超出XPath 1.0功能的查詢,請嘗試使用LINQ。

在您的擴展示例中,您想要將我們鏈接到書籍的作者。這是很容易在LINQ做加盟,這樣的:

var xmlFromMessage = @"<Library> 
    <Writer ID=""writer1""><Name>Shakespeare</Name></Writer> 
    <Writer ID=""writer2""><Name>Tolkien</Name></Writer> 
    <Book><WriterRef REFID=""writer1"" /><Title>King Lear</Title></Book> 
    <Book><WriterRef REFID=""writer2"" /><Title>The Hobbit</Title></Book> 
    <Book><WriterRef REFID=""writer2"" /><Title>Lord of the Rings</Title></Book> 
    </Library>"; 

var libraryDoc = XDocument.Parse(xmlFromMessage); 

var library = from title in libraryDoc.Descendants("Title") 
    join writer in libraryDoc.Descendants("Writer") 
    on title.Parent.Element("WriterRef").Attribute("REFID").Value equals writer.Attribute("ID").Value 
    select new KeyValuePair<string, string>(title.Value, writer.Value); 

現在libraryIEnumerable<KeyValuePair<string,string>>與標題/寫器配對。

+1

也許你應該讓你的例子更清楚,然後呢?你可能沒有描述你想用什麼實現來實現一個無法解釋的目標,而是描述了你想要實現的目標,並將實現留給辯論(也就是XY問題)。 .NET中不支持XPath 2.0。使用LINQ,或在XPath 1.0中使用另一種方法。 '/ Root/Foo [@ID = // Bar/FooRef/@REFID]'選擇*全部*''具有匹配的「@REFID」的節點。這可以在第二步中過濾到您想要的特定人員。 – Tomalak

+0

對此評論+1。 「.Net中不支持XPath 2.0。」這是我一起工作的答案。我會嘗試一些第三方庫。 (仍然是:NuGet包描述提供它的作用:提供定義光標模型的類,用於導航和編輯可擴展標記語言(XML)信息項作爲XQuery 1.0和XPath 2.0數據模型的實例 – realbart

+1

數據模型和查詢語言是獨立的東西,但是在代碼中我看不到任何跡象表明內置工具不能用來做你想做的事情,而且我甚至可以建議一種方法來做到這一點,如果我只是知道你想要做什麼 – Tomalak

2

Tomalek指出正確:

  • 的XPath 2.0未在.NET支持(在System.Xml.XPath),週期
  • 的數據模型和查詢語言是分開的東西。

所以我解決了它通過使用第三方XPath 2庫,XPath2 nuget package。這允許表達式如

for $c in . return ../Writer[@ID=$c/WriterRef/@REFID]/Name 

請注意,我需要使用從書到作家的相對路徑。這確實工作:

# does not work due to the absolute path 
for $c in . return /Library/Writer[@ID=$c/WriterRef/@REFID]/Name 

以供將來參考:安裝的NuGet pacage後,此代碼的工作:

using Microsoft.VisualStudio.TestTools.UnitTesting; 
using System.Collections.Generic; 
using System.Linq; 
using System.Xml.Linq; 
using Wmhelp.XPath2; 

namespace My.Library 
{ 
    [TestClass] 
    public class WmhelpTests 
    { 
     [TestMethod] 
     public void LibraryTest() 
     { 
      string xmlFromMessage = @"<Library> 
       <Writer ID=""writer1""><Name>Shakespeare</Name></Writer> 
       <Writer ID=""writer2""><Name>Tolkien</Name></Writer> 
       <Book><WriterRef REFID=""writer1"" /><Title>King Lear</Title></Book> 
       <Book><WriterRef REFID=""writer2"" /><Title>The Hobbit</Title></Book> 
       <Book><WriterRef REFID=""writer2"" /><Title>Lord of the Rings</Title></Book> 
      </Library>"; 

      var titleXPathFromConfigurationFile = "./Title"; 
      var writerXPathFromConfigurationFile = "for $curr in . return ../Writer[@ID=$curr/WriterRef/@REFID]/Name"; 

      var library = ExtractBooks(xmlFromMessage, titleXPathFromConfigurationFile, writerXPathFromConfigurationFile).ToDictionary(b => b.Key, b => b.Value); 

      Assert.AreEqual("Shakespeare", library["King Lear"]); 
      Assert.AreEqual("Tolkien", library["The Hobbit"]); 
      Assert.AreEqual("Tolkien", library["Lord of the Rings"]); 
     } 


     [TestMethod] 
     public void TestLibraryForCustomer2() 
     { 
      string xmlFromMessage = @"<Library> 
       <Writer ID=""writer1""> 
        <Name>Shakespeare</Name> 
        <Book><Title>Sonnet 18</Title></Book> 
       </Writer> 
       <Writer ID=""writer2""> 
        <Name>Tolkien</Name> 
        <Book><Title>The Hobbit</Title></Book> 
        <Book><Title>Lord of the Rings</Title></Book> 
       </Writer> 
      </Library>"; 

      var titleXPathFromConfigurationFile = "./Title"; 
      var writerXPathFromConfigurationFile = "../Name"; 

      var library = ExtractBooks(xmlFromMessage, titleXPathFromConfigurationFile, writerXPathFromConfigurationFile).ToDictionary(b => b.Key, b => b.Value); 

      Assert.AreEqual("Shakespeare", library["Sonnet 18"]); 
      Assert.AreEqual("Tolkien", library["The Hobbit"]); 
      Assert.AreEqual("Tolkien", library["Lord of the Rings"]); 
     } 


     public IEnumerable<KeyValuePair<string, string>> ExtractBooks(string xml, string titleXPath, string writerXPath) 
     { 
      var library = XDocument.Parse(xml); 
      foreach (var book in library.Descendants().Where(d => d.Name == "Book")) 
      { 
       var title = book.XPath2SelectElement(titleXPath).Value; 
       var writer = book.XPath2SelectElement(writerXPath).Value; 
       yield return new KeyValuePair<string, string>(title, writer); 
      } 
     } 
    } 
} 
+0

這是一個額外的外部依賴和比使用LINQ更多的代碼。爲什麼不使用LINQ? – Tomalak

+2

@Tomalek爲了能夠配置XML結構。每個客戶都會發送不同結構的文件。從XML實體到我的應用程序類的實際映射是爲每個客戶配置的。 – realbart

+1

這很公平。 – Tomalak