Added Error Handling for devNull MDR Mappings AFTER checking for Skipped MDR Mappings.

(:
 : 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 fqt = "http://further.utah.edu/query-translation";

(: Import FURTHeR Module :)
(: Change the location of further.xq when working locally :)
(: import module namespace further = "http://further.utah.edu/xquery-functions-module"
    at "../fqe/further/xq/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 :)
(: Change the location of constants.xq when working locally :)
(: import module namespace const = 'http://further.utah.edu/constants'
    at '../fqe/further/xq/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";
(: DO NOT USE DEFAULT NAMESPACE, it may conflict with the response Namespaces :)

(: Global CONSTANTS, Always Translating FROM FURTHeR Namespace ID :)
(: declare variable $fqt:FURTHER := 'FURTHeR'; :)
declare variable $fqt:FURTHER as xs:string := '32769';
(: We are using SNOMED (Namespace ID 30) for ObservationType Values :)
declare variable $fqt:SNOMED as xs:string := '30';

(: OMOP has a special case where ICD-9 needs to be translated to SNOMED first,
   before translating to OMOP-V2
   SNOMED uses the Standard 'Code in Source' Property Name :)
declare variable $fqt:OMOP-V2 as xs:string := '32868';
declare variable $fqt:ICD-9 as xs:string := '10';
declare variable $fqt:LOINC as xs:string := '5102';

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

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

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

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

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

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

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

declare variable $fqt:skipATTR as xs:string := 'skipAttr';
declare variable $fqt:translateCode as xs:string := 'translateCode';
declare variable $fqt:ageToBirthYear as xs:string := 'ageToBirthYear';
declare variable $fqt:devNull as xs:string := 'devNull';

(: DTS Static Property Names CASE SENSITIVE! :)
declare variable $fqt:dtsSrcPropNm as xs:string := 'Code in Source';
declare variable $fqt:dtsTgPropName as xs:string := 'Local Code';

(: Criteria searchType :)
declare variable $fqt:SIMPLE as xs:string := 'SIMPLE';
declare variable $fqt:BETWEEN as xs:string := 'BETWEEN';
declare variable $fqt:LIKE as xs:string := 'LIKE';
declare variable $fqt:IN as xs:string := 'IN';

(:==================================================================:)
(: Main Translate Query Function                                    :)
(:==================================================================:)
declare function fqt:transQuery($inputXML as document-node(),$targetNamespaceId as xs:string)
{
  (: Get the Namespace Name Using the Namespace ID :)
  let $tgNmspcName := further:getNamespaceName($targetNamespaceId)

  (: Must Keep this Order of Processing :)

  (: Call Initialization :)
  let $initializedInput := fqt:initQueryTranslation($inputXML)

  (: Call insertObsType :)
  let $obsTypeInput := fqt:insertObsType($initializedInput)

  (: Call preTransOMOP :)
  let $preTranslatedOMOP := fqt:preTransOMOP($obsTypeInput,$targetNamespaceId)

  (: Call transCriteriaPhrase :)
  let $translatedCriteriaPhrase := fqt:transCriteriaPhrase($preTranslatedOMOP,$targetNamespaceId)

  (: Call transSingleCriteria :)
  let $translatedSingleCriteria := fqt:transSingleCriteria($translatedCriteriaPhrase,$targetNamespaceId)

  (: Call transAlias :)
  let $translatedAlias := fqt:transAlias($translatedSingleCriteria,$targetNamespaceId)

  (: Call updatedParmAlias :)
  let $updatedParmAlias := fqt:updateParmAlias($translatedAlias)

  (: Call transRoot :)
  let $translatedRoot := fqt:transRoot($updatedParmAlias,$targetNamespaceId)

  (: Call dedupAliases :)
  let $dedupedAliases := fqt:dedupAliases($translatedRoot)

  (: Call validate for Errors :)
  let $validated := fqt:validate($dedupedAliases,$tgNmspcName)

  (: Call cleanup :)
  let $cleaned := fqt:cleanup($validated)

  (: Return Final Cleaned Version :)
  return $cleaned

};

(:==================================================================:)
(: Initialization = Mark All Criterias as NOT Translated            :)
(:==================================================================:)
declare function fqt:initQueryTranslation($inputXML as document-node())
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy1 := $inputXML
modify (

  (: For rootCriterion nodes that needs to be Translated
     Insert Attribute and Rename them as a <criteria> node
     The rename has to be done in a separate transformation,
     since we cannot change the same node in one xquery transformation :)
  for $rootCriterion in $inputCopy1//fq:rootCriterion[fq:parameters]
    return insert node attribute rootCriterion {$fqt:YES} into $rootCriterion

) (: End Modify :)
return
copy $inputCopy2 := $inputCopy1
modify (

  (: Rename <rootCriterion> to <criteria> if it needs processing :)
  for $rootCriterion in $inputCopy2//fq:rootCriterion[fq:parameters]
    return
      rename node $rootCriterion as QName("http://further.utah.edu/core/query", "criteria")

) (: End Modify :)
return
copy $inputCopy3 := $inputCopy2
modify (

  (: Insert Flags for each Criteria that needs Tranlation :)
  for $criteria in $inputCopy3//fq:criteria[fq:parameters]
    return (
      insert node attribute transFlag {$fqt:NO} into $criteria,
      insert node attribute mdrFlag {$fqt:NO} into $criteria
    )
  ,
  (: dtsFlag needs to be at the parameter level since the IN searchType has many parameters :)
  for $parm in $inputCopy3//fq:parameter
    return insert node attribute dtsFlag {$fqt:NO} into $parm
  ,
  (: Insert Alias Key as Parameter Attribute if Parameter Starts With Alias :)
  (: Every Alias Key should be Unique within the Entire Input XML, including SubQuery :)
  for $alias in $inputCopy3//fq:alias
    let $aliasKey := $alias/fq:key/text()
    for $parm in $inputCopy3//fq:parameter
      let $parmText := $parm/text()
      return
        if (starts-with($parmText,concat($aliasKey,'.')))then
          insert node attribute aliasKey {$aliasKey} into $parm
        else()

) (: End Modify :)
return $inputCopy3

(: END Function :)
};

(:==================================================================:)
(: Translate Single Criteria                                        :)
(:==================================================================:)
declare function fqt:transSingleCriteria($inputXML as document-node(),
                                         $targetNamespaceId as xs:string)
{ (: BEGIN FUNCTION :)

  (: DO NOT REMOVE THESE COMMENTS HERE! :)
  (: There are 3 things we need to do here :)
  (: Process Singles at the Most Outer Level :)
  (: Process Singles at SubQuery Outer Level :)
  (: Process Singles Inside Each SubQuery :)

  (: We can do the above 3 things in 2 Transformation steps :)
  (: We need to separate the Transformations because the Pending Update List will Conflict with each other :)

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

  (: IMPORTANT!!! Use this FLWOR to DEBUG the Single Criteria :)
  (: The Single Criteria may contain an ENTIRE SubQuery! :)
  (: for $criteria in $inputCopy//fq:criteria[@transFlag = "N"] :)
  (:  return :)
  (:  replace node $criteria with <MO_DEBUG_SINGLE_CRITERIA>{$criteria}</MO_DEBUG_SINGLE_CRITERIA> :)

  (: For Criterias that are NOT SubQuery Outer Criteria :)
  (: Meaning all the Atomic (No Children) Criterias :)
  for $atomicCriteria in $inputCopy//fq:criteria[@transFlag=$fqt:NO and not(fq:query)]
    (: DEBUG :)
    (: return replace value of node $atomicCriteria/@transFlag with 'MO' :)
    return
      replace node $atomicCriteria with
      fqt:transCriteria($atomicCriteria,$fqt:FURTHER,$targetNamespaceId,$fqt:EMPTY,$fqt:EMPTY)

)
(: Return to a Second Transformation :)

return
(: BEGIN Second XQUERY TRANSFORMATION :)
(: Make a copy of the First Document :)
copy $inputCopy2 := $inputCopy
modify (

  (: IMPORTANT!!! Use this FLWOR to DEBUG the Single Criteria :)
  (: The Single Criteria may contain an ENTIRE SubQuery! :)
  (: for $criteria in $inputCopy2//fq:criteria[@transFlag = "N"] :)
  (:  return :)
  (:  replace node $criteria with <MO_DEBUG_SINGLE_CRITERIA>{$criteria}</MO_DEBUG_SINGLE_CRITERIA> :)

  (: Process the Outer Criteria for each SubQuery :)
  for $subQueryCriteria in $inputCopy2//fq:criteria[@transFlag=$fqt:NO and fq:query]
    (: DEBUG :)
    (: return replace value of node $subQueryCriteria/@transFlag with 'MO' :)
    return
      replace node $subQueryCriteria with
      fqt:transCriteria($subQueryCriteria,$fqt:FURTHER,$targetNamespaceId,$fqt:EMPTY,$fqt:EMPTY)

)
return $inputCopy2

(: END XQUERY TRANSFORMATION :)
};

(:==================================================================:)
(: Translate Criteria Details                                       :)
(: criteriaType & criteriaTypeNmspcId are Optional (Can be Blank)   :)
(:==================================================================:)
declare function fqt:transCriteria($criteria as node(),
                                   $srcNamespaceId as xs:string,
                                   $tgNamespaceId as xs:string,
                                   $criteriaType as xs:string?,
                                   $criteriaTypeNmspcId as xs:string?)
{ (: BEGIN FUNCTION :)

copy $c := $criteria
modify (

  (: Get Search Type for Current Criteria Node :)
  let $searchType := upper-case($c/fq:searchType)

  (: Switch Case to Select which Parameters to Process Based on Search Type :)
  (: Switch Requires XQuery 1.1 or Above, but is easier to read and more scalable :)
  return switch ($searchType)

         case 'SIMPLE'
           (: Parameter[2]=Attribute Name, 3 = Attribute Value :)
           (: return ($criteria/parameters/parameter[2],$criteria/parameters/parameter[3]) :)
           return (

             (: Get the Source Attribute Name :)
             let $sourceAttrText := $c/fq:parameters/fq:parameter[2]/text()
             let $sourceAttrName :=
               if ($c/fq:parameters/fq:parameter[2 and @aliasKey]) then
                 let $aliasKey := fn:string($c/fq:parameters/fq:parameter[2]/@aliasKey)
                 return substring-after($sourceAttrText, concat($aliasKey,'.') )
               else
                 $sourceAttrText

             (: Strip out the Last Token in the Attribute Text :)
             (: tokenize will return the entire string if separator is not found in string,
             whereas string-after or string-after-last will return empty string.
             Therefore, use tokenize and get the last token. Cool! :)
             (: Data Format = RootEntity.TableName.AttributeName :)
             (: The sourceAttrName needs to be set here so i can reference it in the return fn:replace :)
             (: let $sourceAttrName := fn:tokenize($sourceAttrText,'\.')[last()] :)
             (: let $mdrResponse := fqt:getMDRAttrURL($sourceAttrName,$fqt:FURTHER,$tgNamespaceId) :)

             (: There should ONLY be ONE Result per source Attribute :)
             (: if there is more than one result, the criteriaType will determine which result is appropriate :)
             let $mdrResult := fqt:getMDRResult($sourceAttrName,$fqt:FURTHER,$tgNamespaceId,$criteriaType)

             (: DEBUG :)
             (: let $mdrURL := fqt:getMDRAttrURL($sourceAttrName,$fqt:FURTHER,$tgNamespaceId) :)
             (: let $mdrTranslatedAttrName := 'Translated MDR Parm2 Name' :)

             return (

               (: DEBUG
               replace value of node $c/fq:parameters/fq:parameter[2]
                  with $mdrURL :)

               (: if there is a Result, ALWAYS Set translatedAttrName (Except for SKIP) :)
               (: The datatype for attribute name is always String,
                  so there is no need for that Translation :)
               if ($mdrResult[translatedAttribute]) then

                 (: if need to skip, update mdrFlag as fqt:SKIP :)
                 if ($mdrResult/properties/entry[key=$fqt:ATTR_TRANS_FUNC and value=$fqt:skipATTR]) then
                   replace value of node $c/@mdrFlag with $fqt:SKIP
                 (: Error Out devNull Associations AFTER checking for Skipped :)
                 else if ($mdrResult/translatedAttribute=$fqt:devNull) then
                   replace value of node $c/@mdrFlag with $fqt:ERROR
                 else
                   let $translatedAttrName := $mdrResult/translatedAttribute/text()
                   return (
                     replace value of node $c/fq:parameters/fq:parameter[2]
                        with fn:replace($sourceAttrText,$sourceAttrName,$translatedAttrName)
                        (: with $mdrURL :)
                     ,
                     replace value of node $c/@mdrFlag with $fqt:YES
                   )
               else (
                 replace value of node $c/@mdrFlag with $fqt:ERROR
                 (: DEBUG :)
                 (: replace value of node $c/fq:parameters/fq:parameter[2]
                    with concat('Debug_No_MDR_Translation_For=',$sourceAttrName) :)
               )

               , (: Process Code Translation Next :)

               (: Determine if we need to Call DTS to Translate Coded Value :)
               if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:translateCode]) then

                 let $dtsSrcPropVal := $c/fq:parameters/fq:parameter[3]/text()

                 (: Determine the Source Namespace ID since one is for the code, and the other is for the type :)
                 (: This ONLY Happens to SIMPLE, Since that is where the Phrases Occur :)
                 let $srcNamespaceId :=
                   if ($sourceAttrName = 'observationType') then
                     $criteriaTypeNmspcId
                   else
                     $srcNamespaceId

                 (: Call DTS :)
                 let $dtsResponse := further:getTranslatedConcept($srcNamespaceId,
                                                                  $fqt:dtsSrcPropNm,
                                                                  $dtsSrcPropVal,
                                                                  $tgNamespaceId,
                                                                  $fqt:dtsTgPropName)
                 (: DEBUG DTS URL :)
                 (: let $dtsURL := further:getConceptTranslationRestUrl($srcNamespaceId,
                                                                     $fqt:dtsSrcPropNm,
                                                                     $dtsSrcPropVal,
                                                                     $tgNamespaceId,
                                                                     $fqt:dtsTgPropName) :)

                 (: let $debug := concat($srcNamespaceId,'|',
                                      $fqt:dtsSrcPropNm,'|',
                                      $dtsSrcPropVal,'|',
                                      $tgNamespaceId,'|',
                                      $fqt:dtsTgPropName) :)

                 (: let $translatedPropVal := $dtsResponse/dts:concepts/dts:conceptId/propertyValue/text() :)
                 let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

                 return
                   if ($translatedPropVal) then (
                     replace value of node $c/fq:parameters/fq:parameter[3]
                        with $translatedPropVal
                     ,
                     replace value of node $c/fq:parameters/fq:parameter[3]/@dtsFlag with $fqt:YES
                   )
                   else
                     (: Always return Error if there is no DTS Mapping :)
                     replace value of node $c/fq:parameters/fq:parameter[3]/@dtsFlag with $fqt:ERROR

               else if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:ageToBirthYear]) then

                 (: Get the Source Value to be Translated :)
                 let $srcVal := $c/fq:parameters/fq:parameter[3]/text()
                 return
                   replace value of node $c/fq:parameters/fq:parameter[3]
                      with fqt:ageToBirthYear($srcVal)
               else()

               , (: Determine if we need to Translate the DataType :)
               (: WHY IS THERE NO ATTR_VALUE_TRANS_TO_DATA_TYPE in the MDR for the Attributes that NEEDS it! :)
               (: Try ask Rick, maybe it is currently hard coded :)
               for $entry in $mdrResult//entry[key=$fqt:ATTR_VALUE_TRANS_TO_DATA_TYPE]
                 let $newDataType := $entry/value/text()
                 return replace value of node $c/fq:parameters/fq:parameter[3]/@xsi:type with $newDataType

               , (: Do More Stuff :)
               (: Always Set Translation Flag after Translation :)
               if ($mdrResult[translatedAttribute]) then
                 replace value of node $c/@transFlag with $fqt:YES
               else replace value of node $c/@transFlag with $fqt:ERROR

             )

           ) (: END SIMPLE Return :)

         case 'BETWEEN'
           (: Parameter[1]=Attribute Name, 2 & 3 = Attribute Value :)
           return (

             (: Get the Source Attribute Name :)
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName :=
               if ($c/fq:parameters/fq:parameter[1 and @aliasKey]) then
                 let $aliasKey := fn:string($c/fq:parameters/fq:parameter[1]/@aliasKey)
                 return substring-after($sourceAttrText, concat($aliasKey,'.') )
               else
                 $sourceAttrText

             (: Call MDR Web Service to Translate Attribute Name HERE :)
             let $mdrResult := fqt:getMDRResult($sourceAttrName,$fqt:FURTHER,$tgNamespaceId,$criteriaType)
             (: let $mdrURL := fqt:getMDRAttrURL($sourceAttrName,$fqt:FURTHER,$tgNamespaceId) :)
             (: let $translatedAttrName := 'Translated MDR Name' :)

             return (
               (: if there is a Result, ALWAYS Set translatedAttrName :)
               (: The datatype for attribute name is always String,
                  so there is no need for that Translation :)
               if ($mdrResult/.[translatedAttribute]) then

                 (: if need to skip, update mdrFlag as fqt:SKIP :)
                 if ($mdrResult/properties/entry[key=$fqt:ATTR_TRANS_FUNC and value=$fqt:skipATTR]) then
                   replace value of node $c/@mdrFlag with $fqt:SKIP
                 else
                   let $translatedAttrName := $mdrResult/translatedAttribute/text()
                   return (
                     replace value of node $c/fq:parameters/fq:parameter[1]
                        with fn:replace($sourceAttrText,$sourceAttrName,$translatedAttrName)
                     ,
                     replace value of node $c/@mdrFlag with $fqt:YES
                   )
               else (
                 replace value of node $c/@mdrFlag with $fqt:ERROR
                 (: DEBUG :)
                 (: replace value of node $c/fq:parameters/fq:parameter[1]
                    with concat('Debug_No_MDR_Translation_For=',$sourceAttrName) :)
               )

               , (: Process Code Translation Next Parameters 2 & 3 :)

               (: Determine if we need to Call DTS to Translate Coded Value :)
               if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:translateCode]) then

                 let $dtsSrcPropVal2 := $c/fq:parameters/fq:parameter[2]/text()
                 let $dtsSrcPropVal3 := $c/fq:parameters/fq:parameter[3]/text()

                 (: Call DTS for Parameter 2 :)
                 let $dtsResponse2 := further:getTranslatedConcept($srcNamespaceId,
                                                                   $fqt:dtsSrcPropNm,
                                                                   $dtsSrcPropVal2,
                                                                   $tgNamespaceId,
                                                                   $fqt:dtsTgPropName)

                 (: Call DTS for Parameter 3 :)
                 let $dtsResponse3 := further:getTranslatedConcept($srcNamespaceId,
                                                                   $fqt:dtsSrcPropNm,
                                                                   $dtsSrcPropVal3,
                                                                   $tgNamespaceId,
                                                                   $fqt:dtsTgPropName)
                 (: Debug DTS URL :)
                 (: let $dtsURL := further:getConceptTranslationRestUrl($srcNamespaceId,
                                                                     $fqt:dtsSrcPropNm,
                                                                     $dtsSrcPropVal2,
                                                                     $tgNamespaceId,
                                                                     $fqt:dtsTgPropName) :)

                 (: let $debug := concat($srcNamespaceId,'|',
                                      $fqt:dtsSrcPropNm,'|',
                                      $dtsSrcPropVal2,'|',
                                      $tgNamespaceId,'|',
                                      $fqt:dtsTgPropName) :)

                 let $translatedPropVal2 := further:getConceptPropertyValue($dtsResponse2)
                 let $translatedPropVal3 := further:getConceptPropertyValue($dtsResponse3)

                 return (

                   if ($translatedPropVal2) then (
                     replace value of node $c/fq:parameters/fq:parameter[2]
                        with $translatedPropVal2,
                     replace value of node $c/fq:parameters/fq:parameter[2]/@dtsFlag with $fqt:YES
                   )
                   else (: ERROR :)
                     replace value of node $c/fq:parameters/fq:parameter[2]/@dtsFlag with $fqt:ERROR
                   ,

                   if ($translatedPropVal3) then (
                     replace value of node $c/fq:parameters/fq:parameter[3]
                        with $translatedPropVal3,
                     replace value of node $c/fq:parameters/fq:parameter[3]/@dtsFlag with $fqt:YES
                   )
                   else (: ERROR :)
                     replace value of node $c/fq:parameters/fq:parameter[3]/@dtsFlag with $fqt:ERROR

                 ) (: End Replace Return :)

               else if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:ageToBirthYear]) then

                 (: Get the Source Value to be Translated :)
                 let $srcValue2 := $c/fq:parameters/fq:parameter[2]/text()
                 let $srcValue3 := $c/fq:parameters/fq:parameter[3]/text()

                 (: Reverse the Order of 2 & 3 for this Special Case Since the Smaller BirthYear Must come First :)
                 return (
                   replace value of node $c/fq:parameters/fq:parameter[2]
                      with fqt:ageToBirthYear($srcValue3),
                   replace value of node $c/fq:parameters/fq:parameter[3]
                      with fqt:ageToBirthYear($srcValue2)
                 )

               else()

               , (: Determine if we need to Translate the DataType :)
               (: WHY IS THERE NO ATTR_VALUE_TRANS_TO_DATA_TYPE in the MDR for the Attributes that NEEDS it! :)
               (: Try ask Rick, maybe it is currently hard coded :)
               for $entry in $mdrResult//entry[key=$fqt:ATTR_VALUE_TRANS_TO_DATA_TYPE]
                 let $newDataType := $entry/value/text()
                 return (
                   replace value of node $c/fq:parameters/fq:parameter[2]/@xsi:type with $newDataType
                   ,
                   replace value of node $c/fq:parameters/fq:parameter[3]/@xsi:type with $newDataType
                 )

               , (: Do More Stuff :)
               (: Always Set Translation Flag after Translation :)
               if ($mdrResult[translatedAttribute]) then
                 replace value of node $c/@transFlag with $fqt:YES
               else replace value of node $c/@transFlag with $fqt:ERROR

             ) (: End Second Return :)

           ) (: End BETWEEN Return :)

         case 'LIKE'
           (: Parameter[1]=Attribute Name, Parameter[2] = Attribute Value :)
           return (

             (: Get the Source Attribute Name :)
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName :=
               if ($c/fq:parameters/fq:parameter[1 and @aliasKey]) then
                 let $aliasKey := fn:string($c/fq:parameters/fq:parameter[1]/@aliasKey)
                 return substring-after($sourceAttrText, concat($aliasKey,'.') )
               else
                 $sourceAttrText
             (:
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName := fn:tokenize($sourceAttrText,'\.')[last()]
             :)
             (: Call MDR Web Service to Translate Attribute Name HERE :)
             let $mdrResult := fqt:getMDRResult($sourceAttrName,$fqt:FURTHER,$tgNamespaceId,$criteriaType)
             (: let $mdrURL := fqt:getMDRAttrURL($sourceAttrName,$fqt:FURTHER,$tgNamespaceId) :)
             (: let $translatedAttrName := 'Translated MDR Name' :)
             return (
               if ($mdrResult/.[translatedAttribute]) then

                 (: if need to skip, update mdrFlag as fqt:SKIP :)
                 if ($mdrResult/properties/entry[key=$fqt:ATTR_TRANS_FUNC and value=$fqt:skipATTR]) then
                   replace value of node $c/@mdrFlag with $fqt:SKIP
                 else
                   let $translatedAttrName := $mdrResult/translatedAttribute/text()
                   return (
                     replace value of node $c/fq:parameters/fq:parameter[1]
                             with fn:replace($sourceAttrText,$sourceAttrName,$translatedAttrName)
                     ,
                     replace value of node $c/@mdrFlag with $fqt:YES
                   )
               else (
                 replace value of node $c/@mdrFlag with $fqt:ERROR
                 (: DEBUG :)
                 (: replace value of node $c/fq:parameters/fq:parameter[1]
                    with concat('Debug_No_MDR_Translation_For=',$sourceAttrName) :)
               )

               , (: Comma to Separate Sequence Items :)

               (: Determine if we need to Call DTS to Translate Coded Value :)
               if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:translateCode]) then

                 let $dtsSrcPropVal := $c/fq:parameters/fq:parameter[2]/text()

                 (: Call DTS :)
                 let $dtsResponse := further:getTranslatedConcept($srcNamespaceId,
                                                                  $fqt:dtsSrcPropNm,
                                                                  $dtsSrcPropVal,
                                                                  $tgNamespaceId,
                                                                  $fqt:dtsTgPropName)
                 (: Debug DTS URL :)
                 (: let $dtsURL := further:getConceptTranslationRestUrl($srcNamespaceId,
                                                                     $fqt:dtsSrcPropNm,
                                                                     $dtsSrcPropVal,
                                                                     $tgNamespaceId,
                                                                     $fqt:dtsTgPropName) :)

                 (: let $dtsResponse := fqt:getDTSAttrValue($srcNamespaceId,$fqt:dtsSrcPropNm,$dtsSrcPropVal,$tgNamespaceId,$fqt:dtsTgPropName) :)
                 (: let $translatedPropVal := 'Translated MDR Parm3 Value' :)

                 (: let $debug := concat($srcNamespaceId,'|',
                                      $fqt:dtsSrcPropNm,'|',
                                      $dtsSrcPropVal,'|',
                                      $tgNamespaceId,'|',
                                      $fqt:dtsTgPropName) :)

                 (: let $translatedPropVal := $dtsResponse/dts:concepts/dts:conceptId/propertyValue/text() :)
                 let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

                 return
                   if ($translatedPropVal) then (
                     replace value of node $c/fq:parameters/fq:parameter[2]
                        with $translatedPropVal
                     ,
                     replace value of node $c/fq:parameters/fq:parameter[2]/@dtsFlag with $fqt:YES
                   )
                   else replace value of node $c/fq:parameters/fq:parameter[2]/@dtsFlag with $fqt:ERROR

               else if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:ageToBirthYear]) then

                 (: Get the Source Value to be Translated :)
                 let $srcVal := $c/fq:parameters/fq:parameter[2]/text()
                 return
                   replace value of node $c/fq:parameters/fq:parameter[2]
                      with fqt:ageToBirthYear($srcVal)

               else()

               , (: Determine if we need to Translate the DataType :)
               (: WHY IS THERE NO ATTR_VALUE_TRANS_TO_DATA_TYPE in the MDR for the Attributes that NEEDS it! :)
               (: Try ask Rick, maybe it is currently hard coded :)
               for $entry in $mdrResult//entry[key=$fqt:ATTR_VALUE_TRANS_TO_DATA_TYPE]
                 let $newDataType := $entry/value/text()
                 return replace value of node $c/fq:parameters/fq:parameter[2]/@xsi:type with $newDataType

               , (: Do More Stuff :)
               (: Always Set Translation Flag after Translation :)
               if ($mdrResult[translatedAttribute]) then
                 replace value of node $c/@transFlag with $fqt:YES
               else replace value of node $c/@transFlag with $fqt:ERROR

             ) (: End Second Return :)

           ) (: End LIKE Return :)

         case 'IN'
           (: Parameter[1]=Attribute Name, Multiple Attribute Values :)
           return(

             (: Get the Source Attribute Name :)
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName :=
               if ($c/fq:parameters/fq:parameter[1 and @aliasKey]) then
                 let $aliasKey := fn:string($c/fq:parameters/fq:parameter[1]/@aliasKey)
                 return substring-after($sourceAttrText, concat($aliasKey,'.') )
               else
                 $sourceAttrText

             (:
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName := fn:tokenize($sourceAttrText,'\.')[last()]
             :)
             (: Call MDR Web Service to Translate Attribute Name HERE :)
             let $mdrResult := fqt:getMDRResult($sourceAttrName,$fqt:FURTHER,$tgNamespaceId,$criteriaType)
             return (
               if ($mdrResult/.[translatedAttribute]) then

                 (: if need to skip, update mdrFlag as fqt:SKIP :)
                 if ($mdrResult/properties/entry[key=$fqt:ATTR_TRANS_FUNC and value=$fqt:skipATTR]) then
                   replace value of node $c/@mdrFlag with $fqt:SKIP
                 else
                   let $translatedAttrName := $mdrResult/translatedAttribute/text()
                   return (
                     replace value of node $c/fq:parameters/fq:parameter[1]
                        with fn:replace($sourceAttrText,$sourceAttrName,$translatedAttrName)
                     ,
                     replace value of node $c/@mdrFlag with $fqt:YES
                   )
               else (
                 replace value of node $c/@mdrFlag with $fqt:ERROR
                 (: DEBUG :)
                 (: replace value of node $c/fq:parameters/fq:parameter[1]
                       with concat('Debug_No_MDR_Translation_For=',$sourceAttrName) :)
               )

               , (: Process DTS Stuff :)
               (: Determine if we need to Call DTS to Translate Coded Value :)
               if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:translateCode]) then

                 (: Process One Parameter at a Time, there could be many since this is the IN Operator :)
                 (: The first parameter is the Attribute Name, the rest are Attribute Values :)
                 for $parm in $c/fq:parameters/fq:parameter[position()>1]

                   (:return

                     if ($index>1) then

                       let $dtsSrcPropVal := $c/fq:parameters/fq:parameter[$index]/text():)

                       (: Call DTS :)
                       let $dtsResponse := further:getTranslatedConcept($srcNamespaceId,
                                                                        $fqt:dtsSrcPropNm,
                                                                        $parm,
                                                                        $tgNamespaceId,
                                                                        $fqt:dtsTgPropName)
                       (: Debug DTS URL :)
                       (: let $dtsURL := further:getConceptTranslationRestUrl($srcNamespaceId,
                                                                           $fqt:dtsSrcPropNm,
                                                                           $parm,
                                                                           $tgNamespaceId,
                                                                           $fqt:dtsTgPropName) :)

                       let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

                       return
                         if ($translatedPropVal) then (
                           replace value of node $parm with $translatedPropVal
                           ,
                           replace value of node $parm/@dtsFlag with $fqt:YES
                         )
                         else replace value of node $parm/@dtsFlag with $fqt:ERROR

               else if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:ageToBirthYear]) then

                 (: Get the Source Value to be Translated :)
                 (: Process One Parameter at a Time, there could be many since this is the IN Operator :)
                 for $param at $index in ($criteria/fq:parameters/*)

                   (: The first parameter is the Attribute Name, the rest are Attribute Values :)
                   return

                     if ($index>1) then
                       let $srcVal := $c/fq:parameters/fq:parameter[$index]/text()
                       return
                         replace value of node $c/fq:parameters/fq:parameter[$index]
                            with fqt:ageToBirthYear($srcVal)

                     else( (: End Inner IF :) )

               else( (: End Outter IF :) )

             , (: Determine if we need to Translate the DataType :)
             for $entry in $mdrResult//entry[key=$fqt:ATTR_VALUE_TRANS_TO_DATA_TYPE]
               let $newDataType := $entry/value/text()
               return
                 (: Process One Parameter at a Time, there could be many since this is the IN Operator :)
                 for $param at $index in ($criteria/fq:parameters/*)
                   (: The first parameter is the Attribute Name, the rest are Attribute Values :)
                   return
                     if ($index>1) then
                       replace value of node $c/fq:parameters/fq:parameter[$index]/@xsi:type with $newDataType
                     else( (: DO NOTHING :) )

             , (: Do More Stuff :)
             (: Always Set Translation Flag after Translation :)
             if ($mdrResult[translatedAttribute]) then
               replace value of node $c/@transFlag with $fqt:YES
             else replace value of node $c/@transFlag with $fqt:ERROR

             ) (: End Second Return :)

           ) (: End IN Return :)

         default return <error>Invalid searchType in transCriteria function</error>
         (: End Switch Case :)

(: End Modify :)
)

(: Return Single Modified Criteria Node :)
return $c
(: END XQUERY TRANSFORMATION Function :)
};

(:==================================================================:)
(: transAlias = Translate Alias Stuff for Query                     :)
(:==================================================================:)
declare function fqt:transAlias($inputXML as document-node(),
                                $targetNamespaceId as xs:string)
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Must Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Determin if Person translates to a Non-Person Table :)
  (: The Problem is that FURTHER Person fields do not have an Alias.
     So if it translates to a Non-Person Fields at the Target,
     We need to provide the target alias in the translated XML.
     In our XML, we need to get from fieldName to targetAlias.fieldName :)
  (: For example, FURTHER.PERSON.VITAL_STATUS trans to OMOPv2.OBSERVATION_PERIOD.PERSON_STATUS_CONCEPT_ID :)
  (: We're not using this field for now, but would be nice to have this functionality in place :)

  (: Translate Aliases :)
  (: Must Translated the entire group of <aliases> at one time
     Because the replace cannot update single <alias> node with multiple <alias> nodes
     It needs to be <aliases><alias></alias><alias></alias></aliases> :)

  (: The $index is used to differentiate each Alias Name :)
  for $alias at $index in $inputCopy//fq:alias

    (: Store Original Source Values :)
    let $associationObject := fn:string($alias/@associationObject)
    let $aliasKey := $alias/fq:key/text()
    let $aliasValue := $alias/fq:value/text()

    (: Get Translated Response from MDR for Target Namespace :)
    let $mdrResponse := fqt:getMDRAssoc($associationObject,$targetNamespaceId)

    (: Extract Out Translated Alias(es) :)
    let $translatedAlias := fqt:getAlias($mdrResponse,$index,$aliasKey,$targetNamespaceId)

    (: For Each Association :)
    (: Every Table Association Should have MDR PROPERTIES for ONE Key & Value Pair :)
    (: OLD CODE COMMENTED OUT
    let $mdrAssocObject := $mdrResponse/rightAsset/text()
    for $entry in $mdrResponse/properties/entry[key='ALIAS_KEY']
      let $mdrKey := <key>{concat( $entry/value/text(), fn:string($index) )}</key>
    for $entry in $mdrResponse/properties/entry[key='ALIAS_VALUE']
      let $mdrValue := <value>{$entry/value/text()}</value>
    :)

    (: EACH NODE ONLY BE REPLACED ONCE! does not work! :)
    return (

      (: Note :)
      (: Cannot use replace here since replace requires a valid xml document structure :)
      (: I just want to work with nodes here :)

      (: Remove Original Alias Node :)
      delete node $alias,

      (: Insert Translated Alias Node(s) for Each Original Alias Node :)
      (: Inserting into the Parent .. <aliases> node! :)
      insert node $translatedAlias into $alias/..

      (: Cannot use this since sometimes there are New Additional Nodes! :)
      (: There are one alias to many aliases relationships :)
      (:
      replace value of node $alias/@associationObject with $mdrAssocObject,
      replace value of node $alias/fq:key with $mdrKey,
      replace value of node $alias/fq:value with $mdrValue,
      :)

    ) (: End Return :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: transRoot = Translate rootObject                                 :)
(:==================================================================:)
declare function fqt:transRoot($inputXML as document-node(),
                               $targetNamespaceId as xs:string)
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Must Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Querys and SubQuerys have rootObjects :)
  (: Therefore, For Each Query that has an rootObject XML Attribute :)
  for $query in $inputCopy//fq:query[@rootObject]

    (: Get the Original rootObject Value :)
    let $rootObject := fn:string($query/@rootObject)

    (: Get Translated Response from MDR :)
    let $mdrResponse := fqt:getMDRAssoc($rootObject,$targetNamespaceId)

    return
      if ( $mdrResponse/assetAssociation[rightAsset] ) then

        (: DEBUG :)
        (: let $mdrRoot := concat('Translated_Root=',$rootObject) :)

        (: Strip out the Translated rootObject Value :)
        let $mdrRoot := $mdrResponse/assetAssociation/rightAsset/text()
        return replace value of node $query/@rootObject with $mdrRoot
    else ()

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: getMDRAssoc = Get MDR Association as a Response XML              :)
(:==================================================================:)
declare function fqt:getMDRAssoc($assocObject as xs:string, $tgNmspcId as xs:string)
{ (: BEGIN FUNCTION :)

<assetAssociationList>
{
  (: let $baseURL := 'http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/left/translatesTo/' :)
  let $baseURL := fn:concat($const:fmdrRestServer,'/rest/asset/association/left/translatesTo/')
  let $docUrl := fn:concat( $baseURL, $assocObject )

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

  (: Get the Namespace Name so we can use it to strip out the correct assetAssociation :)
  let $tgNmspcName := further:getNamespaceName($tgNmspcId)

  (: Return the proper association(s) for the target Namespace :)
  for $assetAssociation in $response/mdr:assetAssociationList/assetAssociation[rightNamespace=$tgNmspcName]
    return $assetAssociation
}
</assetAssociationList>

(: End of FUNCTION :)
};

(:==================================================================:)
(: Validate (Error Handling)                                        :)
(:==================================================================:)
declare function fqt:validate($inputXML as document-node(),$tgNmspcName as xs:string)
{
(: 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. :)

  (: Error Out ANY 'LIKE' <searchType> Since we are not supporting at this time :)
  if ($inputCopy//fq:searchType='LIKE') then (

    (: Wish i could just replace, but did not work :)
    (: So I'm just deleting the whole file and then inserting the Error :)
    delete node $inputCopy//fq:query,
    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $c in ($inputCopy//fq:criteria[fq:searchType='LIKE'])[1]
      let $attrName := fqt:getAttrNameFromCriteria($c)
      return
      insert node
        <error xmlns="http://further.utah.edu/core/ws">
          <code>QUERY_TRANSLATION_ERROR</code>
          <message>LIKE searchType is not supported at this time.</message>
        </error>
        into $inputCopy
  )

  (: Check for Non-Translated Criterias :)
  else if ($inputCopy//fq:criteria[@transFlag=$fqt:NO]) then (

    (: Wish i could just replace, but did not work :)
    (: So I'm just deleting the whole file and then inserting the Error :)
    delete node $inputCopy//fq:query,
    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $c in ($inputCopy//fq:criteria[@transFlag=$fqt:NO])[1]
      let $attrName := fqt:getAttrNameFromCriteria($c)
      return
      insert node
        <error xmlns="http://further.utah.edu/core/ws">
          <code>QUERY_TRANSLATION_ERROR</code>
          <message>Criteria [ {$tgNmspcName}.{$attrName} ] NOT Tranlated</message>
        </error>
        into $inputCopy
  )
  else if ($inputCopy//fq:criteria[@mdrFlag=$fqt:ERROR]) then (

    (: Wish i could just replace, but did not work :)
    (: So I'm just deleting the whole file and then inserting the Error :)
    delete node $inputCopy//fq:query,
    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $c in ($inputCopy//fq:criteria[@mdrFlag=$fqt:ERROR])[1]
      let $attrName := fqt:getAttrNameFromCriteria($c)
      return
      insert node
        <error xmlns="http://further.utah.edu/core/ws">
          <code>MDR_QUERY_TRANSLATION_ERROR</code>
          <message>MDR Association for [ {$tgNmspcName}.{$attrName} ] May be Missing</message>
        </error>
        into $inputCopy
  )
  else if ($inputCopy//fq:parameter[@dtsFlag=$fqt:ERROR]) then (

    (: Wish i could just replace, but did not work :)
    (: So I'm just deleting the whole file and then inserting the Error :)
    delete node $inputCopy//fq:query,
    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $c in ($inputCopy//fq:criteria[fq:parameters/fq:parameter/@dtsFlag=$fqt:ERROR])[1]
      let $attrName := fqt:getAttrNameFromCriteria($c)
      return
      insert node
        <error xmlns="http://further.utah.edu/core/ws">
          <code>DTS_QUERY_TRANSLATION_ERROR</code>
          <message>DTS Mapping for [ {$tgNmspcName}.{$attrName} ] May be Missing</message>
        </error>
        into $inputCopy
  )
  else(
    (: Remove ALL Criteria Nodes that have been SKIPPED :)
    (: This includes the criterias with 'NamespaceId' in it :)
    (: Exclude SubQuery's FIRST Criteria :)
    delete node $inputCopy//fq:criteria[(@transFlag=$fqt:SKIP or @mdrFlag=$fqt:SKIP) and not(fq:query)]
  ) (: End IF-Else Statement :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: Cleanup                                                          :)
(:==================================================================:)
declare function fqt:cleanup($inputXML as document-node())
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: If the validate step did not Error Out.
     Meaning the <query> is still there.
     Then Process Cleanup.
     Otherwise we do not need to do anything. :)
  if ($inputCopy[fq:query]) then (

    (: Remove all Aliases that are not being used :)
    (: Sometimes one Source Alias translates to multiple target Aliases :)
    (: However, not all translated Aliases may be needed :)
    for $alias in $inputCopy//fq:alias
      let $key := $alias/fq:key
      return
        if (not(fn:exists($inputCopy//fq:parameter[fn:tokenize(.,'\.')[1]=$key]))) then
          delete node $alias
        else ()

    , (: Do more stuff :)

    (: Rename the criteria with @rootCriterion back to <rootCriterion> :)
    for $rc in $inputCopy//fq:criteria[@rootCriterion=$fqt:YES]
      return (
        rename node $rc as QName("http://further.utah.edu/core/query", "rootCriterion"),
        (: Delete rootCriterion Attribute :)
        delete node $rc/@rootCriterion
      )

    ,(: Do more stuff :)

    (: Delete ALL tranFlag Attributes on SUCCESS :)
    delete node $inputCopy//@transFlag,
    delete node $inputCopy//@mdrFlag,
    delete node $inputCopy//@dtsFlag

    ,(: Do more stuff :)

    (: Delete ALL aliasKey Attributes on SUCCESS :)
    delete node $inputCopy//@aliasKey,
    delete node $inputCopy//@oldAliasKey

    ,(: Do more stuff :)

    (: Delete ALL obsType Attributes on SUCCESS :)
    delete node $inputCopy//@obsType

  )
  else( (: DO NOTHING :) )

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: getMDRResult = ALWAYS GET ONE MDR Attribute Translation RESULT   :)
(:==================================================================:)
declare function fqt:getMDRResult(
  $srcAttrName as xs:string,
  $srcNmspcId as xs:string,
  $tgNmspcId as xs:string,
  $criteriaType as xs:string?)
{
  (: EXAMPLE: ${server.mdr.ws}/mdr/rest/asset/association/unique/attribute/administrativeGender/FURTHeR/UUEDW :)
  (:          ${server.mdr.ws}/mdr/rest/asset/association/unique/attribute/{sourceAttr}/{sourceNamespace}/{targetNamespace} :)
  (:          http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/unique/attribute/administrativeGender/FURTHeR/UUEDW :)
  (: let $baseURL := 'http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/attribute/ :)
  let $baseURL := fn:concat($const:fmdrRestServer,'/rest/asset/association/attribute/')
  let $docUrl := fn:concat( $baseURL, $srcAttrName, '/', $srcNmspcId, '/', $tgNmspcId )

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

  (: if there is more than one result, get the correct one based on the criteriaType :)
  return
    if ( count($doc//attributeTranslationResult) > 1 ) then
      $doc/mdr:attributeTranslationResultList/attributeTranslationResult[properties/entry/value=$criteriaType]
    else
      $doc/mdr:attributeTranslationResultList/attributeTranslationResult
};

(:==================================================================:)
(: getMDRAttrURL = Get URL for Debugging                            :)
(:==================================================================:)
declare function fqt:getMDRAttrURL(
  $srcAttrName as xs:string,
  $srcNmspcId as xs:string,
  $tgNmspcId as xs:string)
{
  (: EXAMPLE: ${server.dts.ws}/mdr/rest/asset/association/unique/attribute/administrativeGender/FURTHeR/UUEDW :)
  (:          ${server.dts.ws}/mdr/rest/asset/association/unique/attribute/{sourceAttr}/{sourceNamespace}/{targetNamespace} :)
  (:          http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/unique/attribute/administrativeGender/FURTHeR/UUEDW :)
  (: let $baseURL := 'http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/attribute/':)
  let $baseURL := fn:concat($const:fmdrRestServer,'/rest/asset/association/attribute/')
  let $docUrl := fn:concat( $baseURL, $srcAttrName, '/', $srcNmspcId, '/', $tgNmspcId )

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

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

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

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

(:==================================================================:)
(: insertObsType = Set ObservationType Flag for later Processing    :)
(:==================================================================:)
declare function fqt:insertObsType($inputXML as document-node())
{
copy $inputCopy := $inputXML
modify (

  (: Loop through each Phrase :)
  for $criteriaPhrase in $inputCopy//*[fq:searchType/text()='CONJUNCTION'
                                      and fq:criteria[fq:searchType/text()='SIMPLE'
                                      and fn:contains(fq:parameters/fq:parameter[2]/text(),'observationNamespaceId')]]

    (: Get the observationType Value :)
    for $criteria in $criteriaPhrase/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),'observationType')]
      let $criteriaType := $criteria/fq:parameters/fq:parameter[3]/text()

      (: Set ObservationType For Each 'observation' Related Parameters
         Including SIMPLE & IN searchTypes and observationType
         Since sometimes we do translate the observationType values :)
      (:for $parm in $criteriaPhrase/fq:criteria/fq:parameters/fq:parameter[fn:starts-with(fn:tokenize(.,'\.')[last()],'observation')]
        return insert node attribute obsType {$criteriaType} into $parm :)

      (: A better way may be, for every parameter that has an aliasKey attribute :)
      (: Because for Labs, there are parameters with non 'observation' words in it :)
      for $parm in $criteriaPhrase/fq:criteria/fq:parameters/fq:parameter[@aliasKey]
        return insert node attribute obsType {$criteriaType} into $parm

) (: End Modify :)
return $inputCopy
};

(:==================================================================:)
(: getAlias = Return Alias Node(s)                                  :)
(:==================================================================:)
declare function fqt:getAlias($mdrResponse,$outerIndex,$oldAliasKey,$tgNmspcId)
{
  (: Get the Namespace Name so we can use it to strip out the correct assetAssociation :)
  let $tgNmspcName := further:getNamespaceName($tgNmspcId)

  (: There can be more than one Object Assocations :)
  for $assoc at $innerIndex in $mdrResponse/assetAssociation[rightNamespace=$tgNmspcName]

    (: Create composite Index to Ensure it is Unique Since sometimes there could be multiple assocations :)
    let $compositeIndex := concat($outerIndex,$innerIndex)

    (: Each Table Association MUST have MDR PROPERTIES for ONE Key & Value Pair :)
    let $mdrAssocObject := $assoc/rightAsset/text()
    for $entry in $assoc/properties/entry[key='ALIAS_KEY']
      let $mdrKey := <key>{concat( $entry/value/text(), $compositeIndex )}</key>
    for $entry in $assoc/properties/entry[key='ALIAS_VALUE']
      let $mdrValue := <value>{$entry/value/text()}</value>

  (: Return Alias Node(s) :)
  return
  if ($assoc/properties/entry[starts-with(key,'OBSERVATION_TYPE')]) then
    (: Creates Multiple <alias> nodes if there are more than one OBSERVATION_TYPE entry :)
    for $entry in $assoc/properties/entry[starts-with(key,'OBSERVATION_TYPE')]
    let $obsType := $entry/value/text()
    return <alias oldAliasKey='{$oldAliasKey}' obsType='{$obsType}' associationObject='{$mdrAssocObject}'>{$mdrKey}{$mdrValue}</alias>
  else
    <alias oldAliasKey='{$oldAliasKey}' associationObject='{$mdrAssocObject}'>{$mdrKey}{$mdrValue}</alias>

};

(:==================================================================:)
(: updateParmAlias = Update Alias in Parameters                     :)
(:==================================================================:)
declare function fqt:updateParmAlias($inputXML as document-node())
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Must Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: For Each Alias :)
  for $alias in $inputCopy//fq:alias[@oldAliasKey]

    (: Get the Old and New AliasKeys :)
    let $oldAliasKey := $alias/@oldAliasKey
    let $newAliasKey := $alias/fq:key/text()
    let $obsType := $alias/@obsType

    (: Replace Each Alias Attribute with the new Translated Alias Name :)
    (: Using the XQuery replace (case sensitive by default) function :)
    (: The middle parameter can be a Regular Expression, but we're currently using it as a string :)
    (: Appending a period at the end of the alias
       To ensure that we are replacing the Table Alias and NOT the Table Attribute :)
    (: For example, if we had observation.observation in the parameter,
       Only the "observation." (with the period) will be replaced :)
    (: Determine which Association to use if more than one :)

    return
      if ($alias/.[@obsType]) then
        (: For Each Parameter with Observation Type, Update Alias Prefix :)
        for $parm at $index in $inputCopy//fq:parameter[@aliasKey=$oldAliasKey and @obsType=$obsType]
        return (
          replace value of node $parm with
          fn:replace($parm/text(), concat($oldAliasKey,'.'), concat($newAliasKey,'.') )

        )
      else
        (: For Each Parameter Non-Observation Types, Update Alias Prefix :)
        for $parm at $index in $inputCopy//fq:parameter[@aliasKey=$oldAliasKey]
        return (
          replace value of node $parm with
          fn:replace($parm/text(), concat($oldAliasKey,'.'), concat($newAliasKey,'.') )

        )

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: dedupAliases Remove Duplicate Aliases                            :)
(: Duplicate Aliases occur when a target table is overloaded        :)
(: Meaning One Source Table translates to Multiple Target Tables    :)
(: Intended to implement in cleanup function.
   However it conflicts with the Update Pending List
   Therefore, needed to implement its own function                  :)
(:==================================================================:)

declare function fqt:dedupAliases($inputXML as document-node())
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Remove Duplicate Alias Nodes within the SAME <aliases> Node :)
  for $aliases in $inputCopy//fq:aliases

    (: This is some Tricky Stuff Right Here! :)
    (: Select only one of its occurences,
       Such that its index in all Aliases is the same as the first index of its key in all keys. :)
    let $uniqueAliases := $aliases//fq:alias [index-of($aliases//fq:key, fq:key ) [1] ]
    return (
      delete node $aliases/fq:alias,
      insert node $uniqueAliases into $aliases
    )

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: Translate Criteria Phrase                                        :)
(:==================================================================:)
declare function fqt:transCriteriaPhrase($inputXML as document-node(),
                                         $targetNamespaceId as xs:string){
(: BEGIN XQUERY TRANSFORMATION :)
(: Must Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Loop through each Phrase :)
  for $criteriaPhrase in $inputCopy//*[fq:searchType/text()='CONJUNCTION'
                                      and fq:criteria[fq:searchType/text()='SIMPLE'
                                      and fn:contains(fq:parameters/fq:parameter[2]/text(),'NamespaceId')]]

    (: Find each Namespace Group of Criterias, there could be more than one. :)
    for $criteriaNamespace in $criteriaPhrase/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),'NamespaceId')]

      (: Get Source Namespace ID for this Group :)
      let $sourceNamespaceId := $criteriaNamespace/fq:parameters/fq:parameter[3]/text()
      (: Ensure that Java Fields follow consistent naming convention :)
      (: For example, field and fieldNamespaceId :)
      let $namespaceText := $criteriaNamespace/fq:parameters/fq:parameter[2]/text()
      let $namespaceField := substring-before(fn:tokenize($namespaceText,'\.')[last()],'NamespaceId')

      (: Get the Criteria Type if there is one :)
      let $criteriaType :=
        if ( $criteriaPhrase/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),concat($namespaceField,'Type'))] ) then
          for $criteria in $criteriaPhrase/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),concat($namespaceField,'Type'))]
          let $ctName := $criteria/fq:parameters/fq:parameter[2]/text()
          let $ctValue := $criteria/fq:parameters/fq:parameter[3]/text()
          return ($ctName,$ctValue)
        else
          $fqt:EMPTY

      (: Set the Namespace for the Type Code, Since the Type uses a separate NamespaceID :)
      let $criteriaTypeNmspcId :=
        if (fn:tokenize($criteriaType[1],'\.')[last()] = 'observationType')
        then $fqt:SNOMED
        else $fqt:EMPTY

      (: Note there needs to be a matching "name" from "name"NamespaceId above :)
      (:let $criteria := $criteriaPhrase/fq:criteria[fn:tokenize(fq:parameters/fq:parameter[2]/text(),'\.')[last()]=$namespaceField]:)
      return (

        (: Mark NamespaceId Criterias to be Skipped Since Target does not need them :)
        replace value of node $criteriaNamespace/@transFlag with $fqt:SKIP

        , (: Process Field Criteria for Namespace :)
        (: There may potentially be more than one observation within the SAME Phrase in the future :)
        (: SIMPLE searchType has the fieldName in parameter[2]
           Other searchType (such as 'IN') has the fieldName in parameter[1] :)
        for $criteria in $criteriaPhrase/fq:criteria[fn:tokenize(fq:parameters/fq:parameter[1]/text(),'\.')[last()]=$namespaceField
                                                     or
                                                     fn:tokenize(fq:parameters/fq:parameter[2]/text(),'\.')[last()]=$namespaceField]
        return replace node $criteria
           with fqt:transCriteria($criteria,$sourceNamespaceId,$targetNamespaceId,$criteriaType[2],$criteriaTypeNmspcId)

        , (: See if there is a Type such as observationType :)
        (: The Type only needs to be processed ONCE within one Phrase :)
        if ($criteriaPhrase/fq:criteria
          [ fn:tokenize(fq:parameters/fq:parameter[2]/text(),'\.')[last()]=concat($namespaceField,'Type')])
        then
          let $ct := $criteriaPhrase/fq:criteria
          [ fn:tokenize(fq:parameters/fq:parameter[2]/text(),'\.')[last()]=concat($namespaceField,'Type')]
          return
          replace node $ct
           with fqt:transCriteria($ct,$sourceNamespaceId,$targetNamespaceId,$criteriaType[2],$criteriaTypeNmspcId)
        else()

        (: We can potentially Process the left over Single Criterias Here.
           However, I've decided to handle it in the transSingleCriteria function :)

      ) (: End Return :)

) (: End Modify :)
return $inputCopy
(: END XQUERY TRANSFORMATION :)
};

(:==================================================================:)
(: preTransOMOP                                                     :)
(: Pre-Translate ICD-9 Codes into SNOMED for OMOP Target ONLY!      :)
(: Since DTS Maps from ICD-9 to SNOMED to OMOP                      :)
(:==================================================================:)
declare function fqt:preTransOMOP($inputXML as document-node(),
                                  $targetNamespaceId as xs:string)
{
copy $inputCopy := $inputXML
modify (

  (: For OMOP Targets ONLY! :)
  if ($targetNamespaceId = $fqt:OMOP-V2) then

    (: Find Each ICD-9 Value :)
    for $criteriaGroup in $inputCopy//*[fq:searchType/text()='CONJUNCTION'
                                        and fq:criteria[fq:searchType/text()='SIMPLE'
                                        and fn:contains(fq:parameters/fq:parameter[2]/text(),'observationNamespaceId')
                                        and fq:parameters/fq:parameter[3]/text()=$fqt:ICD-9]]

      return (

      (: Replace NamespaceId to SNOMED :)
      for $criteria in $criteriaGroup/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),'observationNamespaceId')]
        return replace value of node $criteria/fq:parameters/fq:parameter[3] with $fqt:SNOMED

      , (: Do more Stuff :)

      (: Note that ICD-9 Codes ONLY Occur in 'SIMPLE', or 'IN' searchTypes :)

      (: Strip out the observation value for SIMPLE searchType :)
      for $criteria in $criteriaGroup/fq:criteria[fn:tokenize(fq:parameters/fq:parameter[2],'\.')[last()] = 'observation'
                                                  and fq:searchType='SIMPLE']

        let $dtsSrcPropVal := $criteria/fq:parameters/fq:parameter[3]

        (: Call DTS :)
        let $dtsResponse := further:getTranslatedConcept($fqt:ICD-9,
                                                         $fqt:dtsSrcPropNm,
                                                         $dtsSrcPropVal,
                                                         $fqt:SNOMED,
                                                         $fqt:dtsSrcPropNm)

        (: Debug DTS URL :)
        (:let $dtsURL := further:getConceptTranslationRestUrl($fqt:ICD-9,
                                                            $fqt:dtsSrcPropNm,
                                                            $dtsSrcPropVal,
                                                            $fqt:SNOMED,
                                                            $fqt:dtsSrcPropNm):)

        let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

        (: if there is a response :)
        return
        if ($translatedPropVal) then
          replace value of node $criteria/fq:parameters/fq:parameter[3] with $translatedPropVal
        else
          replace value of node $criteria/fq:parameters/fq:parameter[3] with $fqt:ZERO

      , (: Do more Stuff :)

      (: Strip out the observation value for IN searchType :)
      for $criteria in $criteriaGroup/fq:criteria[fn:tokenize(fq:parameters/fq:parameter[1],'\.')[last()] = 'observation'
                                                  and fq:searchType='IN']

        (: For Parameters greater than the first position :)
        for $parm in $criteria/fq:parameters/fq:parameter[position()>1]

          (: Call DTS :)
          let $dtsResponse := further:getTranslatedConcept($fqt:ICD-9,
                                                           $fqt:dtsSrcPropNm,
                                                           $parm,
                                                           $fqt:SNOMED,
                                                           $fqt:dtsSrcPropNm)

          (: Debug DTS URL :)
          (:let $dtsURL := further:getConceptTranslationRestUrl($fqt:ICD-9,
                                                              $fqt:dtsSrcPropNm,
                                                              $parm,
                                                              $fqt:SNOMED,
                                                              $fqt:dtsSrcPropNm):)

          let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

          (: if there is a response :)
          return
          if ($translatedPropVal) then
            replace value of node $parm with $translatedPropVal
          else
            replace value of node $parm with $fqt:ZERO

    ) (: End Return :)

  else( (: DO NOTHING if Target is NOT OMOP :) )

) (: End Modify :)
return $inputCopy
};

(:=====================================================================:)
(: Sanitize SINGLE & DOUBLE Quotes to Prevent XQuery Injection Attacks :)
(:=====================================================================:)
declare function fqt: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 fqt:sanitize :)

(:=====================================================================:)
(: getAttrNameFromCriteria                                             :)
(: Get the Attribute Name from a Single Criteria                       :)
(:=====================================================================:)
declare function fqt:getAttrNameFromCriteria($criteria as element())
as xs:string
{
  (: For SIMPLE searchType, it is always in the Second <parameter>
     Otherise, it is always in the First <parameter> :)
  let $attrName :=
    if ($criteria/fq:searchType = $fqt:SIMPLE) then
      $criteria/fq:parameters/fq:parameter[2]/text()
    else
      $criteria/fq:parameters/fq:parameter[1]/text()

  return $attrName

}; (: END OF FUNCTION fqt:getAttrNameFromCriteria :)

(: 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 fqt = "http://further.utah.edu/query-translation";

(: Import FURTHeR Module :)
(: Change the location of further.xq when working locally :)
(: import module namespace further = "http://further.utah.edu/xquery-functions-module"
    at "../fqe/further/xq/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 :)
(: Change the location of constants.xq when working locally :)
(: import module namespace const = 'http://further.utah.edu/constants'
    at '../fqe/further/xq/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";
(: DO NOT USE DEFAULT NAMESPACE, it may conflict with the response Namespaces :)

(: Global CONSTANTS, Always Translating FROM FURTHeR Namespace ID :)
(: declare variable $fqt:FURTHER := 'FURTHeR'; :)
declare variable $fqt:FURTHER as xs:string := '32769';
(: We are using SNOMED (Namespace ID 30) for ObservationType Values :)
declare variable $fqt:SNOMED as xs:string := '30';

(: OMOP has a special case where ICD-9 needs to be translated to SNOMED first,
   before translating to OMOP-V2
   SNOMED uses the Standard 'Code in Source' Property Name :)
declare variable $fqt:OMOP-V2 as xs:string := '32868';
declare variable $fqt:ICD-9 as xs:string := '10';
declare variable $fqt:LOINC as xs:string := '5102';

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

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

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

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

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

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

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

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

(: DTS Static Property Names CASE SENSITIVE! :)
declare variable $fqt:dtsSrcPropNm as xs:string := 'Code in Source';
declare variable $fqt:dtsTgPropName as xs:string := 'Local Code';

(: Criteria searchType :)
declare variable $fqt:SIMPLE as xs:string := 'SIMPLE';
declare variable $fqt:BETWEEN as xs:string := 'BETWEEN';
declare variable $fqt:LIKE as xs:string := 'LIKE';
declare variable $fqt:IN as xs:string := 'IN';

(:==================================================================:)
(: Main Translate Query Function                                    :)
(:==================================================================:)
declare function fqt:transQuery($inputXML as document-node(),$targetNamespaceId as xs:string)
{
  (: Get the Namespace Name Using the Namespace ID :)
  let $tgNmspcName := further:getNamespaceName($targetNamespaceId)

  (: Must Keep this Order of Processing :)

  (: Call Initialization :)
  let $initializedInput := fqt:initQueryTranslation($inputXML)

  (: Call insertObsType :)
  let $obsTypeInput := fqt:insertObsType($initializedInput)

  (: Call preTransOMOP :)
  let $preTranslatedOMOP := fqt:preTransOMOP($obsTypeInput,$targetNamespaceId)

  (: Call transCriteriaPhrase :)
  let $translatedCriteriaPhrase := fqt:transCriteriaPhrase($preTranslatedOMOP,$targetNamespaceId)

  (: Call transSingleCriteria :)
  let $translatedSingleCriteria := fqt:transSingleCriteria($translatedCriteriaPhrase,$targetNamespaceId)

  (: Call transAlias :)
  let $translatedAlias := fqt:transAlias($translatedSingleCriteria,$targetNamespaceId)

  (: Call updatedParmAlias :)
  let $updatedParmAlias := fqt:updateParmAlias($translatedAlias)

  (: Call transRoot :)
  let $translatedRoot := fqt:transRoot($updatedParmAlias,$targetNamespaceId)

  (: Call dedupAliases :)
  let $dedupedAliases := fqt:dedupAliases($translatedRoot)

  (: Call validate for Errors :)
  let $validated := fqt:validate($dedupedAliases,$tgNmspcName)

  (: Call cleanup :)
  let $cleaned := fqt:cleanup($validated)

  (: Return Final Cleaned Version :)
  return $cleaned

};

(:==================================================================:)
(: Initialization = Mark All Criterias as NOT Translated            :)
(:==================================================================:)
declare function fqt:initQueryTranslation($inputXML as document-node())
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy1 := $inputXML
modify (

  (: For rootCriterion nodes that needs to be Translated
     Insert Attribute and Rename them as a <criteria> node
     The rename has to be done in a separate transformation,
     since we cannot change the same node in one xquery transformation :)
  for $rootCriterion in $inputCopy1//fq:rootCriterion[fq:parameters]
    return insert node attribute rootCriterion {$fqt:YES} into $rootCriterion

) (: End Modify :)
return
copy $inputCopy2 := $inputCopy1
modify (

  (: Rename <rootCriterion> to <criteria> if it needs processing :)
  for $rootCriterion in $inputCopy2//fq:rootCriterion[fq:parameters]
    return
      rename node $rootCriterion as QName("http://further.utah.edu/core/query", "criteria")

) (: End Modify :)
return
copy $inputCopy3 := $inputCopy2
modify (

  (: Insert Flags for each Criteria that needs Tranlation :)
  for $criteria in $inputCopy3//fq:criteria[fq:parameters]
    return (
      insert node attribute transFlag {$fqt:NO} into $criteria,
      insert node attribute mdrFlag {$fqt:NO} into $criteria
    )
  ,
  (: dtsFlag needs to be at the parameter level since the IN searchType has many parameters :)
  for $parm in $inputCopy3//fq:parameter
    return insert node attribute dtsFlag {$fqt:NO} into $parm
  ,
  (: Insert Alias Key as Parameter Attribute if Parameter Starts With Alias :)
  (: Every Alias Key should be Unique within the Entire Input XML, including SubQuery :)
  for $alias in $inputCopy3//fq:alias
    let $aliasKey := $alias/fq:key/text()
    for $parm in $inputCopy3//fq:parameter
      let $parmText := $parm/text()
      return
        if (starts-with($parmText,concat($aliasKey,'.')))then
          insert node attribute aliasKey {$aliasKey} into $parm
        else()

) (: End Modify :)
return $inputCopy3

(: END Function :)
};

(:==================================================================:)
(: Translate Single Criteria                                        :)
(:==================================================================:)
declare function fqt:transSingleCriteria($inputXML as document-node(),
                                         $targetNamespaceId as xs:string)
{ (: BEGIN FUNCTION :)

  (: DO NOT REMOVE THESE COMMENTS HERE! :)
  (: There are 3 things we need to do here :)
  (: Process Singles at the Most Outer Level :)
  (: Process Singles at SubQuery Outer Level :)
  (: Process Singles Inside Each SubQuery :)

  (: We can do the above 3 things in 2 Transformation steps :)
  (: We need to separate the Transformations because the Pending Update List will Conflict with each other :)

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

  (: IMPORTANT!!! Use this FLWOR to DEBUG the Single Criteria :)
  (: The Single Criteria may contain an ENTIRE SubQuery! :)
  (: for $criteria in $inputCopy//fq:criteria[@transFlag = "N"] :)
  (:  return :)
  (:  replace node $criteria with <MO_DEBUG_SINGLE_CRITERIA>{$criteria}</MO_DEBUG_SINGLE_CRITERIA> :)

  (: For Criterias that are NOT SubQuery Outer Criteria :)
  (: Meaning all the Atomic (No Children) Criterias :)
  for $atomicCriteria in $inputCopy//fq:criteria[@transFlag=$fqt:NO and not(fq:query)]
    (: DEBUG :)
    (: return replace value of node $atomicCriteria/@transFlag with 'MO' :)
    return
      replace node $atomicCriteria with
      fqt:transCriteria($atomicCriteria,$fqt:FURTHER,$targetNamespaceId,$fqt:EMPTY,$fqt:EMPTY)

)
(: Return to a Second Transformation :)

return
(: BEGIN Second XQUERY TRANSFORMATION :)
(: Make a copy of the First Document :)
copy $inputCopy2 := $inputCopy
modify (

  (: IMPORTANT!!! Use this FLWOR to DEBUG the Single Criteria :)
  (: The Single Criteria may contain an ENTIRE SubQuery! :)
  (: for $criteria in $inputCopy2//fq:criteria[@transFlag = "N"] :)
  (:  return :)
  (:  replace node $criteria with <MO_DEBUG_SINGLE_CRITERIA>{$criteria}</MO_DEBUG_SINGLE_CRITERIA> :)

  (: Process the Outer Criteria for each SubQuery :)
  for $subQueryCriteria in $inputCopy2//fq:criteria[@transFlag=$fqt:NO and fq:query]
    (: DEBUG :)
    (: return replace value of node $subQueryCriteria/@transFlag with 'MO' :)
    return
      replace node $subQueryCriteria with
      fqt:transCriteria($subQueryCriteria,$fqt:FURTHER,$targetNamespaceId,$fqt:EMPTY,$fqt:EMPTY)

)
return $inputCopy2

(: END XQUERY TRANSFORMATION :)
};

(:==================================================================:)
(: Translate Criteria Details                                       :)
(: criteriaType & criteriaTypeNmspcId are Optional (Can be Blank)   :)
(:==================================================================:)
declare function fqt:transCriteria($criteria as node(),
                                   $srcNamespaceId as xs:string,
                                   $tgNamespaceId as xs:string,
                                   $criteriaType as xs:string?,
                                   $criteriaTypeNmspcId as xs:string?)
{ (: BEGIN FUNCTION :)

copy $c := $criteria
modify (

  (: Get Search Type for Current Criteria Node :)
  let $searchType := upper-case($c/fq:searchType)

  (: Switch Case to Select which Parameters to Process Based on Search Type :)
  (: Switch Requires XQuery 1.1 or Above, but is easier to read and more scalable :)
  return switch ($searchType)

         case 'SIMPLE'
           (: Parameter[2]=Attribute Name, 3 = Attribute Value :)
           (: return ($criteria/parameters/parameter[2],$criteria/parameters/parameter[3]) :)
           return (

             (: Get the Source Attribute Name :)
             let $sourceAttrText := $c/fq:parameters/fq:parameter[2]/text()
             let $sourceAttrName :=
               if ($c/fq:parameters/fq:parameter[2 and @aliasKey]) then
                 let $aliasKey := fn:string($c/fq:parameters/fq:parameter[2]/@aliasKey)
                 return substring-after($sourceAttrText, concat($aliasKey,'.') )
               else
                 $sourceAttrText

             (: Strip out the Last Token in the Attribute Text :)
             (: tokenize will return the entire string if separator is not found in string,
             whereas string-after or string-after-last will return empty string.
             Therefore, use tokenize and get the last token. Cool! :)
             (: Data Format = RootEntity.TableName.AttributeName :)
             (: The sourceAttrName needs to be set here so i can reference it in the return fn:replace :)
             (: let $sourceAttrName := fn:tokenize($sourceAttrText,'\.')[last()] :)
             (: let $mdrResponse := fqt:getMDRAttrURL($sourceAttrName,$fqt:FURTHER,$tgNamespaceId) :)

             (: There should ONLY be ONE Result per source Attribute :)
             (: if there is more than one result, the criteriaType will determine which result is appropriate :)
             let $mdrResult := fqt:getMDRResult($sourceAttrName,$fqt:FURTHER,$tgNamespaceId,$criteriaType)

             (: DEBUG :)
             (: let $mdrURL := fqt:getMDRAttrURL($sourceAttrName,$fqt:FURTHER,$tgNamespaceId) :)
             (: let $mdrTranslatedAttrName := 'Translated MDR Parm2 Name' :)

             return (

               (: DEBUG
               replace value of node $c/fq:parameters/fq:parameter[2]
                  with $mdrURL :)

               (: if there is a Result, ALWAYS Set translatedAttrName (Except for SKIP) :)
               (: The datatype for attribute name is always String,
                  so there is no need for that Translation :)

               if ($mdrResult/.[translatedAttribute]) then

                 (: if need to skip, update mdrFlag as fqt:SKIP :)
                 if ($mdrResult/properties/entry[key=$fqt:ATTR_TRANS_FUNC and value=$fqt:skipATTR]) then
                   replace value of node $c/@mdrFlag with $fqt:SKIP
                 else
                   let $translatedAttrName := $mdrResult/translatedAttribute/text()
                   return (
                     replace value of node $c/fq:parameters/fq:parameter[2]
                        with fn:replace($sourceAttrText,$sourceAttrName,$translatedAttrName)
                        (: with $mdrURL :)
                     ,
                     replace value of node $c/@mdrFlag with $fqt:YES
                   )
               else (
                 replace value of node $c/@mdrFlag with $fqt:ERROR
                 (: DEBUG :)
                 (: replace value of node $c/fq:parameters/fq:parameter[2]
                    with concat('Debug_No_MDR_Translation_For=',$sourceAttrName) :)
               )

               , (: Process Code Translation Next :)

               (: Determine if we need to Call DTS to Translate Coded Value :)
               if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:translateCode]) then

                 let $dtsSrcPropVal := $c/fq:parameters/fq:parameter[3]/text()

                 (: Determine the Source Namespace ID since one is for the code, and the other is for the type :)
                 (: This ONLY Happens to SIMPLE, Since that is where the Phrases Occur :)
                 let $srcNamespaceId :=
                   if ($sourceAttrName = 'observationType') then
                     $criteriaTypeNmspcId
                   else
                     $srcNamespaceId

                 (: Call DTS :)
                 let $dtsResponse := further:getTranslatedConcept($srcNamespaceId,
                                                                  $fqt:dtsSrcPropNm,
                                                                  $dtsSrcPropVal,
                                                                  $tgNamespaceId,
                                                                  $fqt:dtsTgPropName)
                 (: DEBUG DTS URL :)
                 (: let $dtsURL := further:getConceptTranslationRestUrl($srcNamespaceId,
                                                                     $fqt:dtsSrcPropNm,
                                                                     $dtsSrcPropVal,
                                                                     $tgNamespaceId,
                                                                     $fqt:dtsTgPropName) :)

                 (: let $debug := concat($srcNamespaceId,'|',
                                      $fqt:dtsSrcPropNm,'|',
                                      $dtsSrcPropVal,'|',
                                      $tgNamespaceId,'|',
                                      $fqt:dtsTgPropName) :)

                 (: let $translatedPropVal := $dtsResponse/dts:concepts/dts:conceptId/propertyValue/text() :)
                 let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

                 return
                   if ($translatedPropVal) then (
                     replace value of node $c/fq:parameters/fq:parameter[3]
                        with $translatedPropVal
                     ,
                     replace value of node $c/fq:parameters/fq:parameter[3]/@dtsFlag with $fqt:YES
                   )
                   else
                     (: Always return Error if there is no DTS Mapping :)
                     replace value of node $c/fq:parameters/fq:parameter[3]/@dtsFlag with $fqt:ERROR

               else if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:ageToBirthYear]) then

                 (: Get the Source Value to be Translated :)
                 let $srcVal := $c/fq:parameters/fq:parameter[3]/text()
                 return
                   replace value of node $c/fq:parameters/fq:parameter[3]
                      with fqt:ageToBirthYear($srcVal)
               else()

               , (: Determine if we need to Translate the DataType :)
               (: WHY IS THERE NO ATTR_VALUE_TRANS_TO_DATA_TYPE in the MDR for the Attributes that NEEDS it! :)
               (: Try ask Rick, maybe it is currently hard coded :)
               for $entry in $mdrResult//entry[key=$fqt:ATTR_VALUE_TRANS_TO_DATA_TYPE]
                 let $newDataType := $entry/value/text()
                 return replace value of node $c/fq:parameters/fq:parameter[3]/@xsi:type with $newDataType

               , (: Do More Stuff :)
               (: Always Set Translation Flag after Translation :)
               if ($mdrResult[translatedAttribute]) then
                 replace value of node $c/@transFlag with $fqt:YES
               else replace value of node $c/@transFlag with $fqt:ERROR

             )

           ) (: END SIMPLE Return :)

         case 'BETWEEN'
           (: Parameter[1]=Attribute Name, 2 & 3 = Attribute Value :)
           return (

             (: Get the Source Attribute Name :)
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName :=
               if ($c/fq:parameters/fq:parameter[1 and @aliasKey]) then
                 let $aliasKey := fn:string($c/fq:parameters/fq:parameter[1]/@aliasKey)
                 return substring-after($sourceAttrText, concat($aliasKey,'.') )
               else
                 $sourceAttrText

             (: Call MDR Web Service to Translate Attribute Name HERE :)
             let $mdrResult := fqt:getMDRResult($sourceAttrName,$fqt:FURTHER,$tgNamespaceId,$criteriaType)
             (: let $mdrURL := fqt:getMDRAttrURL($sourceAttrName,$fqt:FURTHER,$tgNamespaceId) :)
             (: let $translatedAttrName := 'Translated MDR Name' :)

             return (
               (: if there is a Result, ALWAYS Set translatedAttrName :)
               (: The datatype for attribute name is always String,
                  so there is no need for that Translation :)
               if ($mdrResult/.[translatedAttribute]) then

                 (: if need to skip, update mdrFlag as fqt:SKIP :)
                 if ($mdrResult/properties/entry[key=$fqt:ATTR_TRANS_FUNC and value=$fqt:skipATTR]) then
                   replace value of node $c/@mdrFlag with $fqt:SKIP
                 else
                   let $translatedAttrName := $mdrResult/translatedAttribute/text()
                   return (
                     replace value of node $c/fq:parameters/fq:parameter[1]
                        with fn:replace($sourceAttrText,$sourceAttrName,$translatedAttrName)
                     ,
                     replace value of node $c/@mdrFlag with $fqt:YES
                   )
               else (
                 replace value of node $c/@mdrFlag with $fqt:ERROR
                 (: DEBUG :)
                 (: replace value of node $c/fq:parameters/fq:parameter[1]
                    with concat('Debug_No_MDR_Translation_For=',$sourceAttrName) :)
               )

               , (: Process Code Translation Next Parameters 2 & 3 :)

               (: Determine if we need to Call DTS to Translate Coded Value :)
               if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:translateCode]) then

                 let $dtsSrcPropVal2 := $c/fq:parameters/fq:parameter[2]/text()
                 let $dtsSrcPropVal3 := $c/fq:parameters/fq:parameter[3]/text()

                 (: Call DTS for Parameter 2 :)
                 let $dtsResponse2 := further:getTranslatedConcept($srcNamespaceId,
                                                                   $fqt:dtsSrcPropNm,
                                                                   $dtsSrcPropVal2,
                                                                   $tgNamespaceId,
                                                                   $fqt:dtsTgPropName)

                 (: Call DTS for Parameter 3 :)
                 let $dtsResponse3 := further:getTranslatedConcept($srcNamespaceId,
                                                                   $fqt:dtsSrcPropNm,
                                                                   $dtsSrcPropVal3,
                                                                   $tgNamespaceId,
                                                                   $fqt:dtsTgPropName)
                 (: Debug DTS URL :)
                 (: let $dtsURL := further:getConceptTranslationRestUrl($srcNamespaceId,
                                                                     $fqt:dtsSrcPropNm,
                                                                     $dtsSrcPropVal2,
                                                                     $tgNamespaceId,
                                                                     $fqt:dtsTgPropName) :)

                 (: let $debug := concat($srcNamespaceId,'|',
                                      $fqt:dtsSrcPropNm,'|',
                                      $dtsSrcPropVal2,'|',
                                      $tgNamespaceId,'|',
                                      $fqt:dtsTgPropName) :)

                 let $translatedPropVal2 := further:getConceptPropertyValue($dtsResponse2)
                 let $translatedPropVal3 := further:getConceptPropertyValue($dtsResponse3)

                 return (

                   if ($translatedPropVal2) then (
                     replace value of node $c/fq:parameters/fq:parameter[2]
                        with $translatedPropVal2,
                     replace value of node $c/fq:parameters/fq:parameter[2]/@dtsFlag with $fqt:YES
                   )
                   else (: ERROR :)
                     replace value of node $c/fq:parameters/fq:parameter[2]/@dtsFlag with $fqt:ERROR
                   ,

                   if ($translatedPropVal3) then (
                     replace value of node $c/fq:parameters/fq:parameter[3]
                        with $translatedPropVal3,
                     replace value of node $c/fq:parameters/fq:parameter[3]/@dtsFlag with $fqt:YES
                   )
                   else (: ERROR :)
                     replace value of node $c/fq:parameters/fq:parameter[3]/@dtsFlag with $fqt:ERROR

                 ) (: End Replace Return :)

               else if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:ageToBirthYear]) then

                 (: Get the Source Value to be Translated :)
                 let $srcValue2 := $c/fq:parameters/fq:parameter[2]/text()
                 let $srcValue3 := $c/fq:parameters/fq:parameter[3]/text()

                 (: Reverse the Order of 2 & 3 for this Special Case Since the Smaller BirthYear Must come First :)
                 return (
                   replace value of node $c/fq:parameters/fq:parameter[2]
                      with fqt:ageToBirthYear($srcValue3),
                   replace value of node $c/fq:parameters/fq:parameter[3]
                      with fqt:ageToBirthYear($srcValue2)
                 )

               else()

               , (: Determine if we need to Translate the DataType :)
               (: WHY IS THERE NO ATTR_VALUE_TRANS_TO_DATA_TYPE in the MDR for the Attributes that NEEDS it! :)
               (: Try ask Rick, maybe it is currently hard coded :)
               for $entry in $mdrResult//entry[key=$fqt:ATTR_VALUE_TRANS_TO_DATA_TYPE]
                 let $newDataType := $entry/value/text()
                 return (
                   replace value of node $c/fq:parameters/fq:parameter[2]/@xsi:type with $newDataType
                   ,
                   replace value of node $c/fq:parameters/fq:parameter[3]/@xsi:type with $newDataType
                 )

               , (: Do More Stuff :)
               (: Always Set Translation Flag after Translation :)
               if ($mdrResult[translatedAttribute]) then
                 replace value of node $c/@transFlag with $fqt:YES
               else replace value of node $c/@transFlag with $fqt:ERROR

             ) (: End Second Return :)

           ) (: End BETWEEN Return :)

         case 'LIKE'
           (: Parameter[1]=Attribute Name, Parameter[2] = Attribute Value :)
           return (

             (: Get the Source Attribute Name :)
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName :=
               if ($c/fq:parameters/fq:parameter[1 and @aliasKey]) then
                 let $aliasKey := fn:string($c/fq:parameters/fq:parameter[1]/@aliasKey)
                 return substring-after($sourceAttrText, concat($aliasKey,'.') )
               else
                 $sourceAttrText
             (:
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName := fn:tokenize($sourceAttrText,'\.')[last()]
             :)
             (: Call MDR Web Service to Translate Attribute Name HERE :)
             let $mdrResult := fqt:getMDRResult($sourceAttrName,$fqt:FURTHER,$tgNamespaceId,$criteriaType)
             (: let $mdrURL := fqt:getMDRAttrURL($sourceAttrName,$fqt:FURTHER,$tgNamespaceId) :)
             (: let $translatedAttrName := 'Translated MDR Name' :)
             return (
               if ($mdrResult/.[translatedAttribute]) then

                 (: if need to skip, update mdrFlag as fqt:SKIP :)
                 if ($mdrResult/properties/entry[key=$fqt:ATTR_TRANS_FUNC and value=$fqt:skipATTR]) then
                   replace value of node $c/@mdrFlag with $fqt:SKIP
                 else
                   let $translatedAttrName := $mdrResult/translatedAttribute/text()
                   return (
                     replace value of node $c/fq:parameters/fq:parameter[1]
                             with fn:replace($sourceAttrText,$sourceAttrName,$translatedAttrName)
                     ,
                     replace value of node $c/@mdrFlag with $fqt:YES
                   )
               else (
                 replace value of node $c/@mdrFlag with $fqt:ERROR
                 (: DEBUG :)
                 (: replace value of node $c/fq:parameters/fq:parameter[1]
                    with concat('Debug_No_MDR_Translation_For=',$sourceAttrName) :)
               )

               , (: Comma to Separate Sequence Items :)

               (: Determine if we need to Call DTS to Translate Coded Value :)
               if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:translateCode]) then

                 let $dtsSrcPropVal := $c/fq:parameters/fq:parameter[2]/text()

                 (: Call DTS :)
                 let $dtsResponse := further:getTranslatedConcept($srcNamespaceId,
                                                                  $fqt:dtsSrcPropNm,
                                                                  $dtsSrcPropVal,
                                                                  $tgNamespaceId,
                                                                  $fqt:dtsTgPropName)
                 (: Debug DTS URL :)
                 (: let $dtsURL := further:getConceptTranslationRestUrl($srcNamespaceId,
                                                                     $fqt:dtsSrcPropNm,
                                                                     $dtsSrcPropVal,
                                                                     $tgNamespaceId,
                                                                     $fqt:dtsTgPropName) :)

                 (: let $dtsResponse := fqt:getDTSAttrValue($srcNamespaceId,$fqt:dtsSrcPropNm,$dtsSrcPropVal,$tgNamespaceId,$fqt:dtsTgPropName) :)
                 (: let $translatedPropVal := 'Translated MDR Parm3 Value' :)

                 (: let $debug := concat($srcNamespaceId,'|',
                                      $fqt:dtsSrcPropNm,'|',
                                      $dtsSrcPropVal,'|',
                                      $tgNamespaceId,'|',
                                      $fqt:dtsTgPropName) :)

                 (: let $translatedPropVal := $dtsResponse/dts:concepts/dts:conceptId/propertyValue/text() :)
                 let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

                 return
                   if ($translatedPropVal) then (
                     replace value of node $c/fq:parameters/fq:parameter[2]
                        with $translatedPropVal
                     ,
                     replace value of node $c/fq:parameters/fq:parameter[2]/@dtsFlag with $fqt:YES
                   )
                   else replace value of node $c/fq:parameters/fq:parameter[2]/@dtsFlag with $fqt:ERROR

               else if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:ageToBirthYear]) then

                 (: Get the Source Value to be Translated :)
                 let $srcVal := $c/fq:parameters/fq:parameter[2]/text()
                 return
                   replace value of node $c/fq:parameters/fq:parameter[2]
                      with fqt:ageToBirthYear($srcVal)

               else()

               , (: Determine if we need to Translate the DataType :)
               (: WHY IS THERE NO ATTR_VALUE_TRANS_TO_DATA_TYPE in the MDR for the Attributes that NEEDS it! :)
               (: Try ask Rick, maybe it is currently hard coded :)
               for $entry in $mdrResult//entry[key=$fqt:ATTR_VALUE_TRANS_TO_DATA_TYPE]
                 let $newDataType := $entry/value/text()
                 return replace value of node $c/fq:parameters/fq:parameter[2]/@xsi:type with $newDataType

               , (: Do More Stuff :)
               (: Always Set Translation Flag after Translation :)
               if ($mdrResult[translatedAttribute]) then
                 replace value of node $c/@transFlag with $fqt:YES
               else replace value of node $c/@transFlag with $fqt:ERROR

             ) (: End Second Return :)

           ) (: End LIKE Return :)

         case 'IN'
           (: Parameter[1]=Attribute Name, Multiple Attribute Values :)
           return(

             (: Get the Source Attribute Name :)
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName :=
               if ($c/fq:parameters/fq:parameter[1 and @aliasKey]) then
                 let $aliasKey := fn:string($c/fq:parameters/fq:parameter[1]/@aliasKey)
                 return substring-after($sourceAttrText, concat($aliasKey,'.') )
               else
                 $sourceAttrText

             (:
             let $sourceAttrText := $c/fq:parameters/fq:parameter[1]/text()
             let $sourceAttrName := fn:tokenize($sourceAttrText,'\.')[last()]
             :)
             (: Call MDR Web Service to Translate Attribute Name HERE :)
             let $mdrResult := fqt:getMDRResult($sourceAttrName,$fqt:FURTHER,$tgNamespaceId,$criteriaType)
             return (
               if ($mdrResult/.[translatedAttribute]) then

                 (: if need to skip, update mdrFlag as fqt:SKIP :)
                 if ($mdrResult/properties/entry[key=$fqt:ATTR_TRANS_FUNC and value=$fqt:skipATTR]) then
                   replace value of node $c/@mdrFlag with $fqt:SKIP
                 else
                   let $translatedAttrName := $mdrResult/translatedAttribute/text()
                   return (
                     replace value of node $c/fq:parameters/fq:parameter[1]
                        with fn:replace($sourceAttrText,$sourceAttrName,$translatedAttrName)
                     ,
                     replace value of node $c/@mdrFlag with $fqt:YES
                   )
               else (
                 replace value of node $c/@mdrFlag with $fqt:ERROR
                 (: DEBUG :)
                 (: replace value of node $c/fq:parameters/fq:parameter[1]
                       with concat('Debug_No_MDR_Translation_For=',$sourceAttrName) :)
               )

               , (: Process DTS Stuff :)
               (: Determine if we need to Call DTS to Translate Coded Value :)
               if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:translateCode]) then

                 (: Process One Parameter at a Time, there could be many since this is the IN Operator :)
                 (: The first parameter is the Attribute Name, the rest are Attribute Values :)
                 for $parm in $c/fq:parameters/fq:parameter[position()>1]

                   (:return

                     if ($index>1) then

                       let $dtsSrcPropVal := $c/fq:parameters/fq:parameter[$index]/text():)

                       (: Call DTS :)
                       let $dtsResponse := further:getTranslatedConcept($srcNamespaceId,
                                                                        $fqt:dtsSrcPropNm,
                                                                        $parm,
                                                                        $tgNamespaceId,
                                                                        $fqt:dtsTgPropName)
                       (: Debug DTS URL :)
                       (: let $dtsURL := further:getConceptTranslationRestUrl($srcNamespaceId,
                                                                           $fqt:dtsSrcPropNm,
                                                                           $parm,
                                                                           $tgNamespaceId,
                                                                           $fqt:dtsTgPropName) :)

                       let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

                       return
                         if ($translatedPropVal) then (
                           replace value of node $parm with $translatedPropVal
                           ,
                           replace value of node $parm/@dtsFlag with $fqt:YES
                         )
                         else replace value of node $parm/@dtsFlag with $fqt:ERROR

               else if ($mdrResult/properties/entry[key=$fqt:ATTR_VALUE_TRANS_FUNC and value=$fqt:ageToBirthYear]) then

                 (: Get the Source Value to be Translated :)
                 (: Process One Parameter at a Time, there could be many since this is the IN Operator :)
                 for $param at $index in ($criteria/fq:parameters/*)

                   (: The first parameter is the Attribute Name, the rest are Attribute Values :)
                   return

                     if ($index>1) then
                       let $srcVal := $c/fq:parameters/fq:parameter[$index]/text()
                       return
                         replace value of node $c/fq:parameters/fq:parameter[$index]
                            with fqt:ageToBirthYear($srcVal)

                     else( (: End Inner IF :) )

               else( (: End Outter IF :) )

             , (: Determine if we need to Translate the DataType :)
             for $entry in $mdrResult//entry[key=$fqt:ATTR_VALUE_TRANS_TO_DATA_TYPE]
               let $newDataType := $entry/value/text()
               return
                 (: Process One Parameter at a Time, there could be many since this is the IN Operator :)
                 for $param at $index in ($criteria/fq:parameters/*)
                   (: The first parameter is the Attribute Name, the rest are Attribute Values :)
                   return
                     if ($index>1) then
                       replace value of node $c/fq:parameters/fq:parameter[$index]/@xsi:type with $newDataType
                     else( (: DO NOTHING :) )

             , (: Do More Stuff :)
             (: Always Set Translation Flag after Translation :)
             if ($mdrResult[translatedAttribute]) then
               replace value of node $c/@transFlag with $fqt:YES
             else replace value of node $c/@transFlag with $fqt:ERROR

             ) (: End Second Return :)

           ) (: End IN Return :)

         default return <error>Invalid searchType in transCriteria function</error>
         (: End Switch Case :)

(: End Modify :)
)

(: Return Single Modified Criteria Node :)
return $c
(: END XQUERY TRANSFORMATION Function :)
};

(:==================================================================:)
(: transAlias = Translate Alias Stuff for Query                     :)
(:==================================================================:)
declare function fqt:transAlias($inputXML as document-node(),
                                $targetNamespaceId as xs:string)
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Must Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Determin if Person translates to a Non-Person Table :)
  (: The Problem is that FURTHER Person fields do not have an Alias.
     So if it translates to a Non-Person Fields at the Target,
     We need to provide the target alias in the translated XML.
     In our XML, we need to get from fieldName to targetAlias.fieldName :)
  (: For example, FURTHER.PERSON.VITAL_STATUS trans to OMOPv2.OBSERVATION_PERIOD.PERSON_STATUS_CONCEPT_ID :)
  (: We're not using this field for now, but would be nice to have this functionality in place :)

  (: Translate Aliases :)
  (: Must Translated the entire group of <aliases> at one time
     Because the replace cannot update single <alias> node with multiple <alias> nodes
     It needs to be <aliases><alias></alias><alias></alias></aliases> :)

  (: The $index is used to differentiate each Alias Name :)
  for $alias at $index in $inputCopy//fq:alias

    (: Store Original Source Values :)
    let $associationObject := fn:string($alias/@associationObject)
    let $aliasKey := $alias/fq:key/text()
    let $aliasValue := $alias/fq:value/text()

    (: Get Translated Response from MDR for Target Namespace :)
    let $mdrResponse := fqt:getMDRAssoc($associationObject,$targetNamespaceId)

    (: Extract Out Translated Alias(es) :)
    let $translatedAlias := fqt:getAlias($mdrResponse,$index,$aliasKey,$targetNamespaceId)

    (: For Each Association :)
    (: Every Table Association Should have MDR PROPERTIES for ONE Key & Value Pair :)
    (: OLD CODE COMMENTED OUT
    let $mdrAssocObject := $mdrResponse/rightAsset/text()
    for $entry in $mdrResponse/properties/entry[key='ALIAS_KEY']
      let $mdrKey := <key>{concat( $entry/value/text(), fn:string($index) )}</key>
    for $entry in $mdrResponse/properties/entry[key='ALIAS_VALUE']
      let $mdrValue := <value>{$entry/value/text()}</value>
    :)

    (: EACH NODE ONLY BE REPLACED ONCE! does not work! :)
    return (

      (: Note :)
      (: Cannot use replace here since replace requires a valid xml document structure :)
      (: I just want to work with nodes here :)

      (: Remove Original Alias Node :)
      delete node $alias,

      (: Insert Translated Alias Node(s) for Each Original Alias Node :)
      (: Inserting into the Parent .. <aliases> node! :)
      insert node $translatedAlias into $alias/..

      (: Cannot use this since sometimes there are New Additional Nodes! :)
      (: There are one alias to many aliases relationships :)
      (:
      replace value of node $alias/@associationObject with $mdrAssocObject,
      replace value of node $alias/fq:key with $mdrKey,
      replace value of node $alias/fq:value with $mdrValue,
      :)

    ) (: End Return :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: transRoot = Translate rootObject                                 :)
(:==================================================================:)
declare function fqt:transRoot($inputXML as document-node(),
                               $targetNamespaceId as xs:string)
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Must Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Querys and SubQuerys have rootObjects :)
  (: Therefore, For Each Query that has an rootObject XML Attribute :)
  for $query in $inputCopy//fq:query[@rootObject]

    (: Get the Original rootObject Value :)
    let $rootObject := fn:string($query/@rootObject)

    (: Get Translated Response from MDR :)
    let $mdrResponse := fqt:getMDRAssoc($rootObject,$targetNamespaceId)

    return
      if ( $mdrResponse/assetAssociation[rightAsset] ) then

        (: DEBUG :)
        (: let $mdrRoot := concat('Translated_Root=',$rootObject) :)

        (: Strip out the Translated rootObject Value :)
        let $mdrRoot := $mdrResponse/assetAssociation/rightAsset/text()
        return replace value of node $query/@rootObject with $mdrRoot
    else ()

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: getMDRAssoc = Get MDR Association as a Response XML              :)
(:==================================================================:)
declare function fqt:getMDRAssoc($assocObject as xs:string, $tgNmspcId as xs:string)
{ (: BEGIN FUNCTION :)

<assetAssociationList>
{
  (: let $baseURL := 'http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/left/translatesTo/' :)
  let $baseURL := fn:concat($const:fmdrRestServer,'/rest/asset/association/left/translatesTo/')
  let $docUrl := fn:concat( $baseURL, $assocObject )

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

  (: Get the Namespace Name so we can use it to strip out the correct assetAssociation :)
  let $tgNmspcName := further:getNamespaceName($tgNmspcId)

  (: Return the proper association(s) for the target Namespace :)
  for $assetAssociation in $response/mdr:assetAssociationList/assetAssociation[rightNamespace=$tgNmspcName]
    return $assetAssociation
}
</assetAssociationList>

(: End of FUNCTION :)
};

(:==================================================================:)
(: Validate (Error Handling)                                        :)
(:==================================================================:)
declare function fqt:validate($inputXML as document-node(),$tgNmspcName as xs:string)
{
(: 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. :)

  (: Error Out ANY 'LIKE' <searchType> Since we are not supporting at this time :)
  if ($inputCopy//fq:searchType='LIKE') then (

    (: Wish i could just replace, but did not work :)
    (: So I'm just deleting the whole file and then inserting the Error :)
    delete node $inputCopy//fq:query,
    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $c in ($inputCopy//fq:criteria[fq:searchType='LIKE'])[1]
      let $attrName := fqt:getAttrNameFromCriteria($c)
      return
      insert node
        <error xmlns="http://further.utah.edu/core/ws">
          <code>QUERY_TRANSLATION_ERROR</code>
          <message>LIKE searchType is not supported at this time.</message>
        </error>
        into $inputCopy
  )

  (: Check for Non-Translated Criterias :)
  else if ($inputCopy//fq:criteria[@transFlag=$fqt:NO]) then (

    (: Wish i could just replace, but did not work :)
    (: So I'm just deleting the whole file and then inserting the Error :)
    delete node $inputCopy//fq:query,
    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $c in ($inputCopy//fq:criteria[@transFlag=$fqt:NO])[1]
      let $attrName := fqt:getAttrNameFromCriteria($c)
      return
      insert node
        <error xmlns="http://further.utah.edu/core/ws">
          <code>QUERY_TRANSLATION_ERROR</code>
          <message>Criteria [ {$tgNmspcName}.{$attrName} ] NOT Tranlated</message>
        </error>
        into $inputCopy
  )
  else if ($inputCopy//fq:criteria[@mdrFlag=$fqt:ERROR]) then (

    (: Wish i could just replace, but did not work :)
    (: So I'm just deleting the whole file and then inserting the Error :)
    delete node $inputCopy//fq:query,
    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $c in ($inputCopy//fq:criteria[@mdrFlag=$fqt:ERROR])[1]
      let $attrName := fqt:getAttrNameFromCriteria($c)
      return
      insert node
        <error xmlns="http://further.utah.edu/core/ws">
          <code>MDR_QUERY_TRANSLATION_ERROR</code>
          <message>MDR Association for [ {$tgNmspcName}.{$attrName} ] May be Missing</message>
        </error>
        into $inputCopy
  )
  else if ($inputCopy//fq:parameter[@dtsFlag=$fqt:ERROR]) then (

    (: Wish i could just replace, but did not work :)
    (: So I'm just deleting the whole file and then inserting the Error :)
    delete node $inputCopy//fq:query,
    (: Return the First Criteria that Errored so we do not have a long list of Errors :)
    for $c in ($inputCopy//fq:criteria[fq:parameters/fq:parameter/@dtsFlag=$fqt:ERROR])[1]
      let $attrName := fqt:getAttrNameFromCriteria($c)
      return
      insert node
        <error xmlns="http://further.utah.edu/core/ws">
          <code>DTS_QUERY_TRANSLATION_ERROR</code>
          <message>DTS Mapping for [ {$tgNmspcName}.{$attrName} ] May be Missing</message>
        </error>
        into $inputCopy
  )
  else(
    (: Remove ALL Criteria Nodes that have been SKIPPED :)
    (: This includes the criterias with 'NamespaceId' in it :)
    (: Exclude SubQuery's FIRST Criteria :)
    delete node $inputCopy//fq:criteria[(@transFlag=$fqt:SKIP or @mdrFlag=$fqt:SKIP) and not(fq:query)]
  ) (: End IF-Else Statement :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: Cleanup                                                          :)
(:==================================================================:)
declare function fqt:cleanup($inputXML as document-node())
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: If the validate step did not Error Out.
     Meaning the <query> is still there.
     Then Process Cleanup.
     Otherwise we do not need to do anything. :)
  if ($inputCopy[fq:query]) then (

    (: Remove all Aliases that are not being used :)
    (: Sometimes one Source Alias translates to multiple target Aliases :)
    (: However, not all translated Aliases may be needed :)
    for $alias in $inputCopy//fq:alias
      let $key := $alias/fq:key
      return
        if (not(fn:exists($inputCopy//fq:parameter[fn:tokenize(.,'\.')[1]=$key]))) then
          delete node $alias
        else ()

    , (: Do more stuff :)

    (: Rename the criteria with @rootCriterion back to <rootCriterion> :)
    for $rc in $inputCopy//fq:criteria[@rootCriterion=$fqt:YES]
      return (
        rename node $rc as QName("http://further.utah.edu/core/query", "rootCriterion"),
        (: Delete rootCriterion Attribute :)
        delete node $rc/@rootCriterion
      )

    ,(: Do more stuff :)

    (: Delete ALL tranFlag Attributes on SUCCESS :)
    delete node $inputCopy//@transFlag,
    delete node $inputCopy//@mdrFlag,
    delete node $inputCopy//@dtsFlag

    ,(: Do more stuff :)

    (: Delete ALL aliasKey Attributes on SUCCESS :)
    delete node $inputCopy//@aliasKey,
    delete node $inputCopy//@oldAliasKey

    ,(: Do more stuff :)

    (: Delete ALL obsType Attributes on SUCCESS :)
    delete node $inputCopy//@obsType

  )
  else( (: DO NOTHING :) )

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: getMDRResult = ALWAYS GET ONE MDR Attribute Translation RESULT   :)
(:==================================================================:)
declare function fqt:getMDRResult(
  $srcAttrName as xs:string,
  $srcNmspcId as xs:string,
  $tgNmspcId as xs:string,
  $criteriaType as xs:string?)
{
  (: EXAMPLE: ${server.mdr.ws}/mdr/rest/asset/association/unique/attribute/administrativeGender/FURTHeR/UUEDW :)
  (:          ${server.mdr.ws}/mdr/rest/asset/association/unique/attribute/{sourceAttr}/{sourceNamespace}/{targetNamespace} :)
  (:          http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/unique/attribute/administrativeGender/FURTHeR/UUEDW :)
  (: let $baseURL := 'http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/attribute/ :)
  let $baseURL := fn:concat($const:fmdrRestServer,'/rest/asset/association/attribute/')
  let $docUrl := fn:concat( $baseURL, $srcAttrName, '/', $srcNmspcId, '/', $tgNmspcId )

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

  (: if there is more than one result, get the correct one based on the criteriaType :)
  return
    if ( count($doc//attributeTranslationResult) > 1 ) then
      $doc/mdr:attributeTranslationResultList/attributeTranslationResult[properties/entry/value=$criteriaType]
    else
      $doc/mdr:attributeTranslationResultList/attributeTranslationResult
};

(:==================================================================:)
(: getMDRAttrURL = Get URL for Debugging                            :)
(:==================================================================:)
declare function fqt:getMDRAttrURL(
  $srcAttrName as xs:string,
  $srcNmspcId as xs:string,
  $tgNmspcId as xs:string)
{
  (: EXAMPLE: ${server.dts.ws}/mdr/rest/asset/association/unique/attribute/administrativeGender/FURTHeR/UUEDW :)
  (:          ${server.dts.ws}/mdr/rest/asset/association/unique/attribute/{sourceAttr}/{sourceNamespace}/{targetNamespace} :)
  (:          http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/unique/attribute/administrativeGender/FURTHeR/UUEDW :)
  (: let $baseURL := 'http://dev-esb.further.utah.edu:9000/mdr/rest/asset/association/attribute/':)
  let $baseURL := fn:concat($const:fmdrRestServer,'/rest/asset/association/attribute/')
  let $docUrl := fn:concat( $baseURL, $srcAttrName, '/', $srcNmspcId, '/', $tgNmspcId )

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

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

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

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

(:==================================================================:)
(: insertObsType = Set ObservationType Flag for later Processing    :)
(:==================================================================:)
declare function fqt:insertObsType($inputXML as document-node())
{
copy $inputCopy := $inputXML
modify (

  (: Loop through each Phrase :)
  for $criteriaPhrase in $inputCopy//*[fq:searchType/text()='CONJUNCTION'
                                      and fq:criteria[fq:searchType/text()='SIMPLE'
                                      and fn:contains(fq:parameters/fq:parameter[2]/text(),'observationNamespaceId')]]

    (: Get the observationType Value :)
    for $criteria in $criteriaPhrase/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),'observationType')]
      let $criteriaType := $criteria/fq:parameters/fq:parameter[3]/text()

      (: Set ObservationType For Each 'observation' Related Parameters
         Including SIMPLE & IN searchTypes and observationType
         Since sometimes we do translate the observationType values :)
      (:for $parm in $criteriaPhrase/fq:criteria/fq:parameters/fq:parameter[fn:starts-with(fn:tokenize(.,'\.')[last()],'observation')]
        return insert node attribute obsType {$criteriaType} into $parm :)

      (: A better way may be, for every parameter that has an aliasKey attribute :)
      (: Because for Labs, there are parameters with non 'observation' words in it :)
      for $parm in $criteriaPhrase/fq:criteria/fq:parameters/fq:parameter[@aliasKey]
        return insert node attribute obsType {$criteriaType} into $parm

) (: End Modify :)
return $inputCopy
};

(:==================================================================:)
(: getAlias = Return Alias Node(s)                                  :)
(:==================================================================:)
declare function fqt:getAlias($mdrResponse,$outerIndex,$oldAliasKey,$tgNmspcId)
{
  (: Get the Namespace Name so we can use it to strip out the correct assetAssociation :)
  let $tgNmspcName := further:getNamespaceName($tgNmspcId)

  (: There can be more than one Object Assocations :)
  for $assoc at $innerIndex in $mdrResponse/assetAssociation[rightNamespace=$tgNmspcName]

    (: Create composite Index to Ensure it is Unique Since sometimes there could be multiple assocations :)
    let $compositeIndex := concat($outerIndex,$innerIndex)

    (: Each Table Association MUST have MDR PROPERTIES for ONE Key & Value Pair :)
    let $mdrAssocObject := $assoc/rightAsset/text()
    for $entry in $assoc/properties/entry[key='ALIAS_KEY']
      let $mdrKey := <key>{concat( $entry/value/text(), $compositeIndex )}</key>
    for $entry in $assoc/properties/entry[key='ALIAS_VALUE']
      let $mdrValue := <value>{$entry/value/text()}</value>

  (: Return Alias Node(s) :)
  return
  if ($assoc/properties/entry[starts-with(key,'OBSERVATION_TYPE')]) then
    (: Creates Multiple <alias> nodes if there are more than one OBSERVATION_TYPE entry :)
    for $entry in $assoc/properties/entry[starts-with(key,'OBSERVATION_TYPE')]
    let $obsType := $entry/value/text()
    return <alias oldAliasKey='{$oldAliasKey}' obsType='{$obsType}' associationObject='{$mdrAssocObject}'>{$mdrKey}{$mdrValue}</alias>
  else
    <alias oldAliasKey='{$oldAliasKey}' associationObject='{$mdrAssocObject}'>{$mdrKey}{$mdrValue}</alias>

};

(:==================================================================:)
(: updateParmAlias = Update Alias in Parameters                     :)
(:==================================================================:)
declare function fqt:updateParmAlias($inputXML as document-node())
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Must Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: For Each Alias :)
  for $alias in $inputCopy//fq:alias[@oldAliasKey]

    (: Get the Old and New AliasKeys :)
    let $oldAliasKey := $alias/@oldAliasKey
    let $newAliasKey := $alias/fq:key/text()
    let $obsType := $alias/@obsType

    (: Replace Each Alias Attribute with the new Translated Alias Name :)
    (: Using the XQuery replace (case sensitive by default) function :)
    (: The middle parameter can be a Regular Expression, but we're currently using it as a string :)
    (: Appending a period at the end of the alias
       To ensure that we are replacing the Table Alias and NOT the Table Attribute :)
    (: For example, if we had observation.observation in the parameter,
       Only the "observation." (with the period) will be replaced :)
    (: Determine which Association to use if more than one :)

    return
      if ($alias/.[@obsType]) then
        (: For Each Parameter with Observation Type, Update Alias Prefix :)
        for $parm at $index in $inputCopy//fq:parameter[@aliasKey=$oldAliasKey and @obsType=$obsType]
        return (
          replace value of node $parm with
          fn:replace($parm/text(), concat($oldAliasKey,'.'), concat($newAliasKey,'.') )

        )
      else
        (: For Each Parameter Non-Observation Types, Update Alias Prefix :)
        for $parm at $index in $inputCopy//fq:parameter[@aliasKey=$oldAliasKey]
        return (
          replace value of node $parm with
          fn:replace($parm/text(), concat($oldAliasKey,'.'), concat($newAliasKey,'.') )

        )

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: dedupAliases Remove Duplicate Aliases                            :)
(: Duplicate Aliases occur when a target table is overloaded        :)
(: Meaning One Source Table translates to Multiple Target Tables    :)
(: Intended to implement in cleanup function.
   However it conflicts with the Update Pending List
   Therefore, needed to implement its own function                  :)
(:==================================================================:)

declare function fqt:dedupAliases($inputXML as document-node())
{
(: BEGIN XQUERY TRANSFORMATION :)
(: Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Remove Duplicate Alias Nodes within the SAME <aliases> Node :)
  for $aliases in $inputCopy//fq:aliases

    (: This is some Tricky Stuff Right Here! :)
    (: Select only one of its occurences,
       Such that its index in all Aliases is the same as the first index of its key in all keys. :)
    let $uniqueAliases := $aliases//fq:alias [index-of($aliases//fq:key, fq:key ) [1] ]
    return (
      delete node $aliases/fq:alias,
      insert node $uniqueAliases into $aliases
    )

) (: End Modify :)
return $inputCopy

(: END Function :)
};

(:==================================================================:)
(: Translate Criteria Phrase                                        :)
(:==================================================================:)
declare function fqt:transCriteriaPhrase($inputXML as document-node(),
                                         $targetNamespaceId as xs:string){
(: BEGIN XQUERY TRANSFORMATION :)
(: Must Make a copy of the entire Document :)
copy $inputCopy := $inputXML
modify (

  (: Loop through each Phrase :)
  for $criteriaPhrase in $inputCopy//*[fq:searchType/text()='CONJUNCTION'
                                      and fq:criteria[fq:searchType/text()='SIMPLE'
                                      and fn:contains(fq:parameters/fq:parameter[2]/text(),'NamespaceId')]]

    (: Find each Namespace Group of Criterias, there could be more than one. :)
    for $criteriaNamespace in $criteriaPhrase/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),'NamespaceId')]

      (: Get Source Namespace ID for this Group :)
      let $sourceNamespaceId := $criteriaNamespace/fq:parameters/fq:parameter[3]/text()
      (: Ensure that Java Fields follow consistent naming convention :)
      (: For example, field and fieldNamespaceId :)
      let $namespaceText := $criteriaNamespace/fq:parameters/fq:parameter[2]/text()
      let $namespaceField := substring-before(fn:tokenize($namespaceText,'\.')[last()],'NamespaceId')

      (: Get the Criteria Type if there is one :)
      let $criteriaType :=
        if ( $criteriaPhrase/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),concat($namespaceField,'Type'))] ) then
          for $criteria in $criteriaPhrase/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),concat($namespaceField,'Type'))]
          let $ctName := $criteria/fq:parameters/fq:parameter[2]/text()
          let $ctValue := $criteria/fq:parameters/fq:parameter[3]/text()
          return ($ctName,$ctValue)
        else
          $fqt:EMPTY

      (: Set the Namespace for the Type Code, Since the Type uses a separate NamespaceID :)
      let $criteriaTypeNmspcId :=
        if (fn:tokenize($criteriaType[1],'\.')[last()] = 'observationType')
        then $fqt:SNOMED
        else $fqt:EMPTY

      (: Note there needs to be a matching "name" from "name"NamespaceId above :)
      (:let $criteria := $criteriaPhrase/fq:criteria[fn:tokenize(fq:parameters/fq:parameter[2]/text(),'\.')[last()]=$namespaceField]:)
      return (

        (: Mark NamespaceId Criterias to be Skipped Since Target does not need them :)
        replace value of node $criteriaNamespace/@transFlag with $fqt:SKIP

        , (: Process Field Criteria for Namespace :)
        (: There may potentially be more than one observation within the SAME Phrase in the future :)
        (: SIMPLE searchType has the fieldName in parameter[2]
           Other searchType (such as 'IN') has the fieldName in parameter[1] :)
        for $criteria in $criteriaPhrase/fq:criteria[fn:tokenize(fq:parameters/fq:parameter[1]/text(),'\.')[last()]=$namespaceField
                                                     or
                                                     fn:tokenize(fq:parameters/fq:parameter[2]/text(),'\.')[last()]=$namespaceField]
        return replace node $criteria
           with fqt:transCriteria($criteria,$sourceNamespaceId,$targetNamespaceId,$criteriaType[2],$criteriaTypeNmspcId)

        , (: See if there is a Type such as observationType :)
        (: The Type only needs to be processed ONCE within one Phrase :)
        if ($criteriaPhrase/fq:criteria
          [ fn:tokenize(fq:parameters/fq:parameter[2]/text(),'\.')[last()]=concat($namespaceField,'Type')])
        then
          let $ct := $criteriaPhrase/fq:criteria
          [ fn:tokenize(fq:parameters/fq:parameter[2]/text(),'\.')[last()]=concat($namespaceField,'Type')]
          return
          replace node $ct
           with fqt:transCriteria($ct,$sourceNamespaceId,$targetNamespaceId,$criteriaType[2],$criteriaTypeNmspcId)
        else()

        (: We can potentially Process the left over Single Criterias Here.
           However, I've decided to handle it in the transSingleCriteria function :)

      ) (: End Return :)

) (: End Modify :)
return $inputCopy
(: END XQUERY TRANSFORMATION :)
};

(:==================================================================:)
(: preTransOMOP                                                     :)
(: Pre-Translate ICD-9 Codes into SNOMED for OMOP Target ONLY!      :)
(: Since DTS Maps from ICD-9 to SNOMED to OMOP                      :)
(:==================================================================:)
declare function fqt:preTransOMOP($inputXML as document-node(),
                                  $targetNamespaceId as xs:string)
{
copy $inputCopy := $inputXML
modify (

  (: For OMOP Targets ONLY! :)
  if ($targetNamespaceId = $fqt:OMOP-V2) then

    (: Find Each ICD-9 Value :)
    for $criteriaGroup in $inputCopy//*[fq:searchType/text()='CONJUNCTION'
                                        and fq:criteria[fq:searchType/text()='SIMPLE'
                                        and fn:contains(fq:parameters/fq:parameter[2]/text(),'observationNamespaceId')
                                        and fq:parameters/fq:parameter[3]/text()=$fqt:ICD-9]]

      return (

      (: Replace NamespaceId to SNOMED :)
      for $criteria in $criteriaGroup/fq:criteria[fn:contains(fq:parameters/fq:parameter[2]/text(),'observationNamespaceId')]
        return replace value of node $criteria/fq:parameters/fq:parameter[3] with $fqt:SNOMED

      , (: Do more Stuff :)

      (: Note that ICD-9 Codes ONLY Occur in 'SIMPLE', or 'IN' searchTypes :)

      (: Strip out the observation value for SIMPLE searchType :)
      for $criteria in $criteriaGroup/fq:criteria[fn:tokenize(fq:parameters/fq:parameter[2],'\.')[last()] = 'observation'
                                                  and fq:searchType='SIMPLE']

        let $dtsSrcPropVal := $criteria/fq:parameters/fq:parameter[3]

        (: Call DTS :)
        let $dtsResponse := further:getTranslatedConcept($fqt:ICD-9,
                                                         $fqt:dtsSrcPropNm,
                                                         $dtsSrcPropVal,
                                                         $fqt:SNOMED,
                                                         $fqt:dtsSrcPropNm)

        (: Debug DTS URL :)
        (:let $dtsURL := further:getConceptTranslationRestUrl($fqt:ICD-9,
                                                            $fqt:dtsSrcPropNm,
                                                            $dtsSrcPropVal,
                                                            $fqt:SNOMED,
                                                            $fqt:dtsSrcPropNm):)

        let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

        (: if there is a response :)
        return
        if ($translatedPropVal) then
          replace value of node $criteria/fq:parameters/fq:parameter[3] with $translatedPropVal
        else
          replace value of node $criteria/fq:parameters/fq:parameter[3] with $fqt:ZERO

      , (: Do more Stuff :)

      (: Strip out the observation value for IN searchType :)
      for $criteria in $criteriaGroup/fq:criteria[fn:tokenize(fq:parameters/fq:parameter[1],'\.')[last()] = 'observation'
                                                  and fq:searchType='IN']

        (: For Parameters greater than the first position :)
        for $parm in $criteria/fq:parameters/fq:parameter[position()>1]

          (: Call DTS :)
          let $dtsResponse := further:getTranslatedConcept($fqt:ICD-9,
                                                           $fqt:dtsSrcPropNm,
                                                           $parm,
                                                           $fqt:SNOMED,
                                                           $fqt:dtsSrcPropNm)

          (: Debug DTS URL :)
          (:let $dtsURL := further:getConceptTranslationRestUrl($fqt:ICD-9,
                                                              $fqt:dtsSrcPropNm,
                                                              $parm,
                                                              $fqt:SNOMED,
                                                              $fqt:dtsSrcPropNm):)

          let $translatedPropVal := further:getConceptPropertyValue($dtsResponse)

          (: if there is a response :)
          return
          if ($translatedPropVal) then
            replace value of node $parm with $translatedPropVal
          else
            replace value of node $parm with $fqt:ZERO

    ) (: End Return :)

  else( (: DO NOTHING if Target is NOT OMOP :) )

) (: End Modify :)
return $inputCopy
};

(:=====================================================================:)
(: Sanitize SINGLE & DOUBLE Quotes to Prevent XQuery Injection Attacks :)
(:=====================================================================:)
declare function fqt: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 fqt:sanitize :)

(:=====================================================================:)
(: getAttrNameFromCriteria                                             :)
(: Get the Attribute Name from a Single Criteria                       :)
(:=====================================================================:)
declare function fqt:getAttrNameFromCriteria($criteria as element())
as xs:string
{
  (: For SIMPLE searchType, it is always in the Second <parameter>
     Otherise, it is always in the First <parameter> :)
  let $attrName :=
    if ($criteria/fq:searchType = $fqt:SIMPLE) then
      $criteria/fq:parameters/fq:parameter[2]/text()
    else
      $criteria/fq:parameters/fq:parameter[1]/text()

  return $attrName

}; (: END OF FUNCTION fqt:getAttrNameFromCriteria :)

(: END OF MODULE :)