C# XML -->如何获取属于该属性元素的属性值和xpath

发布于 2024-11-10 09:04:29 字数 1816 浏览 3 评论 0原文

我正在寻找一种方法来遍历 XML 文件并获取文本和 xpath 的一些属性。 但我不知道该怎么做。我知道如何从我想要的属性中获取所有文本,但问题是我看不到它所在的 xpath。 有人可以帮助我吗? 代码 =

       // XML settings
        XmlReaderSettings settings = new XmlReaderSettings();
        settings.IgnoreWhitespace = true;
        settings.IgnoreComments = true;                        

        // Loop through the XML to get all text from the right attributes
        using (XmlReader reader = XmlReader.Create(sourceFilepathTb.Text, settings))
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.HasAttributes)
                    {
                        if (reader.GetAttribute("Caption") != null)
                        {                                
                            MessageBox.Show(reader.GetAttribute("Caption"));
                        }                            
                    }
                }
            }
        }

XML:

<?xml version="1.0" encoding="utf-8"?>
<Test Description="Test XML" VersionFormat="123" ProtectedContentText="(Test test)">
    <Testapp>
        <TestappA>
            <A Id="0" Caption="Test 0" />
            <A Id="1" Caption="Test 1" />
            <A Id="2" Caption="Test 2" />
            <A Id="3" Caption="Test 3">
                <AA>
                    <B Id="4" Caption="Test 4" />
                </AA>
            </A>
        </TestappA>
        <AA>
            <Reason Id="5" Caption="Test 5" />
            <Reason Id="6" Caption="Test 6" />
            <Reason Id="7" Caption="Test 7" />
        </AA>
    </Testapp>
</Test>

I'm looking for a way to walk through a XML file and get for a few attributes the text and the xpath.
But I have no idea how to do that. I know how to get all the text from the attributes I want, but the problem with that is that I cann't see the xpath where it is in.
Can some one help me?
The code =

       // XML settings
        XmlReaderSettings settings = new XmlReaderSettings();
        settings.IgnoreWhitespace = true;
        settings.IgnoreComments = true;                        

        // Loop through the XML to get all text from the right attributes
        using (XmlReader reader = XmlReader.Create(sourceFilepathTb.Text, settings))
        {
            while (reader.Read())
            {
                if (reader.NodeType == XmlNodeType.Element)
                {
                    if (reader.HasAttributes)
                    {
                        if (reader.GetAttribute("Caption") != null)
                        {                                
                            MessageBox.Show(reader.GetAttribute("Caption"));
                        }                            
                    }
                }
            }
        }

The XML:

<?xml version="1.0" encoding="utf-8"?>
<Test Description="Test XML" VersionFormat="123" ProtectedContentText="(Test test)">
    <Testapp>
        <TestappA>
            <A Id="0" Caption="Test 0" />
            <A Id="1" Caption="Test 1" />
            <A Id="2" Caption="Test 2" />
            <A Id="3" Caption="Test 3">
                <AA>
                    <B Id="4" Caption="Test 4" />
                </AA>
            </A>
        </TestappA>
        <AA>
            <Reason Id="5" Caption="Test 5" />
            <Reason Id="6" Caption="Test 6" />
            <Reason Id="7" Caption="Test 7" />
        </AA>
    </Testapp>
</Test>

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

浅浅 2024-11-17 09:04:29

恕我直言,LINQ to XML 更简单:

var document = XDocument.Load(fileName);

var captions = document.Descendants()
    .Select(arg => arg.Attribute("Caption"))
    .Where(arg => arg != null)
    .Select(arg => arg.Value)
    .ToList();

[更新]

查找具有 Caption 属性的每个元素的 XPath:

var captions = document.Descendants()
    .Select(arg =>
        new
        {
            CaptionAttribute = arg.Attribute("Caption"),
            XPath = GetXPath(arg)
        })
    .Where(arg => arg.CaptionAttribute != null)
    .Select(arg => new { Caption = arg.CaptionAttribute.Value, arg.XPath })
    .ToList();

private static string GetXPath(XElement el)
{
    if (el.Parent == null)
        return "/" + el.Name.LocalName;

    var name = GetXPath(el.Parent) + "/" + el.Name.LocalName;

    if (el.Parent.Elements(el.Name).Count() != 1)
        return string.Format(@"{0}[{1}]", name, (el.ElementsBeforeSelf(el.Name).Count() + 1));
    return name;
}

IMHO, LINQ to XML is simpler:

var document = XDocument.Load(fileName);

var captions = document.Descendants()
    .Select(arg => arg.Attribute("Caption"))
    .Where(arg => arg != null)
    .Select(arg => arg.Value)
    .ToList();

[Update]

To find XPath for each element that has Caption attribute:

var captions = document.Descendants()
    .Select(arg =>
        new
        {
            CaptionAttribute = arg.Attribute("Caption"),
            XPath = GetXPath(arg)
        })
    .Where(arg => arg.CaptionAttribute != null)
    .Select(arg => new { Caption = arg.CaptionAttribute.Value, arg.XPath })
    .ToList();

private static string GetXPath(XElement el)
{
    if (el.Parent == null)
        return "/" + el.Name.LocalName;

    var name = GetXPath(el.Parent) + "/" + el.Name.LocalName;

    if (el.Parent.Elements(el.Name).Count() != 1)
        return string.Format(@"{0}[{1}]", name, (el.ElementsBeforeSelf(el.Name).Count() + 1));
    return name;
}
暗藏城府 2024-11-17 09:04:29

这是一个开始。您可以练习如何在前面添加斜杠。

using System;
using System.Xml;

namespace ConsoleApplication4 {
    class Program {
        static void Main(string[] args) {
            // XML settings
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreWhitespace = true;
            settings.IgnoreComments = true;

            // Loop through the XML to get all text from the right attributes
            using ( XmlReader reader = XmlReader.Create("Test.xml", settings) ) {
                while ( reader.Read() ) {
                    if ( reader.NodeType == XmlNodeType.Element ) {
                        Console.Write(reader.LocalName + "/"); // <<<<----
                        if ( reader.HasAttributes ) {
                            if ( reader.GetAttribute("Caption") != null ) {
                                Console.WriteLine(reader.GetAttribute("Caption"));
                            }
                        }
                    }
                }
            }
            Console.Write("Press any key ..."); Console.ReadKey();
        }
    }
}

顺便说一句,我尽量避免将代码嵌套得那么深。太难读了。

干杯。基思.


编辑:(几天后)

我终于有了一些属于自己的时间......所以我坐下来“正确”地做了这件事。事实证明这比我最初想象的要困难得多。恕我直言,这种递归解决方案仍然比 XSLT 更容易理解,我发现 XSLT 非常令人困惑;-)

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace ConsoleApplication4 
{
    public class XPathGrepper : IDisposable
    {
        private XmlReader _rdr;
        private TextWriter _out;

        public XPathGrepper(string xmlFilepath, TextWriter output) {
            _rdr = CreateXmlReader(xmlFilepath);
            _out = output;
        }

        private static XmlReader CreateXmlReader(string xmlFilepath) {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreWhitespace = true;
            settings.IgnoreComments = true;
            return XmlReader.Create(xmlFilepath, settings);
        }

        // descends through the XML, printing the xpath to each @attributeName.
        public void Attributes(string attributeName) {
            Attributes(_rdr, attributeName, "/");
        }
        // a recursive XML-tree descent, printing the xpath to each @attributeName.
        private void Attributes(XmlReader rdr, string attrName, string path) {
            // skip the containing element of the subtree (except root)
            if ( "/" != path ) 
                rdr.Read();
            // count how many times we've seen each distinct path.
            var kids = new Histogram();
            // foreach node at-this-level in the tree
            while ( rdr.Read() ) {
                if (rdr.NodeType == XmlNodeType.Element) {
                    // build the xpath-string to this element
                    string nodePath = path + _rdr.LocalName;
                    nodePath += "[" + kids.Increment(nodePath) + "]/";
                    // print the xpath to the Caption attribute of this node
                    if ( _rdr.HasAttributes && _rdr.GetAttribute(attrName) != null ) {
                        _out.WriteLine(nodePath + "@" + attrName);
                    }
                    // recursively read the subtree of this element.
                    Attributes(rdr.ReadSubtree(), attrName, nodePath);
                }
            }
        }

        public void Dispose() {
            if ( _rdr != null ) _rdr.Close();
        }

        private static void Pause() {
            Console.Write("Press enter to continue....");
            Console.ReadLine();
        }

        static void Main(string[] args) {
            using ( var grep = new XPathGrepper("Test.xml", Console.Out) ) {
                grep.Attributes("Caption");
            }
            Pause();
        }

        private class Histogram : Dictionary<string, int>
        {
            public int Increment(string key) {
                if ( base.ContainsKey(key) )
                    base[key] += 1;
                else
                    base.Add(key, 1);
                return base[key];
            }
        }

    }
}

Here's a start. You can workout how to prepend the leading slash.

using System;
using System.Xml;

namespace ConsoleApplication4 {
    class Program {
        static void Main(string[] args) {
            // XML settings
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreWhitespace = true;
            settings.IgnoreComments = true;

            // Loop through the XML to get all text from the right attributes
            using ( XmlReader reader = XmlReader.Create("Test.xml", settings) ) {
                while ( reader.Read() ) {
                    if ( reader.NodeType == XmlNodeType.Element ) {
                        Console.Write(reader.LocalName + "/"); // <<<<----
                        if ( reader.HasAttributes ) {
                            if ( reader.GetAttribute("Caption") != null ) {
                                Console.WriteLine(reader.GetAttribute("Caption"));
                            }
                        }
                    }
                }
            }
            Console.Write("Press any key ..."); Console.ReadKey();
        }
    }
}

And just BTW, I try to avoid nesting code that deeply. Too hard to read.

Cheers. Keith.


EDIT: (days later)

I finally got some time to myself... So I sat down and did this "correctly". It turned out to be a lot harder than I first thought. IMHO, this recursive solution is still easier to groc than XSLT, which I find infinetely confusing ;-)

using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;

namespace ConsoleApplication4 
{
    public class XPathGrepper : IDisposable
    {
        private XmlReader _rdr;
        private TextWriter _out;

        public XPathGrepper(string xmlFilepath, TextWriter output) {
            _rdr = CreateXmlReader(xmlFilepath);
            _out = output;
        }

        private static XmlReader CreateXmlReader(string xmlFilepath) {
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.IgnoreWhitespace = true;
            settings.IgnoreComments = true;
            return XmlReader.Create(xmlFilepath, settings);
        }

        // descends through the XML, printing the xpath to each @attributeName.
        public void Attributes(string attributeName) {
            Attributes(_rdr, attributeName, "/");
        }
        // a recursive XML-tree descent, printing the xpath to each @attributeName.
        private void Attributes(XmlReader rdr, string attrName, string path) {
            // skip the containing element of the subtree (except root)
            if ( "/" != path ) 
                rdr.Read();
            // count how many times we've seen each distinct path.
            var kids = new Histogram();
            // foreach node at-this-level in the tree
            while ( rdr.Read() ) {
                if (rdr.NodeType == XmlNodeType.Element) {
                    // build the xpath-string to this element
                    string nodePath = path + _rdr.LocalName;
                    nodePath += "[" + kids.Increment(nodePath) + "]/";
                    // print the xpath to the Caption attribute of this node
                    if ( _rdr.HasAttributes && _rdr.GetAttribute(attrName) != null ) {
                        _out.WriteLine(nodePath + "@" + attrName);
                    }
                    // recursively read the subtree of this element.
                    Attributes(rdr.ReadSubtree(), attrName, nodePath);
                }
            }
        }

        public void Dispose() {
            if ( _rdr != null ) _rdr.Close();
        }

        private static void Pause() {
            Console.Write("Press enter to continue....");
            Console.ReadLine();
        }

        static void Main(string[] args) {
            using ( var grep = new XPathGrepper("Test.xml", Console.Out) ) {
                grep.Attributes("Caption");
            }
            Pause();
        }

        private class Histogram : Dictionary<string, int>
        {
            public int Increment(string key) {
                if ( base.ContainsKey(key) )
                    base[key] += 1;
                else
                    base.Add(key, 1);
                return base[key];
            }
        }

    }
}
渡你暖光 2024-11-17 09:04:29

简单而精确的 XSLT 解决方案

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:template match="/">
  <xsl:apply-templates select="//@Caption"/>
 </xsl:template>

 <xsl:template match="@Caption">
  <xsl:apply-templates select="." mode="path"/>
  <xsl:value-of select="concat(': ',.,'
')"/>
 </xsl:template>

 <xsl:template match="@Caption" mode="path">
  <xsl:for-each select="ancestor::*">
   <xsl:value-of select="concat('/',name())"/>

   <xsl:variable name="vSiblings" select=
   "count(../*[name()=name(current())])"/>

   <xsl:if test="$vSiblings > 1">
     <xsl:value-of select="
     concat('[',
              count(preceding-sibling::*
                [name()=name(current())]) +1,
            ']'
           )"/>
   </xsl:if>
  </xsl:for-each>

  <xsl:text>/@Caption</xsl:text>
 </xsl:template>
</xsl:stylesheet>

当此转换应用于提供的 XML 文档时

<Test Description="Test XML" VersionFormat="123" ProtectedContentText="(Test test)">
    <Testapp>
        <TestappA>
            <A Id="0" Caption="Test 0" />
            <A Id="1" Caption="Test 1" />
            <A Id="2" Caption="Test 2" />
            <A Id="3" Caption="Test 3">
                <AA>
                    <B Id="4" Caption="Test 4" />
                </AA>
            </A>
        </TestappA>
        <AA>
            <Reason Id="5" Caption="Test 5" />
            <Reason Id="6" Caption="Test 6" />
            <Reason Id="7" Caption="Test 7" />
        </AA>
    </Testapp>
</Test>

生成所需的正确结果

/Test/Testapp/TestappA/A[1]/@Caption: Test 0
/Test/Testapp/TestappA/A[2]/@Caption: Test 1
/Test/Testapp/TestappA/A[3]/@Caption: Test 2
/Test/Testapp/TestappA/A[4]/@Caption: Test 3
/Test/Testapp/TestappA/A[4]/AA/B/@Caption: Test 4
/Test/Testapp/AA/Reason[1]/@Caption: Test 5
/Test/Testapp/AA/Reason[2]/@Caption: Test 6
/Test/Testapp/AA/Reason[3]/@Caption: Test 7

请注意:这是迄今为止提出的唯一解决方案,可为任何单个 Caption 属性生成精确的 XPath 表达式。

/Test/Testapp/TestappA/A/@Caption

选择4个属性节点,而

/Test/Testapp/TestappA/A[2]/@Caption< /code>

仅选择一个属性节点,这才是真正想要的

A simple and precise XSLT solution:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output method="text"/>

 <xsl:template match="/">
  <xsl:apply-templates select="//@Caption"/>
 </xsl:template>

 <xsl:template match="@Caption">
  <xsl:apply-templates select="." mode="path"/>
  <xsl:value-of select="concat(': ',.,'
')"/>
 </xsl:template>

 <xsl:template match="@Caption" mode="path">
  <xsl:for-each select="ancestor::*">
   <xsl:value-of select="concat('/',name())"/>

   <xsl:variable name="vSiblings" select=
   "count(../*[name()=name(current())])"/>

   <xsl:if test="$vSiblings > 1">
     <xsl:value-of select="
     concat('[',
              count(preceding-sibling::*
                [name()=name(current())]) +1,
            ']'
           )"/>
   </xsl:if>
  </xsl:for-each>

  <xsl:text>/@Caption</xsl:text>
 </xsl:template>
</xsl:stylesheet>

when this transformation is applied on the provided XML document:

<Test Description="Test XML" VersionFormat="123" ProtectedContentText="(Test test)">
    <Testapp>
        <TestappA>
            <A Id="0" Caption="Test 0" />
            <A Id="1" Caption="Test 1" />
            <A Id="2" Caption="Test 2" />
            <A Id="3" Caption="Test 3">
                <AA>
                    <B Id="4" Caption="Test 4" />
                </AA>
            </A>
        </TestappA>
        <AA>
            <Reason Id="5" Caption="Test 5" />
            <Reason Id="6" Caption="Test 6" />
            <Reason Id="7" Caption="Test 7" />
        </AA>
    </Testapp>
</Test>

the wanted, correct result is produced:

/Test/Testapp/TestappA/A[1]/@Caption: Test 0
/Test/Testapp/TestappA/A[2]/@Caption: Test 1
/Test/Testapp/TestappA/A[3]/@Caption: Test 2
/Test/Testapp/TestappA/A[4]/@Caption: Test 3
/Test/Testapp/TestappA/A[4]/AA/B/@Caption: Test 4
/Test/Testapp/AA/Reason[1]/@Caption: Test 5
/Test/Testapp/AA/Reason[2]/@Caption: Test 6
/Test/Testapp/AA/Reason[3]/@Caption: Test 7

Do note: This is the only solution presented so far, that generates the exact XPath expression for any single Caption attribute.

/Test/Testapp/TestappA/A/@Caption

selects 4 attribute nodes, whereas:

/Test/Testapp/TestappA/A[2]/@Caption

selects just a single attribute node ans is what really is wanted.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文