为每个XML文本元素返回XPath

时间:2015-04-30 16:29:48

标签: c# xml xpath

我想为以下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)

之类的列表

2 个答案:

答案 0 :(得分: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的集合,然后转换为列表,我们可以使用IndexOf()的{​​{1}}方法来获取这一点索引。

结果:

List

我看到了一个警告,因为我不知道你将使用它的应用程序,但是如果你打算使用它来从HTML获取元素,那么(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) 功能可能会破裂。 &#34;有效&#34; HTML不一定是有效的XML,加载可能会失败。

答案 1 :(得分:1)

只需在.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'