XML迭代和重新排列 - C#

时间:2010-09-14 01:04:04

标签: c# xml

我有一个类似下面的XML文件:

<?xml version="1.0" ?> 
<System>
  <LP1>
    <Equipment>
      <FromName>Receptacle</FromName> 
      <Wire>1-#10, 1-#10, 1-#10</Wire> 
      <Length>89.8411846136344</Length> 
    </Equipment>
  </LP1>
  <X-1>
    <Equipment>
      <FromName>LP1</FromName> 
      <Wire>3-#3/0, 1-#3/0, 1-#6</Wire> 
      <Length>10.170412377555</Length> 
    </Equipment>   
  </X-1>
  <HP1>
    <Equipment>
      <FromName>X-1</FromName> 
      <Wire>3-#3/0, 1-#3/0, 1-#6</Wire> 
      <Length>8.2423259796908</Length> 
    </Equipment>
    <Equipment>
      <FromName>AH-1</FromName> 
      <Wire>3-#6, 1-#10</Wire> 
      <Length>32.4019419736209</Length> 
    </Equipment>
    <Equipment>
      <FromName>EF-1</FromName> 
      <Wire>3-#12, 1-#12, 1-#12</Wire> 
      <Length>8.33572105849677</Length> 
    </Equipment>
  </HP1>
</System>

我需要阅读它,并重新安排它看起来:

<?xml version="1.0" ?>
  <HP1>
    <Equipment>
      <FromName>X-1</FromName> 
      <Wire>3-#3/0, 1-#3/0, 1-#6</Wire> 
      <Length>8.2423259796908</Length> 
      <Equipment>
        <FromName>LP1</FromName> 
        <Wire>3-#3/0, 1-#3/0, 1-#6</Wire> 
        <Length>10.170412377555</Length> 
        <Equipment>
          <FromName>Receptacle</FromName> 
          <Wire>1-#10, 1-#10, 1-#10</Wire> 
          <Length>89.8411846136344</Length> 
        </Equipment>
      </Equipment>
    </Equipment>
    <Equipment>
      <FromName>AH-1</FromName> 
      <Wire>3-#6, 1-#10</Wire> 
      <Length>32.4019419736209</Length> 
    </Equipment>
    <Equipment>
      <FromName>EF-1</FromName> 
      <Wire>3-#12, 1-#12, 1-#12</Wire> 
      <Length>8.33572105849677</Length> 
    </Equipment>
  </HP1>
</System>

基本上,当设备“FromName”与系统的父元素名称匹配时,原始XML具有单独的元素(LP1,X-1,HP1),我想将其作为子元素放置。

我猜我需要做一些递归函数,但我对C#和编程很新,并且对XML或递归函数没有多少经验。

任何帮助都将不胜感激。

谢谢

3 个答案:

答案 0 :(得分:0)

有很多关于XML文件操作的教程。保存和放大的示例加载(对于教程反向,但两者都有):

http://www.java2s.com/Code/CSharp/XML/Loadxmldocumentfromxmlfile.htm

步骤应该大致......

  • 打开输入文件
  • 将输入加载到XmlNodeList
  • 将输入解析为输出XmlNodeList()
  • 将输出保存到新文件中。

我实际上认为我看到了你想要的东西,经过一番凝视......我不得不亲自尝试一下解析步骤。

答案 1 :(得分:0)

如果您正在使用Xml输出,那么Xslt可能是您最好的选择。您需要将xml和样式表加载到内存中,然后进行相应的处理。类似的东西:

// load the stylesheet
XslTransform stylesheet = new XslTransform();
stylesheet.Load(xsltFilePath);

// load the xml
XPathDocument doc = new XPathDocument(xmlFilePath);

//create the output stream
XmlTextWriter myWriter = new XmlTextWriter("output.xml", null);

// autobots! Transform!
stylesheet.Transform(doc, null, myWriter);        
myWriter.Close();

关于样式表,我假设您将获取xml文件中的最后一个节点,然后从上面嵌套Equipment节点。为了进行递归查找,您需要一个特殊的xslt功能,该功能作为MSXML解析器的一部分提供,特别是ms:node-set。此函数允许您执行xpath查询并返回节点而不是原始文本。

提示:Xslt从下往上处理,向下滚动然后再读。

PS - 我是从记忆中做到的。 Xslt可能很挑剔,所以你可能想要玩这个。

<xsl:stylesheet version="1.0" 
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:ms="urn:schemas-microsoft-com:xslt"
                >

  <!-- wildcard: other content is copied as is -->
  <xsl:template match="node()|@*">
       <xsl:copy>
          <xsl:apply-templates select="node()|@*" />
       </xsl:copy>
  </xsl:template>

  <!-- this is our magic recursive template.  Any element that matches "Equipment"
       will caught and processed here. Everything else will default to our
       Wildcard above -->
  <xsl:template match="Equipment">

      <!-- Read the FromName element into the variable $fromName -->
      <xsl:variable name="fromName" select="FromName/text()" />

      <!-- Manually reconstruct the Equipment Node -->
      <Equipment>
         <!-- copy out FromName, Wire and Length -->
         <xsl:copy-of select="FromName" />
         <xsl:copy-of select="Wire" />
         <xsl:copy-of select="Length" />

         <!-- this is how we recursively pull our Element nodes in, which
              will match on this template -->
         <xsl:apply-templates select="ms:node-set('//' + $fromName')/Equipment" />

      </Equipment>

  </xsl:template>

  <!-- Starting point: Find the last node under system -->
  <xsl:template match="/System/*[last()]">
      <!-- copy the elements and attributes of this node to the output stream -->
      <xsl:copy>
           <!-- match templates on its contents -->
           <xsl:apply-templates select="node()|@*"/>
      </xsl:copy>

  </xsl:template>

</xsl:stylesheet>

答案 2 :(得分:0)

虽然史蒂文建议它可以肯定地压缩成一个单行程,但我选择了一点点扩展以使其更容易理解。当然我还是失败了所以我也会解释一下。

XDocument x = XDocument.Parse(xml);

Func<string, XName> xn = s => XName.Get(s, "");

var systems = x.Elements().First();
var equipments = x.Descendants(xn("Equipment"));
equipments.ToList().ForEach(e =>
{
        string fromName = e.Element(xn("FromName")).Value;
        var found = systems.Element(xn(fromName));
        if (found != null)
        {
            e.Add(found.Elements(xn("Equipment")));
            found.Remove();
    };
});

string result = x.ToString();

假设xml是OP中的字符串,我只是从中解析了一个XDocument。然后,获取XNames的简单快捷方式,因为代码内联更加拥挤。

我们获取System的所有子元素并将其存储以供日后使用;由于缺乏更好的术语,我称之为systems。如果这些元素可能出现多个级别,那么逻辑当然需要进行调整以便可靠地找到它们。

然后我们遍历名称为Equipment(equipments)的所有元素,获取FromName元素值,并在systems中搜索具有相同名称的元素。如果我们找到它,我们只需将它添加到当前元素并将其从父元素中删除;因为元素仍然是x树的所有部分,所以它按预期工作。

Aaand ......完成了。 result是OP发布的预期结果。