簡體   English   中英

XPath查詢以按ID查找(查找)另一個元素

[英]XPath query to find (lookup) another element by ID

我正在編寫使用XPath查詢來解析XML文件的類。 XML可能看起來像這樣:

<?xml version="1.0" encoding="UTF-8"?>
<Doc>
    <Name id="aa">Alice</Name>
    <Name id="bb">Bob</Name>
    <Name id="cc">Candice</Name>
    <Person nameid="aa"></Person>
    <Person nameid="bb"></Person>
    <Person nameid="aa"></Person>
</Doc>

所需的輸出是:

Alice
Bob
Alice

我正在使用C#來解析人員:

// these are dynanically defined elsewhere.
const string personXPath = "/Doc/Person";
const string nameXPath = "/Doc/Name[@id=current()/@nameid]"; // <== modify this line

void ParseXDocument(XDocument doc)
{
    foreach (var personElement in doc.XPathSelectElements(personXPath))
    {
        var nameElement = personElement.XPathSelectElement(nameXPath);
        Console.WriteLine(nameElement.Value);
    }
}

僅通過修改nameXPath變量就能做到嗎? (我的軟件不應該“知道” XML結構,唯一將XML映射到我自己的類的是x-path,它們是可配置的。)

另一個例子:

[TestMethod]
public void TestLibrary()
{
    string xmlFromMessage = @"<Library>
        <Writer ID=""writer1""><Name>Shakespeare</Name></Writer>
        <Writer ID=""writer2""><Name>Tolkien</Name></Writer>
        <Book><WriterRef REFID=""writer1"" /><Title>Sonnet 18</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 = "??? what to put here ???";

    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獲得的值放在第二個表達式中。

const string personXPath = "/Doc/Person";
const string nameXPath = "/Doc/Name[@id='{0}']";


foreach (var personElement in doc.XPathSelectElements(personXPath))
{
    var nameid = personElement.Attribute("nameid").Value;
    var nameElement = doc.XPathSelectElement(string.Format(nameXPath, nameid));
    Console.WriteLine(nameElement.Value);
}

新答案。 下面的舊答案

Sombody正確指出:

  • .NET期間不支持XPath 2.0
  • 數據模型和查詢語言是分開的東西。

因此,我通過使用第三方XPath 2庫XPath2 nuget包來解決了這個問題。 這允許像這樣的表達式

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]
    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"]);
        }

        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);
            }
        }
    }
}

低於我的老答案

我使用了一個骯臟的修復程序:在我的xpath中,將“ current()”替換為實際值。 這樣,當前功能的行為類似於xslt-standard

class MyClass
{

    // these are dynanically defined elsewhere.
    const string personXPath = "/Doc/Person";
    const string nameXPath = "/Doc/Name[@id=current()/@nameid]"; 
    XElement _node;

    void ParseXDocument(XDocument doc)
    {
        foreach (var personElement in doc.XPathSelectElements(personXPath))
        {
            _node = personElement; // my actual code is a bit cleaner
            var nameElement = personElement.XPathSelectElement(PreParse(nameXPath));
            Console.WriteLine(nameElement.Value);
        }
    }

    /// <summary>
    /// Pre-evaluates calls to current()
    /// </summary>
    /// <param name="xpath"></param>
    /// <returns></returns>
    private string PreParse(string xpath)
    {
        var sb = new StringBuilder();
        foreach (var part in Tokenize(xpath))
        {
            if (part.Trim().StartsWith("current()"))
            {
                var query = part.Replace("current()", ".");
                sb.Append("'")
                    .Append(EvaluateXPath(query))
                    .Append("'");
            }
            else
            {
                sb.Append(part);
            }
        }
        return sb.ToString();
    }

    private IEnumerable<string> Tokenize(string path)
    {
        var begin = 0;
        for (var i = 0; i < path.Length; i++)
        {
            if ("[=]".Contains(path[i]))
            {
                yield return path.Substring(begin, i - begin);
                yield return path[i].ToString();
                begin = i + 1;
            }
        }
        yield return path.Substring(begin);
    }

    private string EvaluateXPath(string xpath)
    {
        var result = _node.XPathEvaluate(xpath);
        if (result is IEnumerable)
            foreach (var node in (IEnumerable)result)
                return (node as XElement)?.Value ?? (node as XAttribute).Value;
        return string.Format(CultureInfo.InvariantCulture, "{0}", result);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM