使用C#合并一个父节点下的多个节点存在

时间:2011-03-10 12:03:33

标签: c# xml xmlwriter xmldom

我有一个包含多个<Page Pageid="1">个节点的XML。所有这些节点下都有<Para Paraid="1">个节点。我想单独出现<Page>节点,以便属于同一<Para>节点的所有<Page>节点都显示为特定页面的子节点。 e.g。

INPUT:

<Page PageID="**1**">
   <Para ParaID="1">
     <some nodes as child of para>
   </Para>
</Page>
<Page PageID="**2**">
   <Para ParaID="**1**">
     <some nodes as child of para>
   </Para>
</Page>
<Page PageID="**1**"> <!Page 1 encountered again>
   <Para ParaID="**1**">
     <some nodes as child of para>
   </Para>
</Page>
<Page PageID="**3**">
   <Para ParaID="**1**">
     <some nodes as child of para>
   </Para>
</Page>

预期输出:

<Page PageID="**1**">
   <Para ParaID="**1**">
     <some nodes as child of para>
   </Para>
   <Para ParaID="**2**">           <!all <Para> of Page 1 are under single <Page> node>
     <some nodes as child of para>
   </Para>
</Page>
<Page PageID="**2**">
   <Para ParaID="**1**">
     <some nodes as child of para>
   </Para>
</Page>
<Page PageID="**3**">
   <Para ParaID="**1**">
     <some nodes as child of para>
   </Para>
</Page>

2 个答案:

答案 0 :(得分:0)

如果您使用的是.NET 3.5,则可以使用XDocument系列和Linq扩展来轻松完成任务:

var doc1 = XDocument.Parse(stringContainingYourXML);
var groups = doc1.Root.Elements().ToLookup(elt => elt.Attribute("PageID").Value);
var unique = groups.AsEnumerable().Select(group => group.First());
var doc2 = new XDocument(new XElement("root", unique));

对此的解释是我们在第2行创建了一个查找表,其中包含PageID相同值的元素被组合在一起。给定您的示例XML,它需要4个<Page/>元素并创建3个组,其中一个组包含PageID="1"个元素。

在第3行,我们遍历3个组并仅提取第一个XML元素,在第4行,我们将这3个元素保存到新文档中。生成的XML是:

<root>
  <Page PageID="**1**">
    <Para ParaID="1" />
  </Page>
  <Page PageID="**2**">
    <Para ParaID="**1**" />
  </Page>
  <Page PageID="**3**">
    <Para ParaID="**1**" />
  </Page>
</root>

更新:2011/03/12

下面的代码考虑了以自动递增方式将页面的重复实例中的段落要求合并在一起的要求。

与前一个解决方案相比,修改后的解决方案非常糟糕,但是使用ParaID值(尤其是它们所处的格式)非常烦人。我并不为此感到骄傲,但现在是:

using System;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;

namespace SO {
    class Program {
        static void Main(string[] args) {
            var doc1 = XDocument.Parse(xmlstr);
            var groups = doc1.Root.Elements().ToLookup(page => page.Attribute("PageID").Value);
            var doc2 = new XDocument(new XElement("root"));

            foreach (var group in groups) {
                var firstpage = group.First();
                var startindex = firstpage.Elements("Para").Last().Attribute("ParaID").Value;
                var lastindex = int.Parse(Regex.Match(startindex, @"\d+").Value);

                // Duplicate pages...
                firstpage.Add(
                    group.Skip(1)
                         .SelectMany(page => page.Elements("Para"))
                         .Select(
                             para => {
                                 para.Attribute("ParaID").Value = Regex.Replace(
                                     para.Attribute("ParaID").Value,
                                     @"\d+",
                                     m => (++lastindex).ToString()
                                 );
                                 return para;
                             }
                         )
                );

                doc2.Root.Add(firstpage);
            }

            Console.WriteLine(doc2);
            Console.ReadKey(true);
        }
    }
}

答案 1 :(得分:0)

这不是特别有效 - 使用xsl:key的方法更快 - 但在大多数情况下,源文档不会过大。将以下内容添加到identity transform

<!-- filter out Page elements that aren't the first occurrence for their PageID -->
<xsl:template match="Page[@PageID = preceding-sibling::Page/@PageID]"/>

<!-- for each distinct page, copy all Page child nodes with the current PageID -->      
<xsl:template match="Page">
  <xsl:copy>
    <xsl:apply-templates select="@*"/>
    <xsl:apply-templates select="/root/Page[@PageID = current()/@PageID]/node()"/>
  </xsl:copy>
</xsl:template>

请注意,在您尝试将Page元素组合在一起的情况下,您还没有说要做什么,以上基本上忽略了它们;它只会复制具有给定Page的第一个PageID元素的属性。