在 System.Xml.XPath 中启用 XPath2 查询(XPathException:无效令牌)
Enable XPath2 queries in System.Xml.XPath (XPathException: invalid token)
Microsoft System.Xml.XPath 可用于 .NET 4.6 的 nuget-package,声称支持 XPath 1.0 和 2.0。 De documentation 表示描述命名空间:
System.Xml.XPath 命名空间包含 类 定义用于导航和编辑 XML 信息项的游标模型作为 XQuery 1.0 和 XPath 2.0 数据的实例型号.
升级 Visual Studio 后,我的所有项目都升级到框架版本 4.6,但我仍然无法使用最简单的 XPath-2.0 for 表达式。根据 specification,它们应该可以工作。
我无法想象 Microsoft 声称支持它实际上不支持的东西,所以显然我做错了什么。 如何正确使用 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);
}
}
我怀疑是否真的支持 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。
在您的扩展示例中,您想对我们 link 本书的作者做什么。这在 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);
现在 library
是 IEnumerable<KeyValuePair<string,string>>
和 title/writer 对。
Tomalek 正确指出:
- .NET 不支持 XPath 2.0(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 包后有效:
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);
}
}
}
}
Microsoft System.Xml.XPath 可用于 .NET 4.6 的 nuget-package,声称支持 XPath 1.0 和 2.0。 De documentation 表示描述命名空间:
System.Xml.XPath 命名空间包含 类 定义用于导航和编辑 XML 信息项的游标模型作为 XQuery 1.0 和 XPath 2.0 数据的实例型号.
升级 Visual Studio 后,我的所有项目都升级到框架版本 4.6,但我仍然无法使用最简单的 XPath-2.0 for 表达式。根据 specification,它们应该可以工作。
我无法想象 Microsoft 声称支持它实际上不支持的东西,所以显然我做错了什么。 如何正确使用 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);
}
}
我怀疑是否真的支持 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。
在您的扩展示例中,您想对我们 link 本书的作者做什么。这在 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);
现在 library
是 IEnumerable<KeyValuePair<string,string>>
和 title/writer 对。
Tomalek 正确指出:
- .NET 不支持 XPath 2.0(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 包后有效:
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);
}
}
}
}