I'm stuck with using a web service I have no control over and am trying to parse the XML returned by that service into a standard object.
A portion of the XML structure looks like this
<NO>
<L>Some text here </L>
<L>Some additional text here </L>
<L>Still more text here </L>
</NO>
In the end, I want to end up with one String property that will look like "Some text here Some additional text here Still more text here "
What I have for an initial pass is what follows. I think I'm on the right track, but not quite there yet:
XElement source = \\Output from the Webservice
List<IndexEntry> result;
result = (from indexentry in source.Elements(entryLevel)
select new IndexEntry()
{
EtiologyCode = indexentry.Element("IE") == null ? null : indexentry.Element("IE").Value,
//some code to set other properties in this object
Note = (from l in indexentry.Elements("NO").Descendants
select l.value) //This is where I stop
// and don't know where to go
}
I know that I could add a ToList() operator at the end of that query to return the collection. Is there an opertaor or technique that would allow me to inline the concatentation of that collection to a single string?
Feel free to ask for more info if this isn't clear.
Thanks.
LINQ to XML is indeed the way here:
// Note: in earlier versions of .NET, string.Join only accepts
// arrays. In more modern versions, it accepts sequences.
var text = string.Join(" ", topElement.Elements("L").Select(x => x.Value));
EDIT: Based on the comment, it looks like you just need a single-expression way of representing this. That's easy, if somewhat ugly:
result = (from indexentry in source.Elements(entryLevel)
select new IndexEntry
{
EtiologyCode = indexentry.Element("IE") == null
? null
: indexentry.Element("IE").Value,
//some code to set other properties in this object
Note = string.Join(" ", indexentry.Elements("NO")
.Descendants()
.Select(x => x.Value))
};
Another alternative is to extract it into a separate extension method (it has to be in a top-level static class):
public static string ConcatenateTextNodes(this IEnumerable<XElement> elements) =>
string.Join(" ", elements.Select(x => x.Value));
then change your code to:
result = (from indexentry in source.Elements(entryLevel)
select new IndexEntry
{
EtiologyCode = indexentry.Element("IE") == null
? null
: indexentry.Element("IE").Value,
//some code to set other properties in this object
Note = indexentry.Elements("NO")
.Descendants()
.ConcatenateTextNodes()
}
EDIT: A note about efficiency
Other answers have suggested using StringBuilder
in the name of efficiency. I would check for evidence of this being the right way to go before using it. If you think about it, StringBuilder
and ToArray
do similar things - they create a buffer bigger than they need to, add data to it, resize it when necessary, and come out with a result at the end. The hope is that you won't need to resize too often.
The difference between StringBuilder
and ToArray
here is what's being buffered - in StringBuilder
it's the entire contents of the string you've built up so far. With ToArray
it's just references. In other words, resizing the internal buffer used for ToArray
is likely to be cheaper than resizing the one for StringBuilder
, particularly if the individual strings are long.
After doing the buffering in ToArray
, string.Join
is hugely efficient: it can look through all the strings to start with, work out exactly how much space to allocate, and then concatenate it without ever having to copy the actual character data.
This is in sharp contrast to a previous answer I've given - but unfortunately I don't think I ever wrote up the benchmark.
I certainly wouldn't expect ToArray
to be significantly slower, and I think it makes the code simpler here - no need to use side-effects etc, aggregation etc.