(: I include these variables so that it is easy to access docs. :)
declare variable $emps := doc("docs/v-emps.xml");
declare variable $depts := doc("docs/v-depts.xml");
(: BASIC TEMPORAL FUNCTIONS :)
(: I include two functions to calculate whether periods overlap. These
functions help with just about all of the queries. They are very simple:
The first takes an element with tstart and tend attributes as well as a
date. It determines whether the element overlaps the date, i.e., whether
the date occurs in between tstart and tend.
The second takes an element with start and tend attributes as well as
two dates, one marking the start of a period and one marking the end. It
determines whether the element overlaps that period.
:)
declare function local:overlaps($a as element(), $b as xs:date) as xs:boolean
{
$a/@tstart <= $b and $a/@tend >= $b
};
declare function local:overlaps($a as element()*, $tstart as xs:date, $tend as xs:date) as xs:boolean
{
$a/@tstart <= $tend and $a/@tend >= $tstart
};
(: Min and max functions for dates, to make some queries a bit easier. :)
declare function local:min($a as xs:date, $b as xs:date) as xs:date
{
(if ($a > $b) then $b else ($a))
};
declare function local:max($a as xs:date, $b as xs:date) as xs:date
{
(if ($a > $b) then $a else ($b))
};
(: LONGEST PERIOD :)
(: Find the longest element in a list of elements, where length is defined
by the difference between tend and tstart. This works by calculating the
difference between tend and tstart for each element, then computing the
max, and finally, going through each element, and returning it if its
length matches the max. :)
declare function local:longest($a as element()*) as element()*
{
(let $diffs := (for $n in $a return (xs:date($n/@tend) - xs:date($n/@tstart))),
$max := max($diffs)
for $n in $a
where (xs:date($n/@tend) - xs:date($n/@tstart)) = $max
return $n)
};
(: COALESCING :)
(: This function is useful in simple_coalesce (see below). It takes an
element $e and a list of elements and returns the time marking the end
of the maximal period for that starts at the element's start time. It
does this by assuming that the only possible overlaps between elements
is that the earlier one's tend matches the later one's tstart (i.e., no
elements overlap by more than one time unit). Because of the way that we
are storing temporal data, this assumption should usually hold.
The function runs recursively: if no element overlaps with the current
element's end time, the maximal period ends at the element's tend.
Otherwise, the maximal period ends at max_time on the next element. :)
declare function local:max_time($node as element(), $nodes as element()*) as xs:date
{
let $next := $nodes[@tstart=$node/@tend][text()=$node/text()]
return (if (empty($next)) then (xs:date($node/@tend)) else (local:max_time($next, $nodes)))
};
(: Coalesce while assuming that no elements overlap by more than one time
unit (see max_time above). It simply goes through the list of original
elements, ignores any element that does not mark the start of a maximal
period and returns the result of max_time on the rest.
It checks to see if an element starts a maximal period by ensuring that
there is no element that ends when it starts. :)
declare function local:simple_coalesce($original as element()*) as element()*
{
(
for $node in $original
where not(some $n in $node satisfies
(some $node2 in $original
satisfies $n/@tstart = $node2/@tend and $n/text() = $node2/text()))
return
{$node/node()}
)
};
(: COUNTING :)
(: This function computes a temporal count using delta values. It is given
an current count, an current time, a list of times to count, and a list
of events holding information about those times (see p7.xml). It runs
recursively, by grabbing the minimum time in the list (in $new_time),
getting all of the start and end events that occur at that time (in
$plus and $minus, respectively), and then running itself on the
remaining times, with $new_time as the current time, and an updated
count after including $plus and $minus. Its results are stored in count
elements that are in turn stored in recursively nested result elements.
I tried to return only the count elements, but got several complaints
from Galax. :)
declare function local:count_smart($curr_count as xs:integer,$last_time as xs:date,$times as xs:date*,$events as element()*) as element()*
{
(if (empty($times)) then () else
(let $new_time := min($times),
$plus := count($events//time[@type = "start"][@id = $new_time]),
$minus := count($events//time[@type = "end"][@id = $new_time]),
$new_count := ($curr_count + $plus - $minus),
$new_times := (for $time in $times where $time > $new_time return $time),
$result := ({$curr_count})
return
{
($result),
(local:count_smart($new_count,xs:date($new_time),$new_times,$events))
}
)
)
};