fixing turn logic

fixing turn logic

git-svn-id: a9772e85eaf7a5aa6a3955a90127d64c3c3bddcb@1461 d2cdb213-60f8-4f1b-b4b2-f6b941818144

xquery version "1.0";

declare namespace c4="http://falutin.net/connect4";

import module namespace util="http://falutin.net/connect4/util" at "util.xqy";
import module namespace layout="http://falutin.net/connect4/layout" at "layout.xqy";

declare variable $lux:http as document-node() external;

declare function c4:place-circle (
  $game as element(c4:game),
  $player as element(c4:player),
  $col as xs:int)
{
  (: should always = 1 :)
  let $iplayer := count ($game/c4:players/c4:player[. << $player]) + 1
  let $log := lux:log (count($game/c4:grid/c4:row/c4:cell[$col][empty(node())]), "info")
  let $selected := ($game/c4:grid/c4:row/c4:cell[$col][empty(node())])[last()]
  return if (not($selected or $log)) then "There's no space left in that column - try again" else
  let $updated-game :=
  <c4:game>{
    $game/@id,
    attribute modified { current-dateTime() },
    (: shift player to the end of the queue :)
    <c4:players>{
      $game/c4:players/c4:player[not(position() eq $iplayer)],
      $player
    }</c4:players>,
    <c4:grid>{
      for $row in $game/c4:grid/c4:row return
      <c4:row>{
        for $cell in $row/c4:cell return
          if ($cell is $selected)
            then <c4:cell>{$player/@color/string()}</c4:cell>
          else $cell
      }</c4:row>
    }</c4:grid>
  }</c4:game>
  let $insert := (
    lux:insert (concat('/connect4/', $game/@id), $updated-game),
    lux:commit())
  return $insert
};

declare function c4:play ()
{
  let $game-id := util:param ($lux:http, "game")
  let $game := collection()/c4:game[@id=$game-id]
  let $player-name := util:param ($lux:http, "player")
  let $player := $game/c4:players/c4:player[.=$player-name]
  let $col := util:param ($lux:http, "col")
  return
    if (not($game)) then util:error("Game not found")
  else
    if (not($player)) then util:error("Player not found")
  else
    if (not($player is $game/c4:players/c4:player[1])) then util:error("Player not active")
  else
    if (not(number($col)) or xs:int($col) lt 1 or xs:int($col) gt count($game/c4:grid/c4:row[1]/c4:cell)) then util:error (concat("Invalid column: ", $col))
  else
    let $error := c4:place-circle ($game, $player, xs:int($col))
    let $url := <a href="view.xqy?game={$game-id}&player={$player-name}&error={$error}"/>/@href
    return util:redirect ($url)
};

c4:play()

xquery version "1.0";

declare namespace c4="http://falutin.net/connect4";

import module namespace util="http://falutin.net/connect4/util" at "util.xqy";
import module namespace layout="http://falutin.net/connect4/layout" at "layout.xqy";

declare variable $lux:http as document-node() external;

declare function c4:place-circle (
  $game as element(c4:game),
  $player as element(c4:player),
  $col as xs:int)
{
  (: should always = 1 :)
  let $iplayer := count ($game/c4:players/c4:player[. << $player]) + 1
  let $log := lux:log (count($game/c4:grid/c4:row/c4:cell[$col][empty(node())]), "info")
  let $selected := ($game/c4:grid/c4:row/c4:cell[$col][empty(node())])[last()]
  return if (not($selected or $log)) then util:error ("There's no space left in that column - try again") else
  let $updated-game :=
  <c4:game>{
    $game/@id,
    attribute modified { current-dateTime() },
    (: shift player to the end of the queue :)
    <c4:players>{
      $game/c4:players/c4:player[not(position() eq $iplayer)],
      $player
    }</c4:players>,
    <c4:grid>{
      for $row in $game/c4:grid/c4:row return
      <c4:row>{
        for $cell in $row/c4:cell return
          if ($cell is $selected)
            then <c4:cell>{$player/@color/string()}</c4:cell>
          else $cell
      }</c4:row>
    }</c4:grid>
  }</c4:game>
  let $insert := (
    lux:insert (concat('/connect4/', $game/@id), $updated-game),
    lux:commit())
  return $insert
};

declare function c4:play ()
{
  let $game-id := util:param ($lux:http, "game")
  let $game := collection()/c4:game[@id=$game-id]
  let $player-name := util:param ($lux:http, "player")
  let $player := $game/c4:players/c4:player[.=$player-name]
  let $col := util:param ($lux:http, "col")
  return
    if (not($game)) then util:error("Game not found")
  else
    if (not($player)) then util:error("Player not found")
  else
    if (not($player is $game/c4:players/c4:player[1])) then util:error("Player not active")
  else
    if (not(number($col)) or xs:int($col) lt 1 or xs:int($col) gt count($game/c4:grid/c4:row[1]/c4:cell)) then util:error (concat("Invalid column: ", $col))
  else
    let $update := c4:place-circle ($game, $player, xs:int($col))
    return util:redirect (<a href="view.xqy?game={$game-id, $update}&player={$player-name}"/>/@href)
    (:return layout:outer ("play.xqy", <textarea rows="8" cols="132">{$update}</textarea>):)
};

c4:play()


Corrected Error Handling for DTS Translations

(:
 : Copyright (C) [2013] [The FURTHeR Project]
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 :         http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
 :)
xquery version "3.0";
module namespace frt = "http://further.utah.edu/result-translation";

(: Import FURTHeR Module :)
(: import module namespace further = "http://further.utah.edu/xquery-functions-module"
    at "further.xq"; :)
import module namespace further = 'http://further.utah.edu/xquery-functions-module'
    at '${server.mdr.ws}${path.mdr.ws.resource.path}/fqe/further/xq/further.xq';

(: Import FURTHeR Constants Module :)
(: import module namespace const = 'http://further.utah.edu/constants'
    at 'constants.xq'; :)
import module namespace const = 'http://further.utah.edu/constants'
    at '${server.mdr.ws}${path.mdr.ws.resource.path}/fqe/further/xq/constants.xq';

(: ALWAYS Define Namespaces in XQUERY PROLOG! :)
declare namespace fn  = 'http://www.w3.org/2005/xpath-functions';
declare namespace fq = "http://further.utah.edu/core/query";
declare namespace dts = 'http://further.utah.edu/dts';
declare namespace mdr = "http://further.utah.edu/mdr";
declare namespace xs = "http://www.w3.org/2001/XMLSchema";
declare namespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
declare namespace fqe = 'http://further.utah.edu/fqe';
(: DO NOT USE DEFAULT NAMESPACE, it may conflict with the response Namespaces :)

(: Global CONSTANTS, Always Translating FROM FURTHeR Namespace ID :)
declare variable $frt:FURTHER as xs:string := '32769';
declare variable $frt:FURTHeR as xs:string := 'FURTHeR';

(: We are using SNOMED (Namespace ID 30) for ObservationType Values :)
declare variable $frt:SNOMED as xs:string := '30';

(: DTS Terminology Namespace IDs :)
declare variable $frt:OMOP-V2 as xs:string := '32868';
declare variable $frt:ICD-9 as xs:string := '10';
declare variable $frt:LOINC as xs:string := '5102';

(: Empty String Value for Substituting Empty Arguments to Functions :)
declare variable $frt:EMPTY as xs:string := '';

(: Mark Criterias that got Skipped :)
declare variable $frt:SKIP as xs:string := 'S';

(: Yes Value Used for Translation Flag transFlag :)
declare variable $frt:YES as xs:string := 'Y';

(: No Value Used for Translation Flag transFlag :)
declare variable $frt:NO as xs:string := 'N';

(: ERROR Value Used for Translation Flag transFlag :)
declare variable $frt:ERROR as xs:string := 'E';

(: Placeholder in case preTranslation Fails :)
declare variable $frt:ZERO as xs:string := '0';

(: DELIMITER :)
declare variable $frt:DELIMITER as xs:string := '^';

(: MDR Static Property Names CASE SENSITIVE! :)
declare variable $frt:ATTR_TRANS_FUNC as xs:string := 'ATTR_TRANS_FUNC';
declare variable $frt:ATTR_VALUE_TRANS_FUNC as xs:string := 'ATTR_VALUE_TRANS_FUNC';
declare variable $frt:ATTR_VALUE_TRANS_TO_DATA_TYPE as xs:string := 'ATTR_VALUE_TRANS_TO_DATA_TYPE';
declare variable $frt:CODING_SYSTEM as xs:string := 'CODING_SYSTEM';
declare variable $frt:EXT_PERSON as xs:string := 'EXT_PERSON';
declare variable $frt:EXT_ROOT_ID_ATTR as xs:string := 'EXT_ROOT_ID_ATTR';

declare variable $frt:pickMe as xs:string := 'pickMe';
declare variable $frt:skipATTR as xs:string := 'skipAttr';
declare variable $frt:translateCode as xs:string := 'translateCode';
declare variable $frt:ageToBirthYear as xs:string := 'ageToBirthYear';

(: DTS Static Property Names CASE SENSITIVE! :)
declare variable $frt:CodeInSource as xs:string := 'Code in Source';
declare variable $frt:LocalCode as xs:string := 'Local Code';

(:==================================================================:)
(: Main Translate Result Function                                   :)
(: Our Goal here is to construct the Target Central Model           :)
(:==================================================================:)
declare function frt:transResult($inputXML as document-node(),
                                 $extNmspcId as xs:string,
                                 $dataSetId as xs:string)
{

  (: Get the Namespace Name Using the Namespace ID :)
  let $extNmspcName := further:getNamespaceName($extNmspcId)

  (: Must Keep this Order of Processing :)

  (: Create Empty Person Template :)
  let $emptyPerson := frt:createPersonTemplate($extNmspcName)

  (: Initialize Empty Person Template with MDR Properties :)
  let $mdrPerson := frt:initPersonTemplate($emptyPerson,$extNmspcName)

  return (: $mdrPerson :)

    (: Validate Initialized Template :)
    if ($mdrPerson/*[@extRootObject=$frt:ERROR]) then

		  (: Error Handling :)
		  frt:validateTemplate($mdrPerson,$extNmspcName)

    else (: Continue Processing :)

		  (: Read the Input File from External Result for Processing :)
      (: Returns a document node for the following functions :)
		  let $transResult := frt:process($mdrPerson,$inputXML,$extNmspcId,$dataSetId)

		  (: Error Handling :)
		  let $validated := frt:validate($transResult,$extNmspcName)

		  (: Call cleanup Transformation :)
		  let $cleaned := frt:cleanup($validated)

		  (: Return Final Cleaned Version :)
		  return $cleaned

};

(:==================================================================:)
(: createPersonTemplate = Create Person Template XML Format         :)
(:==================================================================:)
declare function frt:createPersonTemplate($extNmspcName as xs:string)
as document-node()
{

(: Create a Document Type Here! :)
document
{
  <Person centralRootObject="{$frt:EMPTY}" extRootObject="{$frt:EMPTY}">
    <id></id>
    <administrativeGenderNamespaceId>{$frt:SNOMED}</administrativeGenderNamespaceId>
    <administrativeGender extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></administrativeGender>
    <raceNamespaceId>{$frt:SNOMED}</raceNamespaceId>
    <race extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></race>
    <ethnicityNamespaceId>{$frt:SNOMED}</ethnicityNamespaceId>
    <ethnicity extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></ethnicity>
    <dateOfBirth extPath="{$frt:EMPTY}"></dateOfBirth>
    <birthYear extPath="{$frt:EMPTY}"></birthYear>
    <birthMonth extPath="{$frt:EMPTY}"></birthMonth>
    <birthDay extPath="{$frt:EMPTY}"></birthDay>
    <educationLevel extPath="{$frt:EMPTY}"></educationLevel>
    <primaryLanguageNamespaceId>{$frt:SNOMED}</primaryLanguageNamespaceId>
    <primaryLanguage extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></primaryLanguage>
    <maritalStatusNamespaceId>{$frt:SNOMED}</maritalStatusNamespaceId>
    <maritalStatus extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></maritalStatus>
    <religionNamespaceId>{$frt:SNOMED}</religionNamespaceId>
    <religion extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></religion>
    <multipleBirthIndicator extPath="{$frt:EMPTY}"></multipleBirthIndicator>
    <multipleBirthIndicatorOrderNumber extPath="{$frt:EMPTY}"></multipleBirthIndicatorOrderNumber>
    <vitalStatusNamespaceId>{$frt:SNOMED}</vitalStatusNamespaceId>
    <vitalStatus extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></vitalStatus>
    <causeOfDeathNamespaceId>{$frt:SNOMED}</causeOfDeathNamespaceId>
    <causeOfDeath extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></causeOfDeath>
    <dateOfDeath extPath="{$frt:EMPTY}"></dateOfDeath>
    <deathYear extPath="{$frt:EMPTY}"></deathYear>
    <pedigreeQuality extPath="{$frt:EMPTY}"></pedigreeQuality>
  </Person>
}

(: END Function :)
};

(:==================================================================:)
(: initPersonTemplate = Create Person Template XML Format           :)
(:==================================================================:)
declare function frt:initPersonTemplate($emptyPerson as document-node(),
                                        $extNmspcName as xs:string)
as document-node()
{

(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $emptyPerson
modify (

  (: Find the Central Root Object :)
  (: The Name of the Root Element is the Name of the Central Root Object :)
  let $centralRootObject := fn:name($inputCopy/child::node())
  return (

    (: Set the Central Root Object XML Attribute :)
    replace value of node $inputCopy//@centralRootObject with $centralRootObject

    , (: DO MORE STUFF :)

    (: Find the External Root Object to Use as a Reference Point for External XML Doc :)
    let $extRootObject := frt:getExtRootObject($centralRootObject,$extNmspcName)
    return
      replace value of node $inputCopy//@extRootObject with $extRootObject

    , (: DO MORE STUFF :)

    (: Populate the extPath XML Attributes in the Template from MDR :)
    for $centralAttr in $inputCopy//*[@extPath=$frt:EMPTY]
      let $extPath := frt:getExtPath(name($centralAttr),$frt:FURTHeR,$extNmspcName)
      return replace value of node $centralAttr/@extPath with $extPath

    , (: DO MORE STUFF :)

    (: Set DTS Translation Instruction from MDR :)
    for $dtsAttr in $inputCopy//*[@dtsFlag]
      let $dtsFlag := frt:getDTSFlag(name($dtsAttr),$frt:FURTHeR,$extNmspcName)
      return replace value of node $dtsAttr/@dtsFlag with $dtsFlag

  ) (: End Return :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: validateTemplate (Error Handling)                                :)
(:==================================================================:)
declare function frt:validateTemplate($inputXML,$srcNmspcName as xs:string)
as document-node()
{

(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  if ($inputCopy/*[@extRootObject=$frt:ERROR]) then

    (: Get Name of the XML Root Node (Central Root Object) :)
    let $attrName := fn:name($inputCopy/*[@extRootObject=$frt:ERROR])
    return
    (: replace ONLY Works when we are Referring to a Document Type (NOT Element Type) :)
    replace node $inputCopy/*
       with
       <error xmlns="http://further.utah.edu/core/ws">
         MDR_RESULT_TRANSLATION_ERROR
         <message>MDR Association for [ {$srcNmspcName}.{$attrName} ] May be Missing</message>
       </error>

  else ()

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: process = Processing Function                                    :)
(:==================================================================:)
declare function frt:process($mdrPerson,$extXML,$extNmspcId,$dataSetId)
{

  (: Get the Namespace Name Using the Namespace ID :)
  let $extNmspcName := further:getNamespaceName($extNmspcId)

  (: High Level WorkFlow
	  1) Find the rootObject in the External Model by using the extRootObject Attribute Hint
	  2) For each rootObject in the External Model, create a mdrPerson Node and populate it with DTS Translated Data
	  3) Replace the <id> node with getPersonId function
	  4) Place the full result in a <ResultList> node
  :)

  (: Get External Root Object :)
  let $extRootObject := fn:substring-before($mdrPerson//@extRootObject,$frt:DELIMITER)

  (: Loop to Return ALL External Root Objects :)
  return

  if ($extXML[ResultList]) then
	  document{
		  <ResultList>
		    {
		      for $extRoot at $i in $extXML/ResultList/*[fn:name()=$extRootObject]
		      (: debug
		         return $extRootObject :)
		      return frt:transPerson($mdrPerson,$extRoot,$extNmspcId,$dataSetId)
		    }
		  </ResultList>
	  }
  else
    document {
      <error xmlns="http://further.utah.edu/core/ws">
        INVALID_RESULTLIST_ERROR
        <message>Invalid ResultList for Namespace [ {$extNmspcName} ] </message>
      </error>
    }

(: END Function :)
};

(:==================================================================:)
(: transPerson = Translate Single Person                            :)
(:==================================================================:)
declare function frt:transPerson($mdrPerson,$extPerson,$extNmspcId,$dataSetId)
as document-node()
{

(:
1) Get Data from External Input File and Populate our Template
2) Get DTS Translated Data if necessary
3) Replace the <id> node with getPersonId function
:)

(: BEGIN XQUERY TRANSFORMATION :)
copy $transFields := $mdrPerson
modify (

  (: Get Data from External Person :)
  for $field in $transFields//*[@extPath != $frt:EMPTY]
    (: Get the External Field Value using Dynamic Evaluation Function :)
    (: Full Path should be starting from under the rootObject node :)
    (: Create a String to represent the Full Document Path starting with a Slash '/' :)
    let $s := concat("$doc",$field/@extPath)
    (: Evaluate the String with a mapping to the document/element :)
    (: Took long time to figure this out, it is amazing when it works! :)
    let $extValue := xquery:eval($s,map {"doc" := $extPerson})
    let $cnt := fn:count($extValue)
    return (

	    if ( $cnt > 1 ) then (
        (: Error Out if more than one value is found :)
		    insert node attribute multiValueError {$cnt} into $field,
        replace value of node $field/@extPath with $frt:SKIP
      )
      else if (fn:empty($extValue) or $extValue=$frt:EMPTY) then
        (: if no value, Mark to Skip :)
        replace value of node $field/@extPath with $frt:SKIP
      else (
        (: Set Value for everything else :)
        replace value of node $field with $extValue
        ,
        insert node attribute sourceAttrText {fn:name($extValue)} into $field
      )

    ) (: End Return :)

) (: End Modify :)
return

(: BEGIN XQUERY TRANSFORMATION :)
copy $transDTS := $transFields
modify (

  (: Get Data from External Person :)
  (: NEED TO Translate VALUES THAT ARE NOT NULL!!! :)
  for $field in $transDTS//*[@dtsFlag=$frt:translateCode and @extPath!=$frt:SKIP]

    let $dtsSrcPropVal := $field/text()
    let $tgTerm := $field/@dtsTerm

    (: Call DTS :)
    let $dtsResponse := further:getTranslatedConcept($extNmspcId,
                                                     $frt:LocalCode,
                                                     $dtsSrcPropVal,
                                                     $tgTerm,
                                                     $frt:CodeInSource)
    (: DEBUG DTS URL :)
    (: let $dtsURL := further:getConceptTranslationRestUrl($extNmspcId,
                                                        $frt:LocalCode,
                                                        $dtsSrcPropVal,
                                                        $tgTerm,
                                                        $frt:CodeInSource) :)

    (: Get Translated Value :)
    let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

    return
    (: DEBUG :)
    (: replace value of node $field with
         fn:concat($extNmspcId,'^',$frt:LocalCode,'^',$dtsSrcPropVal,'^',$tgTerm,'^',$frt:CodeInSource) :)
    (: replace value of node $field with  $dtsURL :)

      if ($translatedPropVal) then (
        replace value of node $field with $translatedPropVal
        ,
        replace value of node $field/@dtsFlag with $frt:YES
      )
      else
        (: Always return Error if there is no DTS Mapping :)
        replace value of node $field/@dtsFlag with $frt:ERROR

) (: End Modify :)
return

(: BEGIN XQUERY TRANSFORMATION :)
copy $transRootId := $transDTS
modify (

  (: Find the External Root Object & Root Object Attr :)
  let $extRootObject := fn:substring-before($transRootId//@extRootObject,$frt:DELIMITER)
  let $extRootObjectAttr := fn:substring-after($transRootId//@extRootObject,$frt:DELIMITER)
  return
    replace node $transRootId//id
       with frt:getPersonId($extNmspcId,$extPerson,$extRootObject,$extRootObjectAttr,$dataSetId)

) (: End Modify :)
return $transRootId

(: END Function :)
};

(:==================================================================:)
(: Validate (Error Handling)                                        :)
(:==================================================================:)
declare function frt:validate($inputXML as document-node(),$srcNmspcName as xs:string)
as document-node()
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Error out ONLY the FIRST criteria AttributeName :)
  (: This will ensure that the output xml is valid since there is no outter <errors> node at this time :)
  (: It will also prevent us from having a very long list of errors :)
  (: Within the for statement, be sure to use the parentheses after the in clause.
     Therefore, the position predicate[1] will function properly.
     if we take out the parentheses and the [1], we will list the all criterias with error.
     which we may want in the future. :)

  if ($inputCopy/ResultList//*[@dtsFlag=$frt:ERROR]) then

    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $field in ($inputCopy/ResultList//*[@dtsFlag=$frt:ERROR])[1]
    (: let $attrName := fn:name($field) :)
    (: Use the Source Attribute Name instead :)
    let $attrName := fn:data($field/@sourceAttrText)
      return
      replace node $inputCopy/*
         with
         <error xmlns="http://further.utah.edu/core/ws">
           DTS_RESULT_TRANSLATION_ERROR
           <message>DTS Mapping for [ {$srcNmspcName}.{$attrName} ] May be Missing</message>
         </error>

  else if ($inputCopy/ResultList//*[@multiValueError]) then

    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $field in ($inputCopy/ResultList//*[@multiValueError])[1]
    let $attrName := fn:name($field)
      return
      replace node $inputCopy/*
         with
         <error xmlns="http://further.utah.edu/core/ws">
           RESULT_TRANSLATION_ERROR
           <message>Invalid Multiple Values for [ {$srcNmspcName}.{$attrName} ]</message>
         </error>

  else
    (: Remove ALL Nodes that have been Marked to be SKIPPED :)
    delete node $inputCopy/ResultList//*[@extPath=$frt:SKIP]

  (: End IF-Else Statement :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: Cleanup                                                          :)
(:==================================================================:)
declare function frt:cleanup($inputXML)
{

(: BEGIN First XQUERY TRANSFORMATION :)
copy $inputCopy1 := $inputXML
modify (

  (: Remove ALL Empty Data Nodes :)
  for $node in $inputCopy1/ResultList//*[@extPath=$frt:EMPTY]
    return delete node $node

  , (: Delete ALL tranFlag Attributes on SUCCESS :)
  delete node $inputCopy1/ResultList//@extRootObject,
  delete node $inputCopy1/ResultList//@centralRootObject,
  delete node $inputCopy1/ResultList//@extPath,
  delete node $inputCopy1/ResultList//@dtsTerm,
  delete node $inputCopy1/ResultList//@dtsFlag,
  delete node $inputCopy1/ResultList//@sourceAttrText

) (: End Modify :)
return

(: BEGIN Second XQUERY TRANSFORMATION :)
copy $inputCopy2 := $inputCopy1
modify (

  (: Remove ALL Empty Namespace Nodes :)
	(: Loop through Each Person rootObject :)
	for $person in $inputCopy2/ResultList/Person

	  (: For ALL Namespace Nodes :)
	  for $nmspcNode in $person//*[fn:contains(fn:name(),'NamespaceId')]

	    (: Get the coresponding data node name :)
	    let $dataNodeName := fn:substring-before(fn:name($nmspcNode),'NamespaceId')

	    return (:insert node attribute debug {$dataNodeName} into $nmspcNode:)
	      (: Remove ALL Namespace Nodes if coresponding dataNode does NOT exist :)
        if (fn:not(fn:exists($person//*[fn:name()=$dataNodeName]))) then
	        delete node $nmspcNode
	      else()

) (: End Modify :)
return $inputCopy2

(: END Function :)
};

(:=====================================================================:)
(: Sanitize SINGLE & DOUBLE Quotes to Prevent XQuery Injection Attacks :)
(:=====================================================================:)
declare function frt:sanitize($inputXML as document-node())
as document-node()
{

(: Ideally, I want to replace all '&' characters with '&'
   However, BaseX does not allow me to have this character in the xml file or output.
   So I am skipping this for now :)

(: Transformation to replace Single Quotes with ''' :)
copy $inputCopy := $inputXML
modify (

  for $node at $i in $inputCopy//*[contains(text(),'''')]
  return
    replace value of node $node with replace($node, '''','&apos;')

) (: End Modify :)

return
(: Starting another Transformation, so the replace node will not conflict :)
(: Transformation to replace Double Quotes with '"' :)
copy $inputCopy2 := $inputCopy
modify (

  for $node at $i in $inputCopy2//*[contains(text(),"""")]
  return
    replace value of node $node with replace($node, """",'&quot;')

) (: End Modify :)
return $inputCopy2

}; (: END OF FUNCTION frt:sanitize :)

(:=====================================================================:)
(: getPersonId = Construct the group of Person ID Tags                 :)
(:=====================================================================:)
declare function frt:getPersonId($extNmspcId,$extPerson,$extRootObject,$extRootObjectAttr,$dataSetId)
{
  (: Get the External ID Value :)
  let $s := concat("$doc/",$extRootObjectAttr)

  (: Evaluate the String with a mapping to the document/element :)
  (: Took long time to figure this out, it is amazing when it works! :)
  let $extIdValue := xquery:eval($s,map {"doc" := $extPerson})

  let $cnt := fn:count($extIdValue)
  return (
	  if ( $cnt > 1 ) then
      <id multiError='{$cnt}'/>
	  else
		  (: Call Identity Resolution Service :)
		  (:
		    REST CAll FORMAT

		    http://demo.further.utah.edu:9000/fqe/mpi/rest/id/generate/
		    {target_object}/{target_attribute}/{source_namespace_id}/
		    {source_object}/{source_attribute}/{source_id_value}/{queryId}

		    Note:
		    For Result Translation, Target is always the Central Model (FURTHER Model)
		    The Source is the External Data Source Models

		    Examples:
http://demo.further.utah.edu:9000/fqe/mpi/rest/id/generate/PERSON/FPERSON_ID/32868/PERSON/PERSONID/12345/862c9130-0e89-11e3-bb9f-f23c91aec05e
http://demo.further.utah.edu:9000/fqe/mpi/rest/id/generate/PERSON/FPERSON_ID/32776/PATIENT/PAT_DE_ID/12345/862c9130-0e89-11e3-bb9f-f23c91aec05e
		  :)

      (: Built the REST URL :)
		  let $baseURL := fn:concat($const:fqeRestServer,'/mpi/rest/id/generate/PERSON/FPERSON_ID/')
		  let $docUrl := fn:concat($baseURL, $extNmspcId, '/', $extRootObject, '/' , $extRootObjectAttr , '/', $extIdValue , '/', $dataSetId)

		  (: Prevent XQuery Injection Attacks :)
		  let $parsedDocUrl := iri-to-uri($docUrl)
		  let $result := doc($parsedDocUrl)

		  (: Strip out the Person ID from the Result :)
		  (: let $resolvedPersonId := $result//fqe:value/text() :)
		  let $resolvedPersonId := $result/fqe:id/fqe:value/text()

		  return (

        (: DEBUG with trace :)
        (: trace($resolvedPersonId, 'ResolvedPersonID'), :)

				<id>
				  <datasetId>{$dataSetId}</datasetId>
		      <id>{$resolvedPersonId}</id>
			  </id>
			  ,
			  <compositeId>{$dataSetId}:{$resolvedPersonId}</compositeId>
		  )

  ) (: End of Return :)

}; (: END OF FUNCTION :)

(:==================================================================:)
(: getLeftAssoc = Get Left Assocation from MDR                      :)
(:==================================================================:)
declare function frt:getLeftAssoc(
                 $leftAttrName as xs:string,
                 $leftNmspcName as xs:string,
                 $rightNmspcName as xs:string)
{
  (: EXAMPLE: ${server.dts.ws}/mdr/rest/asset/association/left/translatesTo/genderConceptId :)
  (:          ${server.dts.ws}/mdr/rest/asset/association/left/translatesTo/{sourceAttr} :)
  (:          http://demo.further.utah.edu:9000/mdr/rest/asset/association/left/translatesTo/administrativeGender :)
  let $baseURL := fn:concat($const:fmdrRestServer,'/rest/asset/association/left/translatesTo/')
  let $docUrl := fn:concat( $baseURL, $leftAttrName )

  (: Prevent XQuery Injection Attacks :)
  let $parsedDocUrl := iri-to-uri( $docUrl )
  let $doc := doc($parsedDocUrl)

  (: Return the AssetAssociation for the Correct Namespaces :)
  return

    if (fn:count($doc//assetAssociation[rightNamespace=$rightNmspcName and leftNamespace=$leftNmspcName]) > 1) then
      $doc//assetAssociation[rightNamespace=$rightNmspcName and leftNamespace=$leftNmspcName and properties/entry/value=$frt:pickMe]
    else
      $doc//assetAssociation[rightNamespace=$rightNmspcName and leftNamespace=$leftNmspcName]

};

(:==================================================================:)
(: getExtPath = GET XPath of XML Element in External XML from MDR   :)
(:==================================================================:)
declare function frt:getExtPath(
                 $leftAttrName as xs:string,
                 $leftNmspcName as xs:string,
                 $rightNmspcName as xs:string)
{
  let $leftAssoc := frt:getLeftAssoc($leftAttrName,$leftNmspcName,$rightNmspcName)

  (: Extract Out the External XPath Value :)
  for $entry in $leftAssoc/properties/entry[key='RESULT_PATH']
  return
    if ($entry) then $entry/value
    else $frt:EMPTY

};

(:==================================================================:)
(: getDTSFlag = GET DTS Instruction for External XML from MDR       :)
(:==================================================================:)
declare function frt:getDTSFlag(
                 $leftAttrName as xs:string,
                 $leftNmspcName as xs:string,
                 $rightNmspcName as xs:string)
{
  let $leftAssoc := frt:getLeftAssoc($leftAttrName,$leftNmspcName,$rightNmspcName)

  (: Extract Out the External XPath Value :)
  for $entry in $leftAssoc/properties/entry[key=$frt:ATTR_VALUE_TRANS_FUNC]
  return
    if ($entry) then $entry/value
    else $frt:EMPTY

};

(:==================================================================:)
(: getExtRootObject = GET External rootObject from MDR              :)
(:==================================================================:)
declare function frt:getExtRootObject(
                 $centralRootObject as xs:string,
                 $extNmspcName as xs:string
                 )
{

  let $leftAssoc := frt:getLeftAssoc($centralRootObject,$frt:FURTHeR,$extNmspcName)

  return

    (: if there is an Association Result :)
    if ($leftAssoc/properties/entry[key=$frt:EXT_ROOT_ID_ATTR]) then

      (: Get the External Root Object:)
      let $extRootObject := $leftAssoc/rightAsset/text()

      (: Get the External Root Object ID Attribute :)
      let $extRootObjectAttr :=
        for $entry in $leftAssoc/properties/entry[key=$frt:EXT_ROOT_ID_ATTR]
          return $entry/value/text()

      (: Return Concatenated String :)
      return concat($extRootObject,$frt:DELIMITER,$extRootObjectAttr)

    else $frt:ERROR

};

(:==================================================================:)
(: printDebug = Print Debug Variable to Query Info Screen           :)
(:==================================================================:)
declare function frt:printDebug($debugVar,$msg as xs:string?)
{
  let $var := $debugVar
  return fn:trace( $var,concat('DEBUG ', $msg, '=') )
};

(:==================================================================:)
(: ageToBirthYear = Translate Age to BirthYear                      :)
(:==================================================================:)
declare function frt:ageToBirthYear($age)
{
  (: Get Current Year :)
  let $curYear := year-from-date(current-date())

  (: Subtract Age from CurrentYear to Get BirthYear :)
  return $curYear - $age
};

(: END OF MODULE :)

(:
 : Copyright (C) [2013] [The FURTHeR Project]
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 :         http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
 :)
xquery version "3.0";
module namespace frt = "http://further.utah.edu/result-translation";

(: Import FURTHeR Module :)
(: import module namespace further = "http://further.utah.edu/xquery-functions-module"
    at "further.xq"; :)
import module namespace further = 'http://further.utah.edu/xquery-functions-module'
    at '${server.mdr.ws}${path.mdr.ws.resource.path}/fqe/further/xq/further.xq';

(: Import FURTHeR Constants Module :)
(: import module namespace const = 'http://further.utah.edu/constants'
    at 'constants.xq'; :)
import module namespace const = 'http://further.utah.edu/constants'
    at '${server.mdr.ws}${path.mdr.ws.resource.path}/fqe/further/xq/constants.xq';

(: ALWAYS Define Namespaces in XQUERY PROLOG! :)
declare namespace fn  = 'http://www.w3.org/2005/xpath-functions';
declare namespace fq = "http://further.utah.edu/core/query";
declare namespace dts = 'http://further.utah.edu/dts';
declare namespace mdr = "http://further.utah.edu/mdr";
declare namespace xs = "http://www.w3.org/2001/XMLSchema";
declare namespace xsi = "http://www.w3.org/2001/XMLSchema-instance";
declare namespace fqe = 'http://further.utah.edu/fqe';
(: DO NOT USE DEFAULT NAMESPACE, it may conflict with the response Namespaces :)

(: Global CONSTANTS, Always Translating FROM FURTHeR Namespace ID :)
declare variable $frt:FURTHER as xs:string := '32769';
declare variable $frt:FURTHeR as xs:string := 'FURTHeR';

(: We are using SNOMED (Namespace ID 30) for ObservationType Values :)
declare variable $frt:SNOMED as xs:string := '30';

(: DTS Terminology Namespace IDs :)
declare variable $frt:OMOP-V2 as xs:string := '32868';
declare variable $frt:ICD-9 as xs:string := '10';
declare variable $frt:LOINC as xs:string := '5102';

(: Empty String Value for Substituting Empty Arguments to Functions :)
declare variable $frt:EMPTY as xs:string := '';

(: Mark Criterias that got Skipped :)
declare variable $frt:SKIP as xs:string := 'S';

(: Yes Value Used for Translation Flag transFlag :)
declare variable $frt:YES as xs:string := 'Y';

(: No Value Used for Translation Flag transFlag :)
declare variable $frt:NO as xs:string := 'N';

(: ERROR Value Used for Translation Flag transFlag :)
declare variable $frt:ERROR as xs:string := 'E';

(: Placeholder in case preTranslation Fails :)
declare variable $frt:ZERO as xs:string := '0';

(: DELIMITER :)
declare variable $frt:DELIMITER as xs:string := '^';

(: MDR Static Property Names CASE SENSITIVE! :)
declare variable $frt:ATTR_TRANS_FUNC as xs:string := 'ATTR_TRANS_FUNC';
declare variable $frt:ATTR_VALUE_TRANS_FUNC as xs:string := 'ATTR_VALUE_TRANS_FUNC';
declare variable $frt:ATTR_VALUE_TRANS_TO_DATA_TYPE as xs:string := 'ATTR_VALUE_TRANS_TO_DATA_TYPE';
declare variable $frt:CODING_SYSTEM as xs:string := 'CODING_SYSTEM';
declare variable $frt:EXT_PERSON as xs:string := 'EXT_PERSON';
declare variable $frt:EXT_ROOT_ID_ATTR as xs:string := 'EXT_ROOT_ID_ATTR';

declare variable $frt:pickMe as xs:string := 'pickMe';
declare variable $frt:skipATTR as xs:string := 'skipAttr';
declare variable $frt:translateCode as xs:string := 'translateCode';
declare variable $frt:ageToBirthYear as xs:string := 'ageToBirthYear';

(: DTS Static Property Names CASE SENSITIVE! :)
declare variable $frt:CodeInSource as xs:string := 'Code in Source';
declare variable $frt:LocalCode as xs:string := 'Local Code';

(:==================================================================:)
(: Main Translate Result Function                                   :)
(: Our Goal here is to construct the Target Central Model           :)
(:==================================================================:)
declare function frt:transResult($inputXML as document-node(),
                                 $extNmspcId as xs:string,
                                 $dataSetId as xs:string)
{

  (: Get the Namespace Name Using the Namespace ID :)
  let $extNmspcName := further:getNamespaceName($extNmspcId)

  (: Must Keep this Order of Processing :)

  (: Create Empty Person Template :)
  let $emptyPerson := frt:createPersonTemplate($extNmspcName)

  (: Initialize Empty Person Template with MDR Properties :)
  let $mdrPerson := frt:initPersonTemplate($emptyPerson,$extNmspcName)

  return   (:  $mdrPerson :)

    (: Validate Initialized Template :)
    if ($mdrPerson/*[@extRootObject=$frt:ERROR]) then

		  (: Error Handling :)
		  frt:validateTemplate($mdrPerson,$extNmspcName)

    else (: Continue Processing :)

		  (: Read the Input File from External Result for Processing :)
      (: Returns a document node for the following functions :)
		  let $transResult := frt:process($mdrPerson,$inputXML,$extNmspcId,$dataSetId)

		  (: Error Handling :)
		  let $validated := frt:validate($transResult,$extNmspcName)

		  (: Call cleanup Transformation :)
		  let $cleaned := frt:cleanup($validated)

		  (: Return Final Cleaned Version :)
		  return $cleaned

};

(:==================================================================:)
(: createPersonTemplate = Create Person Template XML Format         :)
(:==================================================================:)
declare function frt:createPersonTemplate($extNmspcName as xs:string)
as document-node()
{

(: Create a Document Type Here! :)
document
{
  <Person centralRootObject="{$frt:EMPTY}" extRootObject="{$frt:EMPTY}">
    <id></id>
    <administrativeGenderNamespaceId>{$frt:SNOMED}</administrativeGenderNamespaceId>
    <administrativeGender extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></administrativeGender>
    <raceNamespaceId>{$frt:SNOMED}</raceNamespaceId>
    <race extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></race>
    <ethnicityNamespaceId>{$frt:SNOMED}</ethnicityNamespaceId>
    <ethnicity extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></ethnicity>
    <dateOfBirth extPath="{$frt:EMPTY}"></dateOfBirth>
    <birthYear extPath="{$frt:EMPTY}"></birthYear>
    <birthMonth extPath="{$frt:EMPTY}"></birthMonth>
    <birthDay extPath="{$frt:EMPTY}"></birthDay>
    <educationLevel extPath="{$frt:EMPTY}"></educationLevel>
    <primaryLanguageNamespaceId>{$frt:SNOMED}</primaryLanguageNamespaceId>
    <primaryLanguage extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></primaryLanguage>
    <maritalStatusNamespaceId>{$frt:SNOMED}</maritalStatusNamespaceId>
    <maritalStatus extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></maritalStatus>
    <religionNamespaceId>{$frt:SNOMED}</religionNamespaceId>
    <religion extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></religion>
    <multipleBirthIndicator extPath="{$frt:EMPTY}"></multipleBirthIndicator>
    <multipleBirthIndicatorOrderNumber extPath="{$frt:EMPTY}"></multipleBirthIndicatorOrderNumber>
    <vitalStatusNamespaceId>{$frt:SNOMED}</vitalStatusNamespaceId>
    <vitalStatus extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></vitalStatus>
    <causeOfDeathNamespaceId>{$frt:SNOMED}</causeOfDeathNamespaceId>
    <causeOfDeath extPath="{$frt:EMPTY}" dtsTerm="{$frt:SNOMED}" dtsFlag="{$frt:EMPTY}"></causeOfDeath>
    <dateOfDeath extPath="{$frt:EMPTY}"></dateOfDeath>
    <deathYear extPath="{$frt:EMPTY}"></deathYear>
    <pedigreeQuality extPath="{$frt:EMPTY}"></pedigreeQuality>
  </Person>
}

(: END Function :)
};

(:==================================================================:)
(: initPersonTemplate = Create Person Template XML Format           :)
(:==================================================================:)
declare function frt:initPersonTemplate($emptyPerson as document-node(),
                                        $extNmspcName as xs:string)
as document-node()
{

(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $emptyPerson
modify (

  (: Find the Central Root Object :)
  (: The Name of the Root Element is the Name of the Central Root Object :)
  let $centralRootObject := fn:name($inputCopy/child::node())
  return (

    (: Set the Central Root Object XML Attribute :)
    replace value of node $inputCopy//@centralRootObject with $centralRootObject

    , (: DO MORE STUFF :)

    (: Find the External Root Object to Use as a Reference Point for External XML Doc :)
    let $extRootObject := frt:getExtRootObject($centralRootObject,$extNmspcName)
    return
      replace value of node $inputCopy//@extRootObject with $extRootObject

    , (: DO MORE STUFF :)

    (: Populate the extPath XML Attributes in the Template from MDR :)
    for $centralAttr in $inputCopy//*[@extPath=$frt:EMPTY]
      let $extPath := frt:getExtPath(name($centralAttr),$frt:FURTHeR,$extNmspcName)
      return replace value of node $centralAttr/@extPath with $extPath

    , (: DO MORE STUFF :)

    (: Set DTS Translation Instruction from MDR :)
    for $dtsAttr in $inputCopy//*[@dtsFlag]
      let $dtsFlag := frt:getDTSFlag(name($dtsAttr),$frt:FURTHeR,$extNmspcName)
      return replace value of node $dtsAttr/@dtsFlag with $dtsFlag

  ) (: End Return :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: validateTemplate (Error Handling)                                :)
(:==================================================================:)
declare function frt:validateTemplate($inputXML,$srcNmspcName as xs:string)
as document-node()
{

(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  if ($inputCopy/*[@extRootObject=$frt:ERROR]) then

    (: Get Name of the XML Root Node (Central Root Object) :)
    let $attrName := fn:name($inputCopy/*[@extRootObject=$frt:ERROR])
    return
    (: replace ONLY Works when we are Referring to a Document Type (NOT Element Type) :)
    replace node $inputCopy/*
       with
       <error xmlns="http://further.utah.edu/core/ws">
         MDR_RESULT_TRANSLATION_ERROR
         <message>MDR Association for [ {$srcNmspcName}.{$attrName} ] May be Missing</message>
       </error>

  else ()

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: process = Processing Function                                    :)
(:==================================================================:)
declare function frt:process($mdrPerson,$extXML,$extNmspcId,$dataSetId)
{

  (: Get the Namespace Name Using the Namespace ID :)
  let $extNmspcName := further:getNamespaceName($extNmspcId)

  (: High Level WorkFlow
	  1) Find the rootObject in the External Model by using the extRootObject Attribute Hint
	  2) For each rootObject in the External Model, create a mdrPerson Node and populate it with DTS Translated Data
	  3) Replace the <id> node with getPersonId function
	  4) Place the full result in a <ResultList> node
  :)

  (: Get External Root Object :)
  let $extRootObject := fn:substring-before($mdrPerson//@extRootObject,$frt:DELIMITER)

  (: Loop to Return ALL External Root Objects :)
  return

  if ($extXML[ResultList]) then
	  document{
		  <ResultList>
		    {
		      for $extRoot at $i in $extXML/ResultList/*[fn:name()=$extRootObject]
		      (: debug
		         return $extRootObject :)
		      return frt:transPerson($mdrPerson,$extRoot,$extNmspcId,$dataSetId)
		    }
		  </ResultList>
	  }
  else
    document {
      <error xmlns="http://further.utah.edu/core/ws">
        INVALID_RESULTLIST_ERROR
        <message>Invalid ResultList for Namespace [ {$extNmspcName} ] </message>
      </error>
    }

(: END Function :)
};

(:==================================================================:)
(: transPerson = Translate Single Person                            :)
(:==================================================================:)
declare function frt:transPerson($mdrPerson,$extPerson,$extNmspcId,$dataSetId)
as document-node()
{

(:
1) Get Data from External Input File and Populate our Template
2) Get DTS Translated Data if necessary
3) Replace the <id> node with getPersonId function
:)

(: BEGIN XQUERY TRANSFORMATION :)
copy $transFields := $mdrPerson
modify (

  (: Get Data from External Person :)
  for $field in $transFields//*[@extPath != $frt:EMPTY]
    (: Get the External Field Value using Dynamic Evaluation Function :)
    (: Full Path should be starting from under the rootObject node :)
    (: Create a String to represent the Full Document Path starting with a Slash '/' :)
    let $s := concat("$doc",$field/@extPath)
    (: Evaluate the String with a mapping to the document/element :)
    (: Took long time to figure this out, it is amazing when it works! :)
    let $extValue := xquery:eval($s,map {"doc" := $extPerson})
    let $cnt := fn:count($extValue)
    return (

	    if ( $cnt > 1 ) then (
        (: Error Out if more than one value is found :)
		    insert node attribute multiValueError {$cnt} into $field,
        replace value of node $field/@extPath with $frt:SKIP
      )
      else if (fn:empty($extValue) or $extValue=$frt:EMPTY) then
        (: if no value, Mark to Skip :)
        replace value of node $field/@extPath with $frt:SKIP
      else
        (: Set Value for everything else :)
        replace value of node $field with $extValue
    )

) (: End Modify :)
return

(: BEGIN XQUERY TRANSFORMATION :)
copy $transDTS := $transFields
modify (

  (: Get Data from External Person :)
  (: NEED TO Translate VALUES THAT ARE NOT NULL!!! :)
  for $field in $transDTS//*[@dtsFlag=$frt:translateCode and @extPath!=$frt:SKIP]

    let $dtsSrcPropVal := $field/text()
    let $tgTerm := $field/@dtsTerm

    (: Call DTS :)
    let $dtsResponse := further:getTranslatedConcept($extNmspcId,
                                                     $frt:LocalCode,
                                                     $dtsSrcPropVal,
                                                     $tgTerm,
                                                     $frt:CodeInSource)
    (: DEBUG DTS URL :)
    (: let $dtsURL := further:getConceptTranslationRestUrl($extNmspcId,
                                                        $frt:LocalCode,
                                                        $dtsSrcPropVal,
                                                        $tgTerm,
                                                        $frt:CodeInSource) :)

    (: Get Translated Value :)
    let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

    return
    (: DEBUG :)
    (: replace value of node $field with
         fn:concat($extNmspcId,'^',$frt:LocalCode,'^',$dtsSrcPropVal,'^',$tgTerm,'^',$frt:CodeInSource) :)
    (: replace value of node $field with  $dtsURL :)

      if ($translatedPropVal) then (
        replace value of node $field with $translatedPropVal
        ,
        replace value of node $field/@dtsFlag with $frt:YES
      )
      else
        (: Always return Error if there is no DTS Mapping :)
        replace value of node $field/@dtsFlag with $frt:ERROR

) (: End Modify :)
return

(: BEGIN XQUERY TRANSFORMATION :)
copy $transRootId := $transDTS
modify (

  (: Find the External Root Object & Root Object Attr :)
  let $extRootObject := fn:substring-before($transRootId//@extRootObject,$frt:DELIMITER)
  let $extRootObjectAttr := fn:substring-after($transRootId//@extRootObject,$frt:DELIMITER)
  return
    replace node $transRootId//id
       with frt:getPersonId($extNmspcId,$extPerson,$extRootObject,$extRootObjectAttr,$dataSetId)

) (: End Modify :)
return $transRootId

(: END Function :)
};

(:==================================================================:)
(: Validate (Error Handling)                                        :)
(:==================================================================:)
declare function frt:validate($inputXML as document-node(),$srcNmspcName as xs:string)
as document-node()
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Error out ONLY the FIRST criteria AttributeName :)
  (: This will ensure that the output xml is valid since there is no outter <errors> node at this time :)
  (: It will also prevent us from having a very long list of errors :)
  (: Within the for statement, be sure to use the parentheses after the in clause.
     Therefore, the position predicate[1] will function properly.
     if we take out the parentheses and the [1], we will list the all criterias with error.
     which we may want in the future. :)

  if ($inputCopy/ResultList//*[@dtsFlag=$frt:ERROR]) then

    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $field in ($inputCopy/ResultList//*[@dtsFlag=$frt:ERROR])[1]
    let $attrName := fn:name($field)
      return
      replace node $inputCopy/*
         with
         <error xmlns="http://further.utah.edu/core/ws">
           DTS_RESULT_TRANSLATION_ERROR
           <message>DTS Mapping for [ {$srcNmspcName}.{$attrName} ] May be Missing</message>
         </error>

  else if ($inputCopy/ResultList//*[@multiValueError]) then

    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $field in ($inputCopy/ResultList//*[@multiValueError])[1]
    let $attrName := fn:name($field)
      return
      replace node $inputCopy/*
         with
         <error xmlns="http://further.utah.edu/core/ws">
           RESULT_TRANSLATION_ERROR
           <message>Invalid Multiple Values for [ {$srcNmspcName}.{$attrName} ]</message>
         </error>

  else
    (: Remove ALL Nodes that have been Marked to be SKIPPED :)
    delete node $inputCopy/ResultList//*[@extPath=$frt:SKIP]

  (: End IF-Else Statement :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: Cleanup                                                          :)
(:==================================================================:)
declare function frt:cleanup($inputXML)
{

(: BEGIN First XQUERY TRANSFORMATION :)
copy $inputCopy1 := $inputXML
modify (

  (: Remove ALL Empty Data Nodes :)
  for $node in $inputCopy1/ResultList//*[@extPath=$frt:EMPTY]
    return delete node $node

  , (: Delete ALL tranFlag Attributes on SUCCESS :)
  delete node $inputCopy1/ResultList//@extRootObject,
  delete node $inputCopy1/ResultList//@centralRootObject,
  delete node $inputCopy1/ResultList//@extPath,
  delete node $inputCopy1/ResultList//@dtsTerm,
  delete node $inputCopy1/ResultList//@dtsFlag

) (: End Modify :)
return

(: BEGIN Second XQUERY TRANSFORMATION :)
copy $inputCopy2 := $inputCopy1
modify (

  (: Remove ALL Empty Namespace Nodes :)
	(: Loop through Each Person rootObject :)
	for $person in $inputCopy2/ResultList/Person

	  (: For ALL Namespace Nodes :)
	  for $nmspcNode in $person//*[fn:contains(fn:name(),'NamespaceId')]

	    (: Get the coresponding data node name :)
	    let $dataNodeName := fn:substring-before(fn:name($nmspcNode),'NamespaceId')

	    return (:insert node attribute debug {$dataNodeName} into $nmspcNode:)
	      (: Remove ALL Namespace Nodes if coresponding dataNode does NOT exist :)
        if (fn:not(fn:exists($person//*[fn:name()=$dataNodeName]))) then
	        delete node $nmspcNode
	      else()

) (: End Modify :)
return $inputCopy2

(: END Function :)
};

(:=====================================================================:)
(: Sanitize SINGLE & DOUBLE Quotes to Prevent XQuery Injection Attacks :)
(:=====================================================================:)
declare function frt:sanitize($inputXML as document-node())
as document-node()
{

(: Ideally, I want to replace all '&' characters with '&'
   However, BaseX does not allow me to have this character in the xml file or output.
   So I am skipping this for now :)

(: Transformation to replace Single Quotes with ''' :)
copy $inputCopy := $inputXML
modify (

  for $node at $i in $inputCopy//*[contains(text(),'''')]
  return
    replace value of node $node with replace($node, '''','&apos;')

) (: End Modify :)

return
(: Starting another Transformation, so the replace node will not conflict :)
(: Transformation to replace Double Quotes with '"' :)
copy $inputCopy2 := $inputCopy
modify (

  for $node at $i in $inputCopy2//*[contains(text(),"""")]
  return
    replace value of node $node with replace($node, """",'&quot;')

) (: End Modify :)
return $inputCopy2

}; (: END OF FUNCTION frt:sanitize :)

(:=====================================================================:)
(: getPersonId = Construct the group of Person ID Tags                 :)
(:=====================================================================:)
declare function frt:getPersonId($extNmspcId,$extPerson,$extRootObject,$extRootObjectAttr,$dataSetId)
{
  (: Get the External ID Value :)
  let $s := concat("$doc/",$extRootObjectAttr)

  (: Evaluate the String with a mapping to the document/element :)
  (: Took long time to figure this out, it is amazing when it works! :)
  let $extIdValue := xquery:eval($s,map {"doc" := $extPerson})

  let $cnt := fn:count($extIdValue)
  return (
	  if ( $cnt > 1 ) then
      <id multiError='{$cnt}'/>
	  else
		  (: Call Identity Resolution Service :)
		  (:
		    REST CAll FORMAT

		    http://demo.further.utah.edu:9000/fqe/mpi/rest/id/generate/
		    {target_object}/{target_attribute}/{source_namespace_id}/
		    {source_object}/{source_attribute}/{source_id_value}/{queryId}

		    Note:
		    For Result Translation, Target is always the Central Model (FURTHER Model)
		    The Source is the External Data Source Models

		    Examples:
http://demo.further.utah.edu:9000/fqe/mpi/rest/id/generate/PERSON/FPERSON_ID/32868/PERSON/PERSONID/12345/862c9130-0e89-11e3-bb9f-f23c91aec05e
http://demo.further.utah.edu:9000/fqe/mpi/rest/id/generate/PERSON/FPERSON_ID/32776/PATIENT/PAT_DE_ID/12345/862c9130-0e89-11e3-bb9f-f23c91aec05e
		  :)

      (: Built the REST URL :)
		  let $baseURL := fn:concat($const:fqeRestServer,'/mpi/rest/id/generate/PERSON/FPERSON_ID/')
		  let $docUrl := fn:concat($baseURL, $extNmspcId, '/', $extRootObject, '/' , $extRootObjectAttr , '/', $extIdValue , '/', $dataSetId)

		  (: Prevent XQuery Injection Attacks :)
		  let $parsedDocUrl := iri-to-uri($docUrl)
		  let $result := doc($parsedDocUrl)

		  (: Strip out the Person ID from the Result :)
		  (: let $resolvedPersonId := $result//fqe:value/text() :)
		  let $resolvedPersonId := $result/fqe:id/fqe:value/text()

		  return (

        (: DEBUG with trace :)
        (: trace($resolvedPersonId, 'ResolvedPersonID'), :)

				<id>
				  <datasetId>{$dataSetId}</datasetId>
		      <id>{$resolvedPersonId}</id>
			  </id>
			  ,
			  <compositeId>{$dataSetId}:{$resolvedPersonId}</compositeId>
		  )

  ) (: End of Return :)

}; (: END OF FUNCTION :)

(:==================================================================:)
(: getLeftAssoc = Get Left Assocation from MDR                      :)
(:==================================================================:)
declare function frt:getLeftAssoc(
                 $leftAttrName as xs:string,
                 $leftNmspcName as xs:string,
                 $rightNmspcName as xs:string)
{
  (: EXAMPLE: ${server.dts.ws}/mdr/rest/asset/association/left/translatesTo/genderConceptId :)
  (:          ${server.dts.ws}/mdr/rest/asset/association/left/translatesTo/{sourceAttr} :)
  (:          http://demo.further.utah.edu:9000/mdr/rest/asset/association/left/translatesTo/administrativeGender :)
  let $baseURL := fn:concat($const:fmdrRestServer,'/rest/asset/association/left/translatesTo/')
  let $docUrl := fn:concat( $baseURL, $leftAttrName )

  (: Prevent XQuery Injection Attacks :)
  let $parsedDocUrl := iri-to-uri( $docUrl )
  let $doc := doc($parsedDocUrl)

  (: Return the AssetAssociation for the Correct Namespaces :)
  return

    if (fn:count($doc//assetAssociation[rightNamespace=$rightNmspcName and leftNamespace=$leftNmspcName]) > 1) then
      $doc//assetAssociation[rightNamespace=$rightNmspcName and leftNamespace=$leftNmspcName and properties/entry/value=$frt:pickMe]
    else
      $doc//assetAssociation[rightNamespace=$rightNmspcName and leftNamespace=$leftNmspcName]

};

(:==================================================================:)
(: getExtPath = GET XPath of XML Element in External XML from MDR   :)
(:==================================================================:)
declare function frt:getExtPath(
                 $leftAttrName as xs:string,
                 $leftNmspcName as xs:string,
                 $rightNmspcName as xs:string)
{
  let $leftAssoc := frt:getLeftAssoc($leftAttrName,$leftNmspcName,$rightNmspcName)

  (: Extract Out the External XPath Value :)
  for $entry in $leftAssoc/properties/entry[key='RESULT_PATH']
  return
    if ($entry) then $entry/value
    else $frt:EMPTY

};

(:==================================================================:)
(: getDTSFlag = GET DTS Instruction for External XML from MDR       :)
(:==================================================================:)
declare function frt:getDTSFlag(
                 $leftAttrName as xs:string,
                 $leftNmspcName as xs:string,
                 $rightNmspcName as xs:string)
{
  let $leftAssoc := frt:getLeftAssoc($leftAttrName,$leftNmspcName,$rightNmspcName)

  (: Extract Out the External XPath Value :)
  for $entry in $leftAssoc/properties/entry[key=$frt:ATTR_VALUE_TRANS_FUNC]
  return
    if ($entry) then $entry/value
    else $frt:EMPTY

};

(:==================================================================:)
(: getExtRootObject = GET External rootObject from MDR              :)
(:==================================================================:)
declare function frt:getExtRootObject(
                 $centralRootObject as xs:string,
                 $extNmspcName as xs:string
                 )
{

  let $leftAssoc := frt:getLeftAssoc($centralRootObject,$frt:FURTHeR,$extNmspcName)

  return

    (: if there is an Association Result :)
    if ($leftAssoc/properties/entry[key=$frt:EXT_ROOT_ID_ATTR]) then

      (: Get the External Root Object:)
      let $extRootObject := $leftAssoc/rightAsset/text()

      (: Get the External Root Object ID Attribute :)
      let $extRootObjectAttr :=
        for $entry in $leftAssoc/properties/entry[key=$frt:EXT_ROOT_ID_ATTR]
          return $entry/value/text()

      (: Return Concatenated String :)
      return concat($extRootObject,$frt:DELIMITER,$extRootObjectAttr)

    else $frt:ERROR

};

(:==================================================================:)
(: printDebug = Print Debug Variable to Query Info Screen           :)
(:==================================================================:)
declare function frt:printDebug($debugVar,$msg as xs:string?)
{
  let $var := $debugVar
  return fn:trace( $var,concat('DEBUG ', $msg, '=') )
};

(:==================================================================:)
(: ageToBirthYear = Translate Age to BirthYear                      :)
(:==================================================================:)
declare function frt:ageToBirthYear($age)
{
  (: Get Current Year :)
  let $curYear := year-from-date(current-date())

  (: Subtract Age from CurrentYear to Get BirthYear :)
  return $curYear - $age
};

(: END OF MODULE :)


- added back refresh-index to the documentation - fixed documentation for probe functions - changed the type of the probes' key attributes from $key_i as xs:anyAtomicType* to xs:anyAtomicType? in order to confirm with the implementation

(:
 : Copyright 2006-2009 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module defines a set of functions to probe and refresh indexes.
 :
 : @see http://www.zorba-xquery.com/modules/store/static/collections/ddl
 : @see http://www.zorba-xquery.com/modules/store/static/indexes/ddl
 : @see http://www.zorba-xquery.com/modules/store/static/indexes/dml
 :
 : @author Nicolae Brinza, Matthias Brantner, David Graf, Till Westmann,
 :   Markos Zaharioudakis
 : @project store/indexes/static
 :
 :)
module namespace dml = "http://www.zorba-xquery.com/modules/store/static/indexes/dml";

declare namespace ann = "http://www.zorba-xquery.com/annotations";

declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : The probe function retrieves the domain nodes associated with a particular
 : search condition, which is presented as a single key tuple. This function
 : is variadic and accepts a variable number of key arguments.
 :
 : The first argument is the QName identifying the index to probe. The remaining
 : arguments specify the search condition, which is given as a number of
 : (optional) atomic items comprising a key tuple. The number of key tuples
 : given must be equal to the number of keys declared for the index.
 :
 : @param $name The QName of the index to probe
 : @param $key_i any number of key used to probe the index (i.e. the search condition).
 :
 : @return If the index contains an entry with the given key tuple,
 :   the associated domain nodes are
 : returned. Otherwise, the empty sequence is returned.
 :
 : @error zerr::ZDDY0021 if the index with name $name is not declared.
 : @error zerr::ZDDY0023 if the index with name $name does not exist.
 : @error zerr::ZDDY0025 if the number of keys passed as arguments
 :    does not match the number of keys declared for the index.
 :)
declare %ann:variadic function dml:probe-index-point-value(
  $name as xs:QName,
  $key_i as xs:anyAtomicType?) as node()*  external;

(:~
 : The probe function retrieves the domain nodes associated with a particular
 : search condition, which is presented as a range of key tuples.
 : This function accept a variable number of arguments.
 :
 : To describe the semantics of this function, we start by defining the i-th
 : key column of an index as the set of key items produced by evaluating the
 : i-th keyspec of the index for every domain node. Then, the search condition
 : of a range probe can be defined as a number of rangespecs, where a
 : rangespec describes a constraint on the values of a key column. The first
 : rangespec applies to the first key column, the second rangespec to the
 : second key column, etc. The number of rangespecs must be less or equal to
 : the number of keyspecs found in the declaration of the given index.
 : Each rangespec consists of six values (i.e. rangeLowerBound_i,
 : rangeUpperBound_i, rangeHaveLowerBound_i, rangeHaveUpperBound_i,
 : rangeLowerBoundIncluded_i, rangeUpperBoundIncluded_i). Because the
 : function is declared as variadic, several rangespecs can be passed
 : as arguments to the function.
 :
 : @param $name The QName of the index to probe
 : @param $rangeLowerBound1 The lower bound in a range of key values.
 : @param $rangeUpperBound1 The upper bound in a range of key values.
 : @param $rangeHaveLowerBound If false, then there is no lower bound, or
 :          equivalently, the lower bound is -INFINITY. Otherwise, the lower
 :          bound is the one given by the rangeLowerBound value.
 : @param $rangeHaveUpperBound If false, then there is no upper bound, or
 :          equivalently, the upper bound is +INFINITY. Otherwise, the upper
 :          bound is the one given by the rangeUpperBound value.
 : @param $rangeLowerBoundIncluded If false, then the range is open from below,
 :          i.e., the rangeLowerBound value is not considered part of the
 :          range. Otherwise, the range is closed from below, i.e., the
 :          rangeLowerBound value is part of the range.
 : @param $rangeUpperBoundIncluded If false, then the range is open from above,
 :          i.e., the rangeUpperBound value is not considered part of the
 :          range. Otherwise, the range is closed from above, i.e., the
 :          rangeUpperBound value is part of the range.
 : @return The sequence of domain nodes matching this range probe.
 :
 : @error zerr::ZDDY0021 if the index with name $name is not declared.
 : @error zerr::ZDDY0023 if the index with name $name does not exist.
 : @error zerr::ZDDY0025 if the number of rangespecs passed as arguments
 :    does not match the number of keys declared for the index.
 :)
declare %ann:variadic function dml:probe-index-range-value($name as xs:QName,
  $rangeLowerBound1         as xs:anyAtomicType?,
  $rangeUpperBound1         as xs:anyAtomicType?,
  $rangeHaveLowerBound1     as xs:boolean,
  $rangeHaveupperBound1     as xs:boolean,
  $rangeLowerBoundIncluded1 as xs:boolean,
  $rangeupperBoundIncluded1 as xs:boolean) as node()*  external;

(:~
 : The refresh-index function is an updating function which updates
 : the index with the given name.
 : Note that if the maintenance property of the index is automatic,
 : this function is a NOP.
 :
 : @param $name The QName of the index to refresh.
 : @return The result of the function is an empty XDM instance and a
 :         pending update list which, when applied, refreshes the contents
 :         of the index.that consists of a
 : @error zerr::ZDDY0021 if the index with name $name is not declared.
 : @error zerr::ZDDY0023 if the index with name $name does not exist.
 :)
declare updating function dml:refresh-index($name as xs:QName) external;

(:
 : Copyright 2006-2009 The FLWOR Foundation.
 :
 : Licensed under the Apache License, Version 2.0 (the "License");
 : you may not use this file except in compliance with the License.
 : You may obtain a copy of the License at
 :
 : http://www.apache.org/licenses/LICENSE-2.0
 :
 : Unless required by applicable law or agreed to in writing, software
 : distributed under the License is distributed on an "AS IS" BASIS,
 : WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 : See the License for the specific language governing permissions and
 : limitations under the License.
:)

(:~
 : This module defines a set of functions to probe indexes.
 :
 : @see http://www.zorba-xquery.com/modules/store/static/collections/ddl
 : @see http://www.zorba-xquery.com/modules/store/static/indexes/ddl
 : @see http://www.zorba-xquery.com/modules/store/static/indexes/dml
 :
 : @author Nicolae Brinza, Matthias Brantner, David Graf, Till Westmann, Markos Zaharioudakis
 : @project store/indexes/static
 :
 :)
module namespace dml = "http://www.zorba-xquery.com/modules/store/static/indexes/dml";

declare namespace ann = "http://www.zorba-xquery.com/annotations";

declare namespace ver = "http://www.zorba-xquery.com/options/versioning";
declare option ver:module-version "2.0";

(:~
 : The probe function retrieves the domain nodes associated with a particular
 : search condition, which is presented as a single key tuple. This function
 : accept a variable number of arguments.
 :
 :   dml:probe-index-point($indexUri as xs:QName,
 :                        $key1     as xs:anyAtomictType,
 :                        ...,
 :                        $keyM     as xs:anyAtomicType) as node()*
 :
 : The first argument is always a QName identifying an index. The rest of the
 : arguments specify the search condition, which is given as a number of
 : atomic items comprising a key tuple. This number must be equal to the number
 : of indexspecs found in the index declaration [err:XDDY0025]. If the index
 : contains an entry with the given key tuple, the associated domain nodes are
 : returned. Otherwise, the empty sequence is returned.
 :
 : @param $name The QName of the index.
 : @param $key1 The search condition.
 : @return The sequence of domain nodes or an empty sequence.
 : @error If available indexes does not provide a mapping for
 :        the expanded QName $name.
 :)
declare %ann:variadic function dml:probe-index-point-value(
  $name as xs:QName,
  $key1 as xs:anyAtomicType*) as node()*  external;

(:~
 : The probe function retrieves the domain nodes associated with a particular
 : search condition, which is presented as a range of key tuples. This function
 : accept a variable number of arguments.
 :
 : dml:probe-index-range($indexUri                 as xs:QName,
 :                         $rangeLowerBound1         as xs:anyAtomicType?,
 :                         $rangeUpperBound1         as xs:anyAtomicType?,
 :                         $rangeHaveLowerBound1     as xs:boolean,
 :                         $rangeHaveupperBound1     as xs:boolean,
 :                         $rangeLowerBoundIncluded1 as xs:boolean,
 :                         $rangeupperBoundIncluded1 as xs:boolean,
 :                         ....,
 :                         $rangeLowerBoundM         as xs:anyAtomicType?,
 :                         $rangeUpperBoundM         as xs:anyAtomicType?,
 :                         $rangeHaveLowerBoundM     as xs:boolean,
 :                         $rangeHaveupperBoundM     as xs:boolean,
 :                         $rangeLowerBoundIncludedM as xs:boolean,
 :                         $rangeupperBoundIncludedM as xs:boolean) as node()*
 :
 : To describe the semantics of this function, we start by defining the i-th
 : key column of an index as the set of key items produced by evaluating the
 : i-th keyspec of the index for every domain node. Then, the search condition
 : of a range probe can be defined as a number of rangespecs, where a
 : rangespec describes a constraint on the values of a key column. The first
 : rangespec applies to the first key column, the second rangespec to the
 : second key column, etc. The number of rangespecs must be less or equal to
 : the number of keyspecs found in the declaration of the given index.
 : Each rangespec consists of 6 values:
 :
 :   * rangeLowerBound : The lower bound in a range of key values.
 :   * rangeUpperBound : The upper bound in a range of key values.
 :   * rangeHaveLowerBound : If false, then there is no lower bound, or
 :     equivalently, the lower bound is -INFINITY (the actual rangeLowerBound
 :     value is ignored). Otherwise, the lower bound is the one given by the
 :     rangeLowerBound value. The effective lower bound of the range is either
 :     the rangeLowerBound if rangeHaveLowerBound is true, or -INFINITY if
 :     rangeHaveLowerBound is false.
 :   * rangeHaveUpperBound : If false, then there is no upper bound, or
 :     equivalently, the upper bound is +INFINITY (the actual rangeUpperBound
 :     value is ignored). Otherwise, the upper bound is the one given by the
 :     rangeUpperBound value. The effective upper bound of the range is either
 :     the rangeUpperBound if rangeHaveUpperBound is true, or +INFINITY if
 :     rangeHaveUpperBound is false.
 :   * rangeLowerBoundIncluded : If false, then the range is open from below,
 :     i.e., the rangeLowerBound value is not considered part of the range.
 :     Otherwise, the range is closed from below, i.e., the rangeLowerBound
 :     value is part of the range.
 :   * rangeUpperBoundIncluded : If false, then the range is open from above,
 :     i.e., the rangeUpperBound value is not considered part of the range.
 :     Otherwise, the range is closed from above, i.e., the rangeUpperBound
 :     value is part of the range.
 :
 : @param $name The QName of the index.
 : @param $rangeLowerBound1 The lower bound in a range of key values.
 : @param $rangeUpperBound1 The upper bound in a range of key values.
 : @param $rangeHaveLowerBound If false, then there is no lower bound, or
 :          equivalently, the lower bound is -INFINITY. Otherwise, the lower
 :          bound is the one given by the rangeLowerBound value.
 : @param $rangeHaveUpperBound If false, then there is no upper bound, or
 :          equivalently, the upper bound is +INFINITY. Otherwise, the upper
 :          bound is the one given by the rangeUpperBound value.
 : @param $rangeLowerBoundIncluded If false, then the range is open from below,
 :          i.e., the rangeLowerBound value is not considered part of the
 :          range. Otherwise, the range is closed from below, i.e., the
 :          rangeLowerBound value is part of the range.
 : @param $rangeUpperBoundIncluded If false, then the range is open from above,
 :          i.e., the rangeUpperBound value is not considered part of the
 :          range. Otherwise, the range is closed from above, i.e., the
 :          rangeUpperBound value is part of the range.
 : @return The sequence of domain nodes.
 : @error If available indexes does not provide a mapping for
 :        the expanded QName $name.
 :)
declare %ann:variadic function dml:probe-index-range-value($name as xs:QName,
  $rangeLowerBound1         as xs:anyAtomicType?,
  $rangeUpperBound1         as xs:anyAtomicType?,
  $rangeHaveLowerBound1     as xs:boolean,
  $rangeHaveupperBound1     as xs:boolean,
  $rangeLowerBoundIncluded1 as xs:boolean,
  $rangeupperBoundIncluded1 as xs:boolean) as node()*  external;


adding explanation of duration function

adding explanation of duration function

xquery version "3.1";
declare namespace ebb="http://newtfire.org";
declare variable $rocketColl := collection('/db/rocket/');
(: ebb: This script introduces you to user-defined functions in XQuery.
These can be useful for taking values from your XQuery variables and performing a conversion, as we're doing here.
Since the Rocket Launches project has multiple attributes encoding launch dates and landing dates in the same xs:dateTime format,
and since they work with the xs:duration datatype too, for coding how long a mission lasted,
we can work with these data to do "date arithmetic" to make simple graphs and charts if we can convert it to a simple decimal notation.
Let's start reading this by skipping past the section I've marked off with asterisks as USER-DEFINED FUNCTIONS, and come back to it :)

(: **************** USER-DEFINED FUNCTIONS **************************:)
(: CONVERT DATETIME to DECIMAL :)
(: ebb :This user-defined function will unpack the date components from an input xs:dateTime datatype.
We will first extract the year from the datatype with year-from-dateTime(),
and then use format-dateTime() to return a 'picture string' that will give us the date's numerical position in the year.
We then divide that date number by 365 (since most years have 365 days).
This will give us a decimal format we can use in making charts and graphs. :)
declare function ebb:dateDecimalConverter($dT as xs:dateTime)
as xs:decimal?
{
let $year := year-from-dateTime($dT) ! xs:integer(.)
let $dayInYear := format-dateTime($dT, '[d]') ! xs:integer(.)
let $decimalDay := $dayInYear div 365
return $year + $decimalDay
};
(: CONVERT DURATION TO DECIMAL :)
(: ebb: The next user-defined function will convert the xs:duration datatype into a decimal value based on number of days.
 : If we input a duration indicating 1 day, 6 hours, and 0 minutes, we should return 1.25 from this function.
 : Our function takes four input arguments: $d (a value for the number of days), $h (a value for the number of hours),
 : $m (for the number of minutes), and $s (for the number of seconds). In our XQuery, we created variables to separate each of these values
 : and submitted them as the input to this function.
 : Here's what we're calculating: There are 24 hours in a day. (Divide the H portion by 24) and add it to the days.
 : There are 60 minutes in an hour, and 60 * 24 gives the total number of minutes in a day.
 : So we divide the M portion by 60 * 24) and add it to hours.
 : There are 60 seconds in a minute. and 60 * 60 * 24 gives us the total number of seconds in a day.
 : We then divide the S portion by 60 * 60 * 24 and add that value to minutes and hours to give us
 : the decimal proporition of a day in the duration. Finally we add that to the number of days in the duration.
 : :)
declare function ebb:durationConverter($d as xs:integer, $h as xs:integer, $m as xs:integer, $s as xs:integer?)
as xs:decimal?
{
let $TDec := $h div 24 + $m div (60 * 24) + $s div (60 * 60 * 24)
return $d + $TDec
};
(: *******************END USER-DEFINED FUNCTIONS ********************** :)

let $launchDateTimes := $rocketColl//launch/@sDateTime
for $l in $launchDateTimes
(: To do date arithmetic, take a look at these functions: https://www.w3.org/TR/xpath-functions-31/#dates-times
 : We'll write a user-defined function to convert this datatype into a decimal notation. :)
let $lDec := ebb:dateDecimalConverter($l)
let $m := $rocketColl//launch[@sDateTime = $l]/preceding-sibling::sts
let $mID := $m  ! tokenize(., ':')[1]

let $duration := $m/following-sibling::duration/@time
(: ebb: To do duration arithmetic, start by looking at the functions here:
: https://www.w3.org/TR/xpath-functions-31/#durations
 : Let's try representing durations in terms of days with a decimal.
 : We'll write a user-defined function to convert hours, minutes, and seconds into a fraction of the day. :)
let $durDays := days-from-duration($duration)
let $durHours := hours-from-duration($duration)
let $durMins := minutes-from-duration($duration)
let $durSecs := seconds-from-duration($duration)
(: Now in line 43, we send our variables to our user-defined function for turning date datatypes into decimal values. :)
let $durDayDec := ebb:durationConverter($durDays, $durHours, $durMins, $durSecs)

order by $lDec
return concat('Launch Date: ', $l, ', mission: ', $mID, ': This Launch Decimal Date: ', $lDec,
': Duration: ', $duration, ': Decimal Notation:', $durDayDec)

xquery version "3.1";
declare namespace ebb="http://newtfire.org";
declare variable $rocketColl := collection('/db/rocket/');
(: ebb: This script introduces you to user-defined functions in XQuery.
These can be useful for taking values from your XQuery variables and performing a conversion, as we're doing here.
Since the Rocket Launches project has multiple attributes encoding launch dates and landing dates in the same xs:dateTime format,
and since they work with the xs:duration datatype too, for coding how long a mission lasted,
we can work with these data to do "date arithmetic" to make simple graphs and charts if we can convert it to a simple decimal notation.
Let's start reading this by skipping past the section I've marked off with asterisks as USER-DEFINED FUNCTIONS, and come back to it :)

(: **************** USER-DEFINED FUNCTIONS **************************:)
(: CONVERT DATETIME to DECIMAL :)
(: ebb :This user-defined function will unpack the date components from an input xs:dateTime datatype.
We will first extract the year from the datatype with year-from-dateTime(),
and then use format-dateTime() to return a 'picture string' that will give us the date's numerical position in the year.
We then divide that date number by 365 (since most years have 365 days).
This will give us a decimal format we can use in making charts and graphs. :)
declare function ebb:dateDecimalConverter($dT as xs:dateTime)
as xs:decimal?
{
let $year := year-from-dateTime($dT) ! xs:integer(.)
let $dayInYear := format-dateTime($dT, '[d]') ! xs:integer(.)
let $decimalDay := $dayInYear div 365
return $year + $decimalDay
};
(: CONVERT DURATION TO DECIMAL :)
(: ebb: The next user-defined function will convert the xs:duration datatype into a decimal value based on number of days.
 : So if we input a duration indicating 1 day, 6 hours, and 0 minutes, we should return 1.25 from this function.
 : Here's what we're calculating: There are 24 hours in a day. (Divide the H portion by 24) and add it to the days.
 : There are 60 minutes in an hour, and 60 * 24 gives the total number of minutes in a day.
 : So we divide the M portion by 60 * 24) and add it to hours.
 : There are 60 seconds in a minute. and 60 * 60 * 24 gives us the total number of seconds in a day.
 : We then divide the S portion by 60 * 60 * 24 and add that value to minutes and hours.
 : :)
declare function ebb:durationConverter($d as xs:integer, $h as xs:integer, $m as xs:integer, $s as xs:integer?)
as xs:decimal?
{
let $TDec := $h div 24 + $m div (60 * 24) + $s div (60 * 60 * 24)
return $d + $TDec
};
(: *******************END USER-DEFINED FUNCTIONS ********************** :)

let $launchDateTimes := $rocketColl//launch/@sDateTime
for $l in $launchDateTimes
(: To do date arithmetic, take a look at these functions: https://www.w3.org/TR/xpath-functions-31/#dates-times
 : We'll write a user-defined function to convert this datatype into a decimal notation. :)
let $lDec := ebb:dateDecimalConverter($l)
let $m := $rocketColl//launch[@sDateTime = $l]/preceding-sibling::sts
let $mID := $m  ! tokenize(., ':')[1]

let $duration := $m/following-sibling::duration/@time
(: ebb: To do duration arithmetic, start by looking at the functions here:
: https://www.w3.org/TR/xpath-functions-31/#durations
 : Let's try representing durations in terms of days with a decimal.
 : We'll write a user-defined function to convert hours, minutes, and seconds into a fraction of the day. :)
let $durDays := days-from-duration($duration)
let $durHours := hours-from-duration($duration)
let $durMins := minutes-from-duration($duration)
let $durSecs := seconds-from-duration($duration)
(: Now in line 43, we send our variables to our user-defined function for turning date datatypes into decimal values. :)
let $durDayDec := ebb:durationConverter($durDays, $durHours, $durMins, $durSecs)

order by $lDec
return concat('Launch Date: ', $l, ', mission: ', $mID, ': This Launch Decimal Date: ', $lDec,
': Duration: ', $duration, ': Decimal Notation:', $durDayDec)