views:

89

answers:

3

I have an XML snippet, so:

<STATES>
  <STATE>
    <NAME>Alabama</NAME>
    <ABBREVIATION>AL</ABBREVIATION>
    <CAPITAL>Montgomery</CAPITAL>
    <POPULATION>4661900</POPULATION>
    <AREA>52419</AREA>
    <DATEOFSTATEHOOD>14 December 1819</DATEOFSTATEHOOD>
  </STATE>
  <STATE>
    <NAME>Alaska</NAME>
    <ABBREVIATION>AK</ABBREVIATION>
    <CAPITAL>Juneau</CAPITAL>
    <POPULATION>698473</POPULATION>
    <AREA>663268</AREA>
    <DATEOFSTATEHOOD>1 January 1959</DATEOFSTATEHOOD>
  </STATE>
  <STATE>
    <NAME>Delaware</NAME>
    <ABBREVIATION>DE</ABBREVIATION>
    <CAPITAL>Dover</CAPITAL>
    <POPULATION>885122</POPULATION>
    <AREA>2490</AREA>
    <DATEOFSTATEHOOD>7 December 1787</DATEOFSTATEHOOD>
  </STATE>
</STATES>
<etc, etc.>

I want to retrieve (for example) the capital of the oldest state (i.e. "Dover"). I have managed to get this far:

//STATES/STATE[DATEOFSTATEHOOD='7 December 1787']/CAPITAL/text()

but can't figure out how to say 'DATEOFSTATEHOOD={the earliest DATEOFSTATEHOOD}'.

Can anybody point me in the right direction, please?

SOLUTION: Matt's solution is more or less spot on. I had to reformat the dates (I used YYYYMMDDD) because, as was pointed out, Xpath 1.0 doesn't support the date format I was using. Also, Microsoft's XML library (4.0 and 6.0) returned the whole node list with Matt's expression. Reversing the test fixed that problem, making it return just the earliest node.

So:

//STATES/STATE[(DATEOFSTATEHOOD < //STATES/STATE/DATEOFSTATEHOOD)]/CAPITAL/text()
+1  A: 

Can you reformat them to be xs:dates?

let $dates := (xs:date('2000-10-23'), xs:date('1999-12-26'))
let $min := fn:min($dates)
let $max := fn:max($dates)
return $min

Done in MarkLogic Server, but I think that's all standard stuff.

Dave Cassel
+2  A: 

XPATH 1.0 does not support dates in the format that you are providing. If you were able to use a numerical representation of these dates such as 17871207 then you could easily do it like so:

//STATES/STATE[not(DATEOFSTATEHOOD > //STATES/STATE/DATEOFSTATEHOOD)]/CAPITAL/text()

If this is not feasible then it might be worth trying to format the DATEOFSTATEHOOD node as an xs:date and performing the same:

//STATES/STATE[not(xs:date(DATEOFSTATEHOOD) > xs:date(//STATES/STATE/DATEOFSTATEHOOD))]/CAPITAL/text()

The syntax may not be entirely correct but hopefully it'll get you started.

Matt Weldon
I have control of the date format, so I will try that, thanks.
gkrogers
@Matt: I found that your expression worked in an online expression tester, but not in Microsoft's XML library. However, changing it to //STATES/STATE[(DATEOFSTATEHOOD < //STATES/STATE/DATEOFSTATEHOOD)]/CAPITAL/text() did the tritck - thanks!
gkrogers
+1  A: 

You can reformat the date with XQuery and use min() to locate the earliest date:

declare variable $monthnames := ("January","February","March","April","May","June","July","August","September","October","November","December");

declare function local:pad-zero($s as xs:string) as xs:string {
  if (string-length($s) = 1) then concat("0",$s) else $s
};

declare function local:df ($d as xs:string) as xs:date {
  let $dp := tokenize($d," ")
  let $year := $dp[3]
  let $month := local:pad-zero(string(index-of($monthnames,$dp[2])))
  let $day := local:pad-zero($dp[1])
  return
    concat($year,"-",$month,"-",$day)


};

let $states := 
<STATES>
  <STATE>
    <NAME>Alabama</NAME>
    <ABBREVIATION>AL</ABBREVIATION>
    <CAPITAL>Montgomery</CAPITAL>
    <POPULATION>4661900</POPULATION>
    <AREA>52419</AREA>
    <DATEOFSTATEHOOD>14 December 1819</DATEOFSTATEHOOD>
  </STATE>
  <STATE>
    <NAME>Alaska</NAME>
    <ABBREVIATION>AK</ABBREVIATION>
    <CAPITAL>Juneau</CAPITAL>
    <POPULATION>698473</POPULATION>
    <AREA>663268</AREA>
    <DATEOFSTATEHOOD>1 January 1959</DATEOFSTATEHOOD>
  </STATE>
  <STATE>
    <NAME>Delaware</NAME>
    <ABBREVIATION>DE</ABBREVIATION>
    <CAPITAL>Dover</CAPITAL>
    <POPULATION>885122</POPULATION>
    <AREA>2490</AREA>
    <DATEOFSTATEHOOD>7 December 1787</DATEOFSTATEHOOD>
  </STATE>
</STATES>


return 
   $states//STATE
     [local:df(DATEOFSTATEHOOD) = 
      min($states//STATE/local:df(DATEOFSTATEHOOD))
     ]

You can execute this in the eXist sandbox

Chris Wallace