开发者

XML LINQ transform

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)));


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"))
            );


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.


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

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜