简体   繁体   中英

How to flatten a multi level XML into a single level XML using c# code LINQ

I went through certain posts about flattening an XML structure so every element and it's values were turned into attributes on the root element. However, my requirement is to flatten the input XML as per the below example for which I could not find any help.

I have got an XML structure like below,

<RATES ID="1" RatesEffectivateDate="27/02/2014">
  <Type Name="Type1" Code="A">
    <Del ID="D1">
      <Field1>10</Field1>
      <Field2>20</Field2>
      <Field3>30</Field3>
      <Field4>40</Field4>
    </Del>
    <Del ID="D2">
      <Field1>50</Field1>
      <Field2>60</Field2>
      <Field3>70</Field3>
      <Field4>80</Field4>
    </Del>
  </Type>
  <Type Name="Type2" Code="B">
    <Del ID="D1">
      <Field1>110</Field1>
      <Field2>120</Field2>
      <Field3>130</Field3>
      <Field4>140</Field4>
    </Del>
    <Del ID="D3">
      <Field1>150</Field1>
      <Field2>160</Field2>
      <Field3>170</Field3>
      <Field4>180</Field4>
    </Del>
  </Type>
</RATES>

This needs to be normalised to the format below,

<RATES>
  <RATE>
    <ID>Type1</ID>
    <Code>A</Code>
    <DelID>D1</DelID>
    <Field1>10</Field1>
  </RATE>
  <RATE>
    <ID>Type1</ID>
    <Code>A</Code>
    <DelID>D1</DelID>
    <Field2>20</Field2>
  </RATE>
  <RATE>
    <ID>Type1</ID>
    <Code>A</Code>
    <DelID>D1</DelID>
    <Field3>30</Field3>
  </RATE>
  <RATE>
    <ID>Type1</ID>
    <Code>A</Code>
    <DelID>D1</DelID>
    <Field4>40</Field4>
  </RATE>
  <RATE>
    <ID>Type1</ID>
    <Code>A</Code>
    <DelID>D2</DelID>
    <Field1>50</Field1>
  </RATE>
  <RATE>
    <ID>Type1</ID>
    <Code>A</Code>
    <DelID>D2</DelID>
    <Field2>60</Field2>
  </RATE>
  <RATE>
    <ID>Type1</ID>
    <Code>A</Code>
    <DelID>D2</DelID>
    <Field3>70</Field3>
  </RATE>
  <RATE>
    <ID>Type1</ID>
    <Code>A</Code>
    <DelID>D2</DelID>
    <Field4>80</Field4>
  </RATE>
  <RATE>
    <ID>Type2</ID>
    <Code>B</Code>
    <DelID>D1</DelID>
    <Field1>110</Field1>
  </RATE>
  <RATE>
    <ID>Type2</ID>
    <Code>B</Code>
    <DelID>D1</DelID>
    <Field2>120</Field2>
  </RATE>
  <RATE>
    <ID>Type2</ID>
    <Code>B</Code>
    <DelID>D1</DelID>
    <Field3>130</Field3>
  </RATE>
  <RATE>
    <ID>Type2</ID>
    <Code>B</Code>
    <DelID>D1</DelID>
    <Field4>140</Field4>
  </RATE>
  <RATE>
    <ID>Type2</ID>
    <Code>B</Code>
    <DelID>D3</DelID>
    <Field1>50</Field1>
  </RATE>
  <RATE>
    <ID>Type2</ID>
    <Code>B</Code>
    <DelID>D3</DelID>
    <Field2>160</Field2>
  </RATE>
  <RATE>
    <ID>Type2</ID>
    <Code>B</Code>
    <DelID>D3</DelID>
    <Field3>170</Field3>
  </RATE>
  <RATE>
    <ID>Type2</ID>
    <Code>B</Code>
    <DelID>D3</DelID>
    <Field4>180</Field4>
  </RATE>
</RATES>

Please suggest on this as I am new to LINQ. Thanks!

I don't know if there are any build-in functions to flaten the XML, but I'm very concerned they can fit your needs, because some attributes must be ignored according to your example.

You better use LINQ to XML which allows transforming such documents very easy and with not a lot of code. See my solution:

string origXml = "<RATES ID=\"1\" RatesEffectivateDate=\"27/02/2014\">\n  <Type Name=\"Type1\" Code=\"A\">\n    <Del ID=\"D1\">\n      <Field1>10</Field1>\n      <Field2>20</Field2>\n      <Field3>30</Field3>\n      <Field4>40</Field4>\n    </Del>\n    <Del ID=\"D2\">\n      <Field1>50</Field1>\n      <Field2>60</Field2>\n      <Field3>70</Field3>\n      <Field4>80</Field4>\n    </Del>\n  </Type>\n  <Type Name=\"Type2\" Code=\"B\">\n    <Del ID=\"D1\">\n      <Field1>110</Field1>\n      <Field2>120</Field2>\n      <Field3>130</Field3>\n      <Field4>140</Field4>\n    </Del>\n    <Del ID=\"D3\">\n      <Field1>150</Field1>\n      <Field2>160</Field2>\n      <Field3>170</Field3>\n      <Field4>180</Field4>\n    </Del>\n  </Type>\n</RATES>";
var xDoc = XDocument.Parse(origXml);

var resDoc = new XDocument(
    new XElement("RATES",
                    xDoc.Element("RATES")
                        .Elements("Type")
                        .SelectMany(typeEl =>
                                    typeEl.Elements("Del")
                                        .SelectMany(delEl =>
                                                    delEl.Elements()
                                                        .Select(fieldEl =>
                                                                new XElement("RATE",
                                                                                new XElement("ID", typeEl.Attribute("Name").Value),
                                                                                new XElement("Code", typeEl.Attribute("Code").Value),
                                                                                new XElement("DelID", delEl.Attribute("ID").Value),
                                                                                new XElement(fieldEl.Name, fieldEl.Value)))))
        ));

resDoc.Save("transformedDoc.xml", SaveOptions.None);

The output is exactly what you provided for your example.

You can do it like this:

var xmlDocument = XDocument.Load("path");

var rates = new List<XElement>();

foreach (var type in xmlDocument.Descendants("Type"))
{
    foreach (var del in type.Elements("Del"))
    {
          foreach (var field in del.Elements())
          {
              XElement rate = new XElement("RATE",
                        new XElement("ID", (string) type.Attribute("Name")),
                        new XElement("Code", (string) type.Attribute("Code")),
                        new XElement("DelID", (string) del.Attribute("ID")),
                        new XElement(field.Name, (string) field));
              rates.Add(rate);
          }
    }
}

XElement root = new XElement("RATES");
root.Add(rates);
root.Save("newFile.xml");

Another way using LINQ instead of loops but I think this is less readable

var xmlDocument = XDocument.Load("path");

var newXML = new XElement("RATES",
            xmlDocument.Descendants()
                .Where(x => x.Name.ToString().StartsWith("Field"))
                .Select(
                    x =>
                        new XElement("RATE",
                            new XElement("ID", (string) x.Parent.Parent.Attribute("Name")),
                            new XElement("Code", (string) x.Parent.Parent.Attribute("Code")),
                            new XElement("DelID", (string) x.Parent.Attribute("ID")),
                            new XElement(x.Name, (string) x))));

newXML.Save("newFile.xml");

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM