0

I have xml that is sent by a third party and I want to validate it.

XElement xTree = XElement.Parse(@"<Container>
    <TrackingReferences>
        <TrackingReference>
          <TrackingName>Donny</TrackingName>
          <TrackingCodes>
            <TrackingCode>
                <Name></Name>
            </TrackingCode>
            <TrackingCode>
                <Name>DisplayThis</Name>
            </TrackingCode>
            <TrackingCode>
                <Name></Name>
            </TrackingCode>
          </TrackingCodes>
        </TrackingReference>
      </TrackingReferences>
    </Container>");

IEnumerable<XElement> childList = xTree.Element("TrackingReferences").Descendants("TrackingReference").Where(
    tr => (
            tr.Element("TrackingName") != null && !tr.Element("TrackingName").IsEmpty && !String.IsNullOrEmpty(tr.Element("TrackingName").Value) &&
            tr.Descendants("TrackingCodes").Any(
                tc => tc.HasElements &&
                    tc.Elements("TrackingCode").Any(
                        code => code.Element("Name") != null && !code.Element("Name").IsEmpty && !String.IsNullOrEmpty(code.Element("Name").Value)
                    )
            )
    )
);

I can't figure out how to return the descendants that I would like.

The problem I have is that I only want the TrackingReference element to contain TrackingCode descendants when that TrackingCode has a Name element that isn't null or empty.

The below example returns:

<TrackingReference>
  <TrackingName>Donny</TrackingName>
  <TrackingCodes>
    <TrackingCode>
      <Name></Name>
    </TrackingCode>
    <TrackingCode>
      <Name>DisplayThis</Name>
    </TrackingCode>
    <TrackingCode>
      <Name></Name>
    </TrackingCode>
  </TrackingCodes>
</TrackingReference>

However in this example I don't want the first and third TrackingCode elements to be returned, just the second as this has a Name element with value, like this:

<TrackingReference>
      <TrackingName>Donny</TrackingName>
      <TrackingCodes>
        <TrackingCode>
          <Name>DisplayThis</Name>
        </TrackingCode>
      </TrackingCodes>
    </TrackingReference>

This is the first time I've tried a LINQ query to XML so any advice on how to make the query more clean/efficient would be much appreciated, or if I'm going about this the wrong way.

  • 1
    Do you actually want a modified version of the TrackingReference element, or would you be happy with a sequence of TrackingCode elements that have a non-empty name? The latter is significantly simpler. – Jon Skeet Jun 18 '14 at 12:17
  • Assuming you want a trimmed document, an Xsl transform may be a better fit for this problem? – StuartLC Jun 18 '14 at 12:30
  • @JonSkeet - the latter; a sequence of TrackingCode elements that have a non-empty name, so in above example just the one TrackingCode element would be returned instead of three TrackingCode elements (as the query is currently returning). Thanks a lot. – strangerinthealps Jun 18 '14 at 13:00
  • @StuartLC - I may well go with XSLT ultimately, but as I haven't done much LINQ to XML I wanted the challenge to understand it better – strangerinthealps Jun 18 '14 at 13:03

2 Answers2

2

Okay, it sounds like you want the TrackingCode elements rather than the TrackingReference elements, so it's actually pretty easy:

var query = doc.Descendants("TrackingReference")
               // TODO: Filter based on TrackingName if you want...
               .Descendants("TrackingCode")
               .Where(x => !string.IsNullOrEmpty((string) x.Element("Name"));

This uses the fact that the explicit string conversion on XElement will return null if you call it with a null operand.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I was looking for the TrackingReference element but for it to have just the TrackingCode descendants with a non-empty name. I edited the question to show what I was hoping to be returned. In theory there could be many TrackingReference elements in the original xml so I would like to run the filter on all of them. Nice tip with the explicit string conversion. Thanks for the help – strangerinthealps Jun 18 '14 at 13:17
0

Just in case you did want to consider a xslt solution, you can eliminate the empty TrackingCodes with the identity transform and a template to eat empty nodes:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" />
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="TrackingCode[not(Name)] | TrackingCode[Name='']"/>
</xsl:stylesheet>

XslCake fiddle here

StuartLC
  • 104,537
  • 17
  • 209
  • 285