2010-09-14 44 views
0

我有类似下面的XML文件:XML迭代和rearrangement- C#

<?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> 

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

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

任何帮助,将不胜感激。

谢谢

+1

我只是在等待Skeet显示一行LINQ表达式,完成这一切。 – 2010-09-14 04:26:38

回答

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 ... done。 result是OP发布的期望结果。

+0

为什么'x.Elements()。First()'而不是'X.Root'? – 2010-09-14 21:31:06

+0

为什么定义'xn'而不是使用常规的'XName.Get(s)'或甚至使用's'本身? – 2010-09-14 21:33:19

+0

对于一个不熟悉Linq to XML的人,我想引起人们对XName的命名空间的关注,因为在开始时我曾经遇到过麻烦。当然,我应该使用x.Root,是的。在我的防守中,这已经很晚了。 – 2010-09-15 05:40:57

0

有很多关于XML文件操作的教程。这两个实例保存&负载(反转的教程,但都存在):

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

的步骤应该是大致...

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

我真的觉得我看到你想要什么,经过一番凝视之后...我不得不亲自解析一下这个步骤。

+0

'XmlNodeList'即使不完全过时,也有点过时。 LINQ to XML中'XDocument'相关的类通常更灵活。 – 2010-09-14 04:25:51

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文件中的最后一个节点,然后从上面筑巢的设备节点。为了进行递归查找,您需要一个特殊的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> 
+0

-1用于发布过时的信息。从.NET 2.0开始,'XslTransform'和'new XmlTextWriter()'已被弃用。 – 2010-09-14 04:21:32