【问题标题】:Return XPath for each XML text element返回每个 XML 文本元素的 XPath
【发布时间】:2015-04-30 16:29:47
【问题描述】:

我想为以下 XML 中的每个文本元素返回一个 XPath 或类似内容。 我试过XPathNodeIterator,但似乎只返回指定节点级别下的节点。如何获取所有节点和子节点并返回如下对象列表?

String exp = "/*/*/child::*";
XPathNodeIterator NodeIter = navigator.Select(exp);

XML:

<div>
    <p>Title</p>
    <ul>
       <li>Features</li>
    </ul>
    <ul>
       <li>Name</li>
       <li>Age</li>
       <li>Gender</li>
    </ul>
    <h2>Comments</h2>
    <p>Bill</p>
    <p>Link</p>
</div>

期望的结果: 我想得到一个类似(div/p[1], Title), (div/ul[1]/li[1], Features), (div/ul[2]/li[1], Name), (div/ul[2]/li[2], Age), (div/ul[2]/li[3], Gender), (div/h2[1], Comments), (div/p[2], Bill), (div/p[3], Link)的列表

【问题讨论】:

  • 从那以后是[0] XPath?使用 XPath 的索引从 1 开始。如果你想选择不包含其他元素的元素,那么//*[not(*)] 应该这样做。至于生成 XPath 表达式,你想用 XML 和命名空间做什么?
  • 你想要ui而不是ul
  • 我建议你尝试一下递归,如果你遇到问题,我们会帮助你。
  • 我不确定我是否理解您的问题。您是否尝试 1) 查找所有具有文本值的 XmlElement 节点?遍历XmlElement 层次结构并为每个唯一指定它的 XPath 查询生成一个 XPath 查询?
  • @Chuck Savage 这是一个错字,应该是 ul。

标签: c# xml xpath


【解决方案1】:

我找不到可以为您提供所需路径的内置方法。但是我能够创建一个可以解决问题的递归函数。这是我想出的代码:

    private void button1_Click(object sender, EventArgs e)
    {
        string xmlText = textBox1.Text;

        String exp = "//text()";
        XmlDocument xml = new XmlDocument();
        xml.LoadXml(xmlText);

        //Writes the text out to a textbox
        foreach (XmlNode x in xml.SelectNodes(exp))
            textBox2.AppendText("(" + GetPath(x) + ", " + x.InnerText + ")\n");
    }

    string GetPath(XmlNode nd)
    {
        if (nd.ParentNode != null && nd.NodeType == XmlNodeType.Text)
        {
            return GetPath(nd.ParentNode);
        }
        else if (nd.ParentNode != null && nd.NodeType != XmlNodeType.Text)
        {
            var index = nd.ParentNode.ChildNodes.Cast<XmlNode>().ToList().IndexOf(nd);
            string path = GetPath(nd.ParentNode);
            path += (path != "") ? "/" : "";
            return string.Format("{0}{1}[{2}]", path, nd.Name, index);
        }
        else return "";
    }

我在Form 上测试它,因此是按钮单击事件。使用//text() 获取所有文本节点是很容易的部分。想出一个递归函数来构建路径比我预期的要困难一些。我花了一点时间才发现,通过将ParentNode.ChildNodes 转换为XmlNode 的集合,然后转换为列表,我们可以使用ListIndexOf() 方法来获取索引。

结果:

(div[0]/p[0], Title)
(div[0]/ul[1]/li[0], Features)
(div[0]/ul[2]/li[0], Name)
(div[0]/ul[2]/li[1], Age)
(div[0]/ul[2]/li[2], Gender)
(div[0]/h2[3], Comments)
(div[0]/p[4], Bill)
(div[0]/p[5], Link)

我看到了一个警告,因为我不知道您将使用它来做什么应用程序,但是如果您要使用它从 HTML 中获取元素,LoadXML() 函数可能会中断。 “有效”的 HTML 不一定是有效的 XML,加载可能会失败。

【讨论】:

  • 感谢 jwatts1980。您的代码有效。我正在修改您的解决方案,看看是否可以使用 XDocument 来完成。
【解决方案2】:

只需在 .NET 中运行此转换(使用 XslCompiledTransform):

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:output omit-xml-declaration="yes" indent="yes"/>
  <xsl:strip-space elements="*"/>

  <xsl:variable name="vApos">'</xsl:variable>

  <xsl:template match="text()">
     <xsl:apply-templates select="ancestor-or-self::*" mode="path"/>
     <xsl:value-of select="concat('=',$vApos,.,$vApos)"/>
     <xsl:text>&#xA;</xsl:text>
  </xsl:template>

  <xsl:template match="*" mode="path">
    <xsl:value-of select="concat('/',name())"/>
    <xsl:variable name="vnumPrecSiblings" select=
      "count(preceding-sibling::*[name()=name(current())])"/>
    <xsl:if test="$vnumPrecSiblings or following-sibling::*[name()=name(current())]">
        <xsl:value-of select="concat('[', $vnumPrecSiblings +1, ']')"/>
    </xsl:if>
  </xsl:template>
</xsl:stylesheet>

应用于提供的源 XML 文档时

<div>
    <p>Title</p>
    <ul>
       <li>Features</li>
    </ul>
    <p/>
    <ul>
       <li>Name</li>
       <li>Age</li>
       <li>Gender</li>
    </ul>
    <h2>Comments</h2>
    <p>Bill</p>
    <p>Link</p>
</div>

产生想要的正确结果

/div/p[1]='Title'
/div/ul[1]/li='Features'
/div/ul[2]/li[1]='Name'
/div/ul[2]/li[2]='Age'
/div/ul[2]/li[3]='Gender'
/div/h2='Comments'
/div/p[3]='Bill'
/div/p[4]='Link'

【讨论】:

  • 感谢 Dimitre Novatchev。我测试了它,它也有效。
猜你喜欢
  • 2020-09-27
  • 2012-03-23
  • 2018-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-25
  • 1970-01-01
相关资源
最近更新 更多