XML to object using LINQ, using different constructors depending on data
-
I am parsing an XML with data I want to convert to objects of an already existing class (so I don't want to modify the class itself). There is a specific sub-element that each element may or may not have, and depending on whether that sub-element exists, I want to use a different object constructor in my select clause. This is how I did it (both code and xml are simplified):
<channel>
<display-name>SVT Europa</display-name>
</channel>
<channel>
<display-name>Eurosport</display-name>
<icon src="http://whatever" />
</channel>var channels =
(
//Icon URL is not required so the LINQ is divided into two parts, first get channels with an icon URL.
from x in channelsXml.Root.Elements("channel")
where
x.Element("icon") != null &&
x.Element("icon").Attribute("src") != null &&
Uri.IsWellFormedUriString(x.Element("icon").Attribute("src").Value, UriKind.RelativeOrAbsolute)
select new Channel(
x.Element("display-name").Value,
new Uri(x.Element("icon").Attribute("src").Value)
)
)
//Secondly, add channels missing an icon URL.
.Concat(
from x in channelsXml.Root.Elements("channel")
where
x.Element("icon") == null ||
x.Element("icon").Attribute("src") == null
select new Channel(
x.Element("display-name").Value
)
);Is this a good way to do it? My primary concern is whether the collection of "channel" elements is searched twice, and if there is a solution to the problem where it isn't. I also don't think it's very readable, hence the comments, is there a "cleaner" way to do it? (Remember that I simplified the code and xml, in the actual code several more elements are read, and a lot more error checking are handled in the where clauses, and a lot of the error checking is duplicated because of the .Concat) Comments? Insights? I'm new at LINQ so feel free to bash my way of using it if you like ;)
-
I am parsing an XML with data I want to convert to objects of an already existing class (so I don't want to modify the class itself). There is a specific sub-element that each element may or may not have, and depending on whether that sub-element exists, I want to use a different object constructor in my select clause. This is how I did it (both code and xml are simplified):
<channel>
<display-name>SVT Europa</display-name>
</channel>
<channel>
<display-name>Eurosport</display-name>
<icon src="http://whatever" />
</channel>var channels =
(
//Icon URL is not required so the LINQ is divided into two parts, first get channels with an icon URL.
from x in channelsXml.Root.Elements("channel")
where
x.Element("icon") != null &&
x.Element("icon").Attribute("src") != null &&
Uri.IsWellFormedUriString(x.Element("icon").Attribute("src").Value, UriKind.RelativeOrAbsolute)
select new Channel(
x.Element("display-name").Value,
new Uri(x.Element("icon").Attribute("src").Value)
)
)
//Secondly, add channels missing an icon URL.
.Concat(
from x in channelsXml.Root.Elements("channel")
where
x.Element("icon") == null ||
x.Element("icon").Attribute("src") == null
select new Channel(
x.Element("display-name").Value
)
);Is this a good way to do it? My primary concern is whether the collection of "channel" elements is searched twice, and if there is a solution to the problem where it isn't. I also don't think it's very readable, hence the comments, is there a "cleaner" way to do it? (Remember that I simplified the code and xml, in the actual code several more elements are read, and a lot more error checking are handled in the where clauses, and a lot of the error checking is duplicated because of the .Concat) Comments? Insights? I'm new at LINQ so feel free to bash my way of using it if you like ;)
This looks like a job for the ternary operator. You can check for the existence of the attribute in the select statement as the condition of a ternary operator. I also pulled the condition out to a separate function just because it seemed a bit large for a ternary condition and figured it would be easier to read.
from x in channelsXml.Root.Elements("channel")
select HasIcon(x) ?
new Channel(x.Element("display-name").Value,
new Uri(x.Element("icon").Attribute("src").Value)
) :
new Channel(x.Element("display-name").Value);bool HasIcon(XElement x)
{
return x.Element("icon") != null &&
x.Element("icon").Attribute("src") != null &&
Uri.IsWellFormedUriString(x.Element("icon").Attribute("src").Value, UriKind.RelativeOrAbsolute);
} -
This looks like a job for the ternary operator. You can check for the existence of the attribute in the select statement as the condition of a ternary operator. I also pulled the condition out to a separate function just because it seemed a bit large for a ternary condition and figured it would be easier to read.
from x in channelsXml.Root.Elements("channel")
select HasIcon(x) ?
new Channel(x.Element("display-name").Value,
new Uri(x.Element("icon").Attribute("src").Value)
) :
new Channel(x.Element("display-name").Value);bool HasIcon(XElement x)
{
return x.Element("icon") != null &&
x.Element("icon").Attribute("src") != null &&
Uri.IsWellFormedUriString(x.Element("icon").Attribute("src").Value, UriKind.RelativeOrAbsolute);
}