我有一个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?希望提前获得可能的解决方案。
答案 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>
</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>
我添加了谓词过滤器来指定元素的位置。这样,如果您在同一级别有多个piece
或itemc
元素,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;
}
}
}