如何从 VB.NET 中的 XML 文件构建唯一的树结构

Posted

技术标签:

【中文标题】如何从 VB.NET 中的 XML 文件构建唯一的树结构【英文标题】:How can I build a unique tree structure from an XML file in VB.NET 【发布时间】:2021-12-25 03:13:33 【问题描述】:

我需要使用 XML 文件中的计数创建一个唯一的节点和属性列表。实际上,我需要对结构中等未知的文件中的每个节点/属性组合进行计数。

例如,如果我有一个看起来像这样的 XML 文件;

<level1>
    <item id="a">blah</item>
    <item id="b">blah</item>
    <item id="c">blah</item>
    <level2>
        <item id="d">blah</item>
        <item id="e">blah</item>
        <item id="f">blah</item>
        <level3>
            <item id="g">blah</item>
            <item id="h">blah</item>
            <item id="i">blah</item>
            <item id="k">blah</item>
        </level3>
        <level3>
            <item id="g">blah</item>
            <item id="h">blah</item>
            <item id="i">blah</item>
            <item id="j">blah</item>
        </level3>
    </level2>
    <level2>
        <item id="d">blah</item>
        <item id="e">blah</item>
        <item id="f">blah</item>
        <item id="k">blah</item>
        <level3>
            <item id="g">blah</item>
            <item id="i">blah</item>
        </level3>
        <level3>
            <item id="g">blah</item>
            <item id="h">blah</item>
            <item id="j">blah</item>
        </level3>
    </level2>
<level1>

我可能想要一些看起来像的东西;

<level1 count="1">
    <item id="a">1</item>
    <item id="b">1</item>
    <item id="c">1</item>
    <level2 count="2">
        <item id="d">2</item>
        <item id="e">2</item>
        <item id="f">2</item>
        <item id="k">1</item>
        <level3 count="4">
            <item id="g">4</item>
            <item id="h">3</item>
            <item id="i">3</item>
            <item id="j">2</item>
            <item id="k">1</item>
        </level3>
    </level2>
<level1>

虽然我已将输出表示为任意的 XML 结构。真的只需要一份信息报告。

我的开发环境是使用VS2019的VB.NET。我有读取 XML 树的代码,但我需要帮助设计存储数据的结构,以及搜索它以增加计数器的能力。

    For Each Element As XmlElement In XmlDoc.SelectNodes("//*")
        Console.WriteLine("Processing element with name 0:", Element.Name)
        
        For Each Attribute As XmlAttribute In Element.Attributes

            Console.WriteLine("0: 1", Attribute.Name, Attribute.Value)

        Next

        Console.WriteLine()
    Next

【问题讨论】:

您可以在 XQuery、XSLT(例如 Saxon 10 支持 .NET 框架)或使用 LINQ to XML 中轻松递归地对元素进行分组和计数。使用 DOM 似乎对这项任务没有帮助。 您是否知道任何可以链接到的与我感兴趣的事情类似的示例? 好吧,之前已经在这里使用了分组和递归,但是从那个单个示例中您的要求并不清楚。你想数数吗?所有嵌套级别的所有&lt;item id="a"&gt;blah&lt;/item&gt;?还是仅当它们处于相同深度时? 【参考方案1】:

您可以使用 Saxon 10 HE(或 9.9 或 9.8)在 .NET 框架中运行的示例 XSLT 3 样式表是

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:mf="http://example.com/mf"
    exclude-result-prefixes="#all"
    version="3.0">
  
  <xsl:function name="mf:group" as="node()*">
    <xsl:param name="parents" as="node()*"/>
    <xsl:for-each-group select="$parents/*" composite="yes" group-by="node-name(), @id">
      <xsl:copy>
          <xsl:copy-of select="@*"/>
          <xsl:attribute name="count" select="count(current-group())"/>
          <xsl:sequence select="mf:group(current-group())"/>
        </xsl:copy>      
    </xsl:for-each-group>
  </xsl:function>
  
  <xsl:template match="/">
      <xsl:sequence select="mf:group(.)"/>
  </xsl:template>

  <xsl:output method="xml" indent="yes" />

</xsl:stylesheet>

在线示例位于https://xsltfiddle.liberty-development.net/bESWQsP,输入的输出(关闭根元素的结束标记后)例如

<level1 count="1">
   <item id="a" count="1"/>
   <item id="b" count="1"/>
   <item id="c" count="1"/>
   <level2 count="2">
      <item id="d" count="2"/>
      <item id="e" count="2"/>
      <item id="f" count="2"/>
      <level3 count="4">
         <item id="g" count="4"/>
         <item id="h" count="3"/>
         <item id="i" count="3"/>
         <item id="k" count="1"/>
         <item id="j" count="2"/>
      </level3>
      <item id="k" count="1"/>
   </level2>
</level1>

所以我认为它有你正在寻找的计数。

用于 Saxon 10 的 .NET API 记录在 https://saxonica.com/html/documentation10/dotnetdoc/index.html 并在 https://saxonica.com/documentation10/index.html#!dotnet/dotnetapi 进行了介绍,因此您基本上使用 Processor 对象开始,创建一个 XsltCompiler 将上述 XSLT 文档编译为 @987654331 @ 然后从中创建一个 Xslt30Transformer 以向它提供您首先使用从 Processor 创建的 DocumentBuilder 构建的 XML 输入到 ApplyTemplates 方法或 Transform 方法而不使用 DocumentBuilder,只需FileStream

因此,没有任何错误处理(例如,在编译或 XSLT 执行会出错的情况下)的最短示例是

Imports System.IO
Imports Saxon.Api

Module Module1

    Sub Main()
        Dim processor = New Processor()

        Dim xsltCompiler = processor.NewXsltCompiler()

        Dim xsltExecutable As XsltExecutable

        Using fs = File.OpenRead("recursive-grouping.xsl")
            xsltExecutable = xsltCompiler.Compile(fs)
        End Using

        Dim xslt30Processor = xsltExecutable.Load30()

        Using fs = File.OpenRead("input-sample.xml")
            xslt30Processor.Transform(fs, processor.NewSerializer(Console.Out))
        End Using
    End Sub

End Module

在线https://github.com/martin-honnen/SaxonXSLT30ExampleWithVB。

或者使用 LINQ 和 LINQ to XML 进行分组,至少只要你只需要通过已知的 id 属性进行分组:

Sub Main()
    Dim doc = <?xml version="1.0"?>
              <level1>
                  <item id="a">blah</item>
                  <item id="b">blah</item>
                  <item id="c">blah</item>
                  <level2>
                      <item id="d">blah</item>
                      <item id="e">blah</item>
                      <item id="f">blah</item>
                      <level3>
                          <item id="g">blah</item>
                          <item id="h">blah</item>
                          <item id="i">blah</item>
                          <item id="k">blah</item>
                      </level3>
                      <level3>
                          <item id="g">blah</item>
                          <item id="h">blah</item>
                          <item id="i">blah</item>
                          <item id="j">blah</item>
                      </level3>
                  </level2>
                  <level2>
                      <item id="d">blah</item>
                      <item id="e">blah</item>
                      <item id="f">blah</item>
                      <item id="k">blah</item>
                      <level3>
                          <item id="g">blah</item>
                          <item id="i">blah</item>
                      </level3>
                      <level3>
                          <item id="g">blah</item>
                          <item id="h">blah</item>
                          <item id="j">blah</item>
                      </level3>
                  </level2>
              </level1>

    Console.WriteLine(New XDocument(GroupChildren(doc)))

End Sub

Function GroupChildren(parents As IEnumerable(Of XContainer)) As IEnumerable(Of XContainer)
    Return From child In parents.Elements()
           Group By nameCount = New With 
             Key .Name = child.Name,
             Key .Id = child.@id
     Into group = Group
           Select <<%= nameCount.Name %> Count=<%= group.Count() %> Id=<%= nameCount.Id %>>
                      <%= GroupChildren(group) %>
                  </>
End Function

【讨论】:

我喜欢 Saxon 10 样式表方法的概念。我已经开始看文档了。为了快速开始我的理解,在 VB.NET、C# 中是否有任何代码示例? 下载页面 saxonica.com/download/download_page.xml#resources 有一个资源 zip,其中包含 C# 示例(连同 Java 和 XML,请查看 cs 文件夹)。其中一些也在线saxonica.plan.io/projects/saxon/repository/he/revisions/master/…。恐怕我不知道任何 VB.NET 示例,但 C# 和 VB.NET 之间的大多数“转录”相对容易,甚至可能有在线转换器。

以上是关于如何从 VB.NET 中的 XML 文件构建唯一的树结构的主要内容,如果未能解决你的问题,请参考以下文章

VB.NET修改替换xml文件中的值

如何通过 VB.NET 修改 XML 中的 CData

VB.NET:使用 XDocument 在 XML 文件中添加/编辑/删除 XElement

如何从 C# 读取 VB.NET 项目中的“app.config”文件

如何确定 VB.Net DataRow 中是不是存在列

在vb net中,如何猎取和修改已选定的某一项的值?