3

Trying to achieve the below, basically a flat XML into a hierarchy XML using LINQ...

Any takers? Really stuck here :(

I have an xml document with:

<DriverDetails>
    <Index>0</Index>
    <DriverTitle>Mr</DriverTitle>
    <DriverFirstName>Something</DriverFirstName>
    <DriverSurname>SOMETHING</DriverSurname>
    <DriverTelephone>01234 123123</DriverTelephone>
    <DriverMobile />
    <DriverEmail>something@something.co.uk</DriverEmail>
    <Index>1</Index>
    <DriverTitle>Mr</DriverTitle>
    <DriverFirstName>Something</DriverFirstName>
    <DriverSurname>Something</DriverSurname>
    <DriverTelephone>01234 123456</DriverTelephone>
    <DriverMobile />
    <DriverEmail>something@something.co.uk</DriverEmail>
</DriverDetails>

I’m trying to get this into this XML:

The index being the indentifer of a new set of data

<driverContacts>
    <addressType>Something</addressType>
    <surname>something</surname>
    <forename>something</forename>
    <title>Mr</title>
    <phoneNo />
    <mobileNo />
    <eMail>something@something.co.uk</eMail>
    <fax />
</driverContacts>

<driverContacts>
    <addressType>Something</addressType>
    <surname>something</surname>
    <forename>something</forename>
    <title>Mr</title>
    <phoneNo />
    <mobileNo />
    <eMail>something@something.co.uk</eMail>
    <fax />
</driverContacts>

So far I've got this:

XElement driverContacts = 
          new XElement("driverContacts",
                from driverDetails in loaded.Descendants("DriverDetails")
                select new XElement("driverContacts",
                            new XElement("surname",
                            driverDetails.Element("surname").Value)));
R. Martinho Fernandes
  • 228,013
  • 71
  • 433
  • 510
user203538
  • 295
  • 5
  • 15
  • 4
    Whoever wrote the code that produces the first XML document should really be let go. – Jon Mar 10 '11 at 11:26
  • 1
    what was your 1st linq attempt to help us see where you'd got to. also, not quite sure how you're going to get the property out of the set as it doesn't exist in the original! (and the addresstype appears to be hardcoded too) – jim tollan Mar 10 '11 at 11:28
  • The incoming code is from an external system, so have no control over the input. – user203538 Mar 10 '11 at 11:32
  • sorry, what i meant was, how do you arrive at the fax and addresstype properties as they're not part of the original incoming code. – jim tollan Mar 10 '11 at 11:34
  • XElement driverContacts = new XElement("driverContacts", from driverDetails in loaded.Descendants("DriverDetails") select new XElement("driverContacts", new XElement("surname", driverDetails.Element("surname").Value) )); – user203538 Mar 10 '11 at 11:34
  • Ignore the fax value for now, its more about how I get the hierarchy from the flat xml – user203538 Mar 10 '11 at 11:37
  • The "recommended" way to transform XML documents into another schema is to use XSLT: http://en.wikipedia.org/wiki/XSLT. I don't know if it will work for the exact problem in question, but it might be worth a look. .net support for XSLT is built-in: http://stackoverflow.com/questions/34093/how-to-apply-an-xslt-stylesheet-in-c – Heinzi Mar 10 '11 at 13:17

3 Answers3

3

Be aware that I wouldn't EVER use this piece of code, because it parses multiple times the XML, BUT you asked XLINQ, and you'll get XLINQ.

var res = (from p in doc.Descendants("DriverDetails").Elements("Index")
            select new XElement("driverContacts",
                                new XElement("surname", p.ElementsAfterSelf("DriverSurname").First().Value),
                                new XElement("forename", p.ElementsAfterSelf("DriverFirstName").First().Value)
                ));

OR a little better, if you aren't sure all the fields will have a value:

Func<XElement, string> getValue = p => p != null ? p.Value : String.Empty;
Func<XElement, string, string> getSibling = (p, q) => getValue(p.ElementsAfterSelf().TakeWhile(r => r.Name != "Index").FirstOrDefault(r => r.Name == q));

var res = from p in doc.Descendants("DriverDetails").Elements("Index")
            select new XElement("driverContacts",
                new XElement("surname", getSibling(p, "DriverFirstName")),
                new XElement("forename", getSibling(p, "DriverSurname"))
            );
xanatos
  • 109,618
  • 12
  • 197
  • 280
  • +1 I don't play enough - the "ElementsAfterSelf" bit is the trick needed to render a query rather than a loop. However the assertion "I wouldn't ever use" is less than helpful because the problem is not with the solution but with the source data and how would you deal with that? – Murph Mar 10 '11 at 17:04
  • @Murph I would do as Murph has suggested... Oh wait... You are Murph :-) I would make a class to store the data (or I could use a Dictionary – xanatos Mar 10 '11 at 17:09
  • I would just build the elements on the fly - no intermediate class at this point - unless performance became an issue. – Murph Mar 10 '11 at 17:16
  • @Murph If you build it directly you lose the ordering, unless your order is the same as the "source" order. By doing a "full" read and the a "full" write you can reorder the elements. – xanatos Mar 10 '11 at 17:49
  • @xantos - Doh! You're absolutely right! Only reason not to do it your way would be if you decided to do the bare minimum to convert the input into something slightly sensible (basically wrap at each index) in order to then use linq on the XML result to derive the output XML. – Murph Mar 10 '11 at 18:27
1

You are kind of stuffed here - at least in terms of processing this in a proper set based sort of style as the necessary hierarchy information isn't encoded in the input data.

What you have here is a sequential file structure encoded with XML style markup, this in turn suggest you're possibly better off just rolling through a loop unless that has significant performance issues.

Its a bit clunky in that the loop would contain a switch or similar that would trigger creation of a new driverContacts when you hit an index element and would otherwise map from the source element to the required equivalent target element (in the current new contact).

Elegant this won't be - but its straightforward to implement, its reasonably easy to understand and it will work to solve the problem - which is applying structure to data that isn't currently structured in a manner that will allow you to take advantage of the tools you have.

Murph
  • 9,985
  • 2
  • 26
  • 41
1

It's reasonably straightforward to do this in XSLT. Add this to the identity transform:

<xsl:template match="DriverDetails">
   <xsl:apply-templates select="Index"/>
</xsl:template>

<xsl:template match="Index">
   <driverContacts>
      <addressType>something</addressType>
      <surname><xsl:value-of select="following-sibling::DriverName[1]"/></surname>
      <forename><xsl:value-of select="following-sibling::DriverFirstName[1]"/></forename>
      <!-- repeat for remaining desired elements -->
   </driverContacts>
</xsl:template>

This will create a driverContacts element from each Index element, populating its child elements from the following DriverName, DriverFirstName, etc. elements.

Note that if any of those elements is missing, the XPath will search down the following-sibling axis past the next Index element until it finds one. You should only use this method if the structure of the source XML is consistent.

If it's not, you can still do this, but it's trickier. You basically have to do something like:

<xsl:variable name="nextIndex" select="following-sibling::Index[1]"/>
<xsl:variable name="elements" select="following-sibling::*[not($nextIndex) or . = $nextIndex/preceding-sibling::*]"/>

which restricts $elements to contain only those following elements that also precede the next Index element (or all following elements, if there is no next Index element). Then you set your result elements like this:

<surname><xsl:value-of select="$elements[name() = 'DriverSurname'][1]"/></surname>
Robert Rossney
  • 94,622
  • 24
  • 146
  • 218