Reputation: 295
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>[email protected]</DriverEmail>
<Index>1</Index>
<DriverTitle>Mr</DriverTitle>
<DriverFirstName>Something</DriverFirstName>
<DriverSurname>Something</DriverSurname>
<DriverTelephone>01234 123456</DriverTelephone>
<DriverMobile />
<DriverEmail>[email protected]</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>[email protected]</eMail>
<fax />
</driverContacts>
<driverContacts>
<addressType>Something</addressType>
<surname>something</surname>
<forename>something</forename>
<title>Mr</title>
<phoneNo />
<mobileNo />
<eMail>[email protected]</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)));
Upvotes: 3
Views: 1781
Reputation: 96840
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>
Upvotes: 1
Reputation: 111910
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"))
);
Upvotes: 3
Reputation: 10190
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.
Upvotes: 1