2010-09-14 42 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