0

I'm just trying to understand Linq and I am trying to do something that seems very simple, but I can't get it to output the way I would like. I have been stuck on this for days trying various different methods I just can't get it right.

So I have a class EarObs, it has members: eventID, icaoId, frm, sta, db.

I'm trying to build an XML document from a List. I want the XML document to look like so:

<EarObs EventId = "123456789">
    <icao icaoID = "0001">
        <frm frm = "01">
            <sta sta = "00">
                <db>87</db>
                <hz>99</hz>
            </sta>
            <sta station = "01">
                <db>79</db>
                <hz>99</hz>
            </sta>
        </frm>
        <frm frm = "02">
        ................
        </frm>
    </icao>
</EarObs>

And this would continue all the way down keeping the same order if there was more than one frame or more than one code etc.

So this is what I have been trying most recently but it still does not output they way I would like, Obs get repeated and I do not know where I am going wrong.

string eventGUID = "eventGUID";

List<EarObs> frameObsList = new List<EarObs>();
for (int frm = 2; frm > 0; frm--)
{
    for (int sta = 5; sta > 0; sta--)
    {
        frameObsList.Add(new EarObs("KAPF", eventGUID, frm, sta, 85 + sta, 99 + sta));
        cnt++;
    }

}
String eventID = obsList.First().EventGUID;

List<EarObs> distinctApts =
    obsList
    .GroupBy(p => p.IcaoId)
    .Select(g => g.First())
    .ToList();


XElement xElement = new XElement("EarObs", new XAttribute("eventID", eventID),

    from ea in distinctApts
    orderby ea.IcaoId
    select new XElement("icao", new XAttribute("code", ea.IcaoId),
        from eb in obsList
        where ea.IcaoId == eb.IcaoId
        orderby eb.Frm
        select new XElement("frm", new XAttribute("frm", eb.Frm),
            from ec in obsList                 
            where eb.Frm == ec.Frm
            orderby ec.Sta
            select  new XElement("sta", new XAttribute("sta", ec.Sta),
                new XElement("db", ec.Db),
                new XElement("hz", ec.Hz)))));

Using this code I get an xml document that repeats the frame once for each station. This is not correct. I feel like this is easily done sequentially, but I'm trying to learn and this seems just so simple that I should be able to do it in Linq. I need each element in the List to only be represented in the XML document once. How do I go about this?

I would also like to expand it so that it can handle multiple eventId's as well, but that is not as important as getting the XML structure right. Any help would be much appreciated, I haven't been able to find too many example of creating an XML including the filtering of the elements using linq, most examples seem to have the List all ready structured before they create the XML.

Ben
  • 35
  • 7
  • It may not be "correct", but it's doing exactly as instructed - you're adding an object for each iteration while looping over the stations. What you're probably intending instead is to add the stations to a local collection, then add a frame that has the station collection. – GalacticCowboy Feb 15 '13 at 20:07
  • So are you saying I should add another from statement under the select sta Element line? Could you elaborate on what you mean by add the stations to a local collection? – Ben Feb 15 '13 at 20:17
  • Earlier than that, when you're building the frameObsList, you add one for each station. – GalacticCowboy Feb 15 '13 at 20:27
  • Basically, even though you're not using a database, you have a normalization issue. – GalacticCowboy Feb 15 '13 at 20:37
  • So you're saying I should make a List similar to the distinctApts one and then use that in the from statements? Sorry, I'm just thoroughly confused and I don't have the option of changing the XML format. I could do this other ways, I know that, I'm just really trying to get a handle on Linq. – Ben Feb 15 '13 at 20:54

2 Answers2

1

Since you have a custom class, EarObs why not define Xml attributes to your object and serialize the object using the XmlSerlizer class? This way, you can continue use Linq on your objects, and also output your objects.

e.g. Below is a team, with players on it.

[XmlRoot("root")]
public class Team
{
    private List<Player> players = new List<Player>();

    [XmlElement("player")]
    public List<Player> Players { get { return this.players; } set { this.players = value; } }

    // serializer requires a parameterless constructor class
    public Team() { }
}

public class Player
{
    private List<int> verticalLeaps = new List<int>();

    [XmlElement]
    public string FirstName { get; set; }
    [XmlElement]
    public string LastName { get; set; }
    [XmlElement]
    public List<int> vertLeap { get { return this.verticalLeaps; } set { this.verticalLeaps = value; } }

    // serializer requires a parameterless constructor class
    public Player() { }
}

Once I create a team, with some players on it, I just have to do:

Team myTeamData = new Team();
// add some players on it.
XmlSerializer deserializer = new XmlSerializer(typeof(Team));
using (TextReader textReader = new StreamReader(@"C:\temp\temp.txt"))
{
    myTeamData = (Team)deserializer.Deserialize(textReader);
    textReader.Close();
}

The output will look like this:

<?xml version="1.0" encoding="utf-8"?>
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <player>
    <FirstName>dwight</FirstName>
    <LastName>howard</LastName>
    <vertLeap>1</vertLeap>
    <vertLeap>2</vertLeap>
    <vertLeap>3</vertLeap>
  </player>
  <player>
    <FirstName>dwight</FirstName>
    <LastName>howard</LastName>
    <vertLeap>1</vertLeap>
  </player>
</root>
Magnum
  • 1,555
  • 4
  • 18
  • 39
  • I would be more than willing to do that, although how do I get the structured form like the one in my original post without creating separate classes for station, frame and icao? I can see how if I had a class named sta with the two XMLElements db and hz, and then a class frame that had a List of sta's and then a icao class that had a List of frames, I could easily do it this way, but that seems like a lot of unneeded abstraction. – Ben Feb 15 '13 at 20:25
  • You can use the modifiers like I mentioned above XmlElement as [XmlElement("nameyourelementhere")] or [XmlAttribute("attributename")] to get it to look exactly like the XML input (or output) file you mentioned. Sometimes, you may have to use wrappers so that's why if it wasn't designed this way it may be a tedious task to make it so. – Magnum Feb 18 '13 at 17:39
0

The easiest way is to create a set of classes to handle the serialization like so;

public class sta
{
    public int db { get; set; }
    public int hz { get; set; }

    [XmlAttribute()]
    public string station { get; set; }
}

public class frm
{
    [XmlAttribute("frm")]
    public string frmID { get; set; }

    [XmlElement("sta")]
    public List<sta> stas { get; set; }
}

public class icao
{
    [XmlAttribute]
    public string icaoID { get; set; }

    [XmlElement("frm")]
    public List<frm> frms { get; set; }
}

public class EarObs
{
    [XmlAttribute]
    public string EventId { get; set; }

    [XmlElement("icao")]
    public List<icao> icaos { get; set; }
}

and you can use the xml serializer to serialize/deserialize. The following serializes to the structure identical to what you have;

XmlSerializer serializer = new XmlSerializer(typeof(EarObs));
EarObs obs = new EarObs() { EventId = "123456789" };
obs.icaos = new List<icao>();
obs.icaos.Add(new icao() { icaoID = "0001" });
obs.icaos[0].frms = new List<frm>();
obs.icaos[0].frms.Add(new frm() { frmID = "01" });
obs.icaos[0].frms[0].stas = new List<sta>();
obs.icaos[0].frms[0].stas.Add(new sta() { station = "00", db = 87, hz = 99 });
obs.icaos[0].frms[0].stas.Add(new sta() { station = "01", db = 79, hz = 99 });
obs.icaos[0].frms.Add(new frm() { frmID = "02" });

using (StringWriter s = new StringWriter())
{
    serializer.Serialize(s, obs);
    string test = s.ToString();
}

Outputs;

<?xml version="1.0" encoding="utf-16"?>
<EarObs xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" EventId="123456789">
  <icao icaoID="0001">
    <frm frm="01">
      <sta station="00">
        <db>87</db>
        <hz>99</hz>
      </sta>
      <sta station="01">
        <db>79</db>
        <hz>99</hz>
      </sta>
    </frm>
    <frm frm="02" />
  </icao>
</EarObs>

Now, while this seems like a lot of trouble to go to, it's possible to use the xsd.exe tool (comes with the framework I believe), to automatically create a set of classes that match any given xml file, although it does use an intermediary xsd file (painless though). You can find out how here; How to generate .NET 4.0 classes from xsd?

Community
  • 1
  • 1
DiskJunky
  • 4,750
  • 3
  • 37
  • 66