有没有办法使用c#或vb从xml递归查找最内层节点

时间:2009-12-30 06:20:27

标签: .net xml xpath

我有一个XML文件说

  <items>
      <item1>
        <piece>1300</piece>
        <itemc>665583</itemc> 
      </item1>
      <item2>
        <piece>100</piece>
        <itemc>665584</itemc>
      </item2>
    </items>

我正在尝试编写一个c#应用程序来获取内部大多数节点的所有x路径,例如:

items/item1/piece
items/item1/itemc
items/item2/piece
items/item2/itemc

有没有办法使用C#或VB?希望提前获得可能的解决方案。

7 个答案:

答案 0 :(得分:15)

//*[not(*)]

是XPath,用于查找没有子节点的所有子元素,因此您可以执行类似

的操作
doc.SelectNodes("//*[not(*)]")

但我不太确定.Net API所以请查看。

参考

// --> descendant (not only children)
*  --> any name
[] --> predicate to evaluate
not(*) --> not having children

答案 1 :(得分:4)

你去了:

static void Main()
{
   XmlDocument doc = new XmlDocument();
   doc.Load(@"C:\Test.xml");

   foreach (XmlNode node in doc.DocumentElement.ChildNodes)
   {
        ProcesNode(node, doc.DocumentElement.Name);
   }
}


    private void ProcesNode(XmlNode node, string parentPath)
    {
        if (!node.HasChildNodes
            || ((node.ChildNodes.Count == 1) && (node.FirstChild is System.Xml.XmlText)))
        {
            System.Diagnostics.Debug.WriteLine(parentPath + "/" + node.Name);
        }
        else
        {
            foreach (XmlNode child in node.ChildNodes)
            {
                ProcesNode(child, parentPath + "/" + node.Name);
            }
        }
    }

以上代码将为任何类型的文件生成所需的输出。请根据需要添加检查。 主要部分是我们忽略输出中的Text节点(节点内的Text)。

答案 2 :(得分:2)

为了稍微扩展helios的答案,您可以使用[text()]对xpath进行质量调整,以使特定于具有text()节点的节点:

// XDocument
foreach(XElement textNode in xdoc.XPathSelectElements("//*[not(*)][text()]"))
{
    Console.WriteLine(textNode.Value);
}

// XmlDocument
foreach(XmlText textNode in doc.SelectNodes("//*[not(*)]/text()"))
{
    Console.WriteLine(textNode.Value);
}

答案 3 :(得分:2)

这是一个 XSLT 解决方案,可为每个最内层元素生成 XPATH 表达式。

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:template match="/">
        <xsl:apply-templates />
    </xsl:template>

    <!--Match on all elements that do not contain child elements -->
    <xsl:template match="//*[not(*)]">
        <!--look up the node tree and write out:
           - a slash
           - the name of the element
           - and a predicate filter for the position of the element at each step -->
        <xsl:for-each select="ancestor-or-self::*">
            <xsl:text>/</xsl:text>
            <xsl:value-of select="local-name()"/>
            <!--add a predicate filter to specify the position, in case there are more than one element with that name at that step -->
            <xsl:text>[</xsl:text>
            <xsl:value-of select="count(preceding-sibling::*[name()=name(current())])+1" />
            <xsl:text>]</xsl:text>
        </xsl:for-each>  
        <!--Create a new line after ever element -->
        <xsl:text>&#xA;</xsl:text>
    </xsl:template>

<!--override default template to prevent extra whitespace and carriage return from being copied into the output-->
<xsl:template match="text()" />

</xsl:stylesheet>

我添加了谓词过滤器来指定元素的位置。这样,如果您在同一级别有多个pieceitemc元素,XPATH将指定正确的元素。

所以,而不是:

items/item1/piece
items/item1/itemc
items/item2/piece
items/item2/itemc

它产生:

/items[1]/item1[1]/piece[1]
/items[1]/item1[1]/itemc[1]
/items[1]/item2[1]/piece[1]
/items[1]/item2[1]/itemc[1]

答案 4 :(得分:1)

下面的代码查找文档中的所有叶元素,并为每个叶元素输出一个XPath表达式,该表达式将明确导航到文档根目录中的元素,包括每个节点步骤中的谓词,以消除具有相同名称的元素之间的歧义:

static void Main(string[] arguments)
{
    XDocument d = XDocument.Load("xmlfile1.xml");

    foreach (XElement e in d.XPathSelectElements("//*[not(*)]"))
    {
        Console.WriteLine("/" + string.Join("/",
            e.XPathSelectElements("ancestor-or-self::*")
                .Select(x => x.Name.LocalName 
                    + "[" 
                    + (x.ElementsBeforeSelf()
                        .Where(y => y.Name.LocalName == x.Name.LocalName)
                        .Count() + 1)
                    + "]")
                .ToArray()));            
    }

    Console.ReadKey();
}

例如,此输入:

<foo>
  <bar>
    <fizz/>
    <baz>
      <bat/>
    </baz>
    <fizz/>
  </bar>
  <buzz></buzz>
</foo>

生成此输出:

/foo[1]/bar[1]/fizz[1]
/foo[1]/bar[1]/baz[1]/bat[1]
/foo[1]/bar[1]/fizz[2]
/foo[1]/buzz[1]

答案 5 :(得分:0)

这是未经测试的,并且需要完成一些工作才能获得编译,但是你想要这样的东西吗?

class Program
{
    static void Main()
    {
        XmlDocument xml = new XmlDocument();
        xml.Load("test.xml");

        var toReturn = new List<string>();
        GetPaths(string.Empty, xml.ChildNodes[0], toReturn);
    }

    public static void GetPaths(string pathSoFar, XmlNode node, List<string> results)
    {
        string scopedPath = pathSoFar + node.Name + "/";

        if (node.HasChildNodes)
        {
            foreach (XmlNode itemNode in node.ChildNodes)
            {
                GetPaths(scopedPath, itemNode, results);
            }
        }
        else
        {
            results.Add(scopedPath);
        }
    }
}

对于大块的xml,虽然它可能不是非常有效的内存。

答案 6 :(得分:0)

也许不是最快的解决方案,但它显示允许将任意XPath表达式用作选择器,对我而言,这似乎也最清楚地表达了代码的意图。

class Program
{
    static void Main(string[] args)
    {
        XmlDocument xml = new XmlDocument();
        xml.Load("test.xml");

        IEnumerable innerItems = (IEnumerable)e.XPathEvaluate("//*[not(*)]");
        foreach (XElement innerItem in innerItems)
        {
            Console.WriteLine(GetPath(innerItem));
        }
    }

    public static string GetPath(XElement e)
    {
        if (e.Parent == null)
        {
            return "/" + e.Name;
        }
        else
        {
            return GetPath(e.Parent) + "/" + e.Name;
        }
    }
}