Andrew Truckle
Andrew Truckle

Reputation: 19097

Building a select query to isolate all nodes with a date greater than

Here is a snippet of XML:

<AssignmentHistory Version="210002" DateLastPurged="2020-12-11">
    <W20190107>
        <Teaching>
          <Name Class="1">Brother 1</Name>
          <Name Class="2">Brother 2</Name>
          <Name Class="3">Brother 3</Name>
        </Teaching>
    </W20190107>
    <W20190114>
        <Teaching>
          <Name Class="1">Brother 1</Name>
          <Name Class="2">Brother 2</Name>
          <Name Class="3">Brother 3</Name>
        </Teaching>
    </W20190114>
</AssignmentHistory>

I realise that my use of WYYYYMMDD for the element name was not very clever. I designed the XML file when I was only learning XML / XSL and now see it not to be good.

The above is stripped down just to show what I need to show.

I know I can do a path query like this:

XmlDocument doc = new XmlDocument();
doc.Load("file.xml");
XmlNodeList nl = doc.SelectNodes("AssignmentHist/*/Teaching");
...

And it will return all of the Teaching elements in a XmlNodeList.

The XML file is sorted in descending date order and what I would like to do is use a query that will select all teaching nodes where the date is greater than a specified value. At the moment I have to:

It would be nice if the single select query could put all of those out. But can it be done?

Upvotes: 0

Views: 279

Answers (3)

Yitzhak Khabinsky
Yitzhak Khabinsky

Reputation: 22167

Here is a clean method by using LINQ to XML that is available in .Net since 2007.

It is a one liner with chained logic:

  • Find all <Teaching> elements
  • Where clause is going to the parent node, its name is converted to a string, "W" is removed, converted to DateTime data type and compared with a DateTime parameter dt.

c#

void Main()
{
    DateTime dt = DateTime.Parse("2019-01-10");

    XDocument xdoc = XDocument.Parse(@"<AssignmentHistory Version='210002' DateLastPurged='2020-12-11'>
        <W20190107>
            <Teaching>
                <Name Class='1'>Brother 1</Name>
                <Name Class='2'>Brother 2</Name>
                <Name Class='3'>Brother 3</Name>
            </Teaching>
        </W20190107>
        <W20190114>
            <Teaching>
                <Name Class='1'>Brother 4</Name>
                <Name Class='2'>Brother 5</Name>
                <Name Class='3'>Brother 6</Name>
            </Teaching>
        </W20190114>
    </AssignmentHistory>");

    var xelem = xdoc.Descendants("Teaching")
        .Where(x => DateTime.ParseExact(x.Parent.Name.ToString().Replace("W", "")
            , "yyyyMMdd", CultureInfo.InvariantCulture) > dt);
}

Output

<Teaching>
  <Name Class="1">Brother 4</Name>
  <Name Class="2">Brother 5</Name>
  <Name Class="3">Brother 6</Name>
</Teaching>

Upvotes: 2

Caius Jard
Caius Jard

Reputation: 74605

I don't think you need to go to any great lengths of manipulation here; your strings thankfully will sort alphamerically as they would numerically (W20200101 is greater than W20191231)..

//AssignmentHistory/*[name() >= 'W20190107']

To be clear, the reason I didn't put /Teaching on the end is because without it the result from the posted example is:

<W20190107>
    <Teaching>
      <Name Class="1">Brother 1</Name>
      <Name Class="2">Brother 2</Name>
      <Name Class="3">Brother 3</Name>
    </Teaching>
</W20190107>
<W20190114>
    <Teaching>
      <Name Class="1">Brother 1</Name>
      <Name Class="2">Brother 2</Name>
      <Name Class="3">Brother 3</Name>
    </Teaching>
</W20190114>

And with it the result is:

    <Teaching>
      <Name Class="1">Brother 1</Name>
      <Name Class="2">Brother 2</Name>
      <Name Class="3">Brother 3</Name>
    </Teaching>
    <Teaching>
      <Name Class="1">Brother 1</Name>
      <Name Class="2">Brother 2</Name>
      <Name Class="3">Brother 3</Name>
    </Teaching>

Because this data in the latter example is repeated/these Teaching nodes seemingly have nothing to tell them apart, I figured to leave the W nodes in because they provide identity. If you want just the Teaching nodes, use an xpath of:

//AssignmentHistory/*[name() >= 'W20190107']/Teaching

Upvotes: 2

Michael Sch&#246;nbauer
Michael Sch&#246;nbauer

Reputation: 1001

Thats a nice puzzle,

You could try that, it uses an XPath query and string interpolation (so don't forget the $):

DateTime searchDate = new DateTime(2019,01,14);
XmlDocument doc = new XmlDocument();
doc.Load("file.xml");
XmlNodeList nl = doc.SelectNodes($"//Teaching[xs:date(replace(substring(parent::*/name(), 2),'(\\d{{4}})(\\d{{2}})(\\d{{2}})','$1-$2-$3')) > xs:date('{searchDate:yyyy-dd-MM}')]");

I didn't try it in C# code though, so it might have syntax errors.

Upvotes: 1

Related Questions