Enhanced Error Handling for DTS Translations to include the Value that Failed.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  (: Must Keep this Order of Processing :)

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

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

  return (: $mdrPerson :)

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

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

    else (: Continue Processing :)

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

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

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

		  (: Return Final Cleaned Version :)
		  return $cleaned

};

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

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

(: END Function :)
};

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

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

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

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

    , (: DO MORE STUFF :)

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

    , (: DO MORE STUFF :)

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

    , (: DO MORE STUFF :)

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

  ) (: End Return :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

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

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

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

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

  else ()

) (: End Modify :)
return $inputCopy

(: END Function :)
};

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

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

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

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

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

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

(: END Function :)
};

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

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

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

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

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

    ) (: End Return :)

) (: End Modify :)
return

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

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

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

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

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

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

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

) (: End Modify :)
return

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

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

) (: End Modify :)
return $transRootId

(: END Function :)
};

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

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

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

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

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

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

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

  (: End IF-Else Statement :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

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

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

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

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

) (: End Modify :)
return

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

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

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

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

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

) (: End Modify :)
return $inputCopy2

(: END Function :)
};

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

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

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

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

) (: End Modify :)

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

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

) (: End Modify :)
return $inputCopy2

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

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

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

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

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

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

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

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

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

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

		  return (

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

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

  ) (: End of Return :)

}; (: END OF FUNCTION :)

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

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

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

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

};

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

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

};

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

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

};

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

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

  return

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

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

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

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

    else $frt:ERROR

};

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

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

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

(: END OF MODULE :)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  (: Must Keep this Order of Processing :)

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

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

  return (: $mdrPerson :)

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

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

    else (: Continue Processing :)

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

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

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

		  (: Return Final Cleaned Version :)
		  return $cleaned

};

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

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

(: END Function :)
};

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

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

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

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

    , (: DO MORE STUFF :)

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

    , (: DO MORE STUFF :)

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

    , (: DO MORE STUFF :)

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

  ) (: End Return :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

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

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

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

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

  else ()

) (: End Modify :)
return $inputCopy

(: END Function :)
};

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

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

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

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

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

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

(: END Function :)
};

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

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

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

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

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

    ) (: End Return :)

) (: End Modify :)
return

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

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

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

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

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

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

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

) (: End Modify :)
return

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

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

) (: End Modify :)
return $transRootId

(: END Function :)
};

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

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

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

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

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

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

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

  (: End IF-Else Statement :)

) (: End Modify :)
return $inputCopy

(: END Function :)
};

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

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

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

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

) (: End Modify :)
return

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

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

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

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

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

) (: End Modify :)
return $inputCopy2

(: END Function :)
};

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

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

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

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

) (: End Modify :)

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

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

) (: End Modify :)
return $inputCopy2

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

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

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

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

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

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

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

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

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

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

		  return (

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

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

  ) (: End of Return :)

}; (: END OF FUNCTION :)

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

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

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

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

};

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

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

};

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

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

};

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

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

  return

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

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

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

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

    else $frt:ERROR

};

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

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

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

(: END OF MODULE :)


Infrastructure for getting indirect links coming together. No syntax errors

(: =====================================================

   DITA Link Management Utilities

   Utilities that support general DITA link management
   actions.

   Author: W. Eliot Kimber

   Copyright (c) 2015 DITA For Small Teams
   Licensed under Apache License 2

   ===================================================== :)

module namespace lmutil="http://dita-for-small-teams.org/xquery/modules/linkmgmt-utils";

declare namespace dfst="http://dita-for-small-teams.org";

import module namespace df="http://dita-for-small-teams.org/xquery/modules/dita-utils";
import module namespace bxutil="http://dita-for-small-teams.org/xquery/modules/basex-utils";
import module namespace dfstcnst="http://dita-for-small-teams.org/xquery/modules/dfst-constants";
import module namespace relpath="http://dita-for-small-teams.org/xquery/modules/relpath-utils";

declare variable $lmutil:useMatchParamsLocalMaps :=
                               map{'linktype' :'topicref',
                                   'format' : 'ditamap',
                                   'scope' : 'local'
                                  };
declare variable $lmutil:useMatchParamsPeerMaps :=
                               map{'linktype' :'topicref',
                                   'format' : 'ditamap',
                                   'scope' : 'peer'
                                  };

(:~
 : Find all links that use direct URI references to their target resources.
 : Returns list of link item maps
 :
 : Each link item map has the following items:
 :
 : 'link' : The element that is the link
 : 'rootMap' : The root map document that defines the namespace the link
 :             is resolved in.
 : 'resolvedMap' : The fully-resolved map that defines key space the key
 :                 reference is resolved in.
 : 'keySpace' : The constructed key space for the root map.
 : 'processingRole' : For topicrefs, the @processing-role value. For
 :                    other links, 'normal'.
 : 'linkContext': The relevant container type for the link, one of:
 :                - 'title' : Link occurs within a title element
 :                - 'prolog' : Link occurs with topicmeta (maps) or prolog (topics)
 :                - 'shortdesc' : Link occurs within a short description
 :                - 'abstract' : Link occurs within an abstract (but not within a
 :                               short description within an abstract)
 :                - 'body' : Link occurs within a topic body
 :                - 'reltable' : Link is a topicref within a relationship table.
 :                - 'navtree' : Link is a topicref with @processing-role of 'normal' and
 :                              is not within a relationship table
 :                - 'resources' : Link is a topicref with a processing role of "resource-only".
 :  'isDirect' : - 'true' if link is a direct URI reference
 :               - 'false' if link uses a resolvable key reference
 :
 : For direct links the link element is sufficient
 : to allow resolution as use context has no affect on how the link is resolved.
 : For "." ("this topic") fragment IDs, the context is always the containing topic.
 :
 : @param dbName Name of the database that contains the documents to be processed.
 : @param logID ID of the log to put log messages in.
 :)
declare function lmutil:findAllDirectLinks($dbName) as map(*)* {
  let $db := db:open($dbName)
  (: First do direct URI references, which don't require any
     context knowledge:
   :)
  let $links := collection($dbName)//*[df:isTopicRef(.) and not(@keyref)] |
                collection($dbName)//*[contains(@class, ' topic/xref ') and (@href and not(@keyref))] |
                collection($dbName)//*[contains(@class, ' topic/data-about ') and (@href and not(@keyref))] |
                collection($dbName)//*[contains(@class, ' topic/longdescref ') and (@href and not(@keyref))] |
                collection($dbName)//*[@conref]
   (: Construct a link item map for each link element :)
   for $link in $links
       return lmutil:constructLinkItemMap($link)
};

(:~
 : Constructs a link item map for a direct link.
 :
 : @param link The link element
 : @return Link item map
 :)
declare function lmutil:constructLinkItemMap(
                        $link as element()) as map(*) {
  let $result := lmutil:constructLinkItemMap($link)
  return $result
};

(:~
 : Constructs a link item map
 :
 : @param link The link element
 : @param topicref For indirect links, the topicref that establishes
 :                 the key resolution context for the link. Not set for direct URI links.
 :                 Note that this topicref element must be from the resolved map, not the
 :                 original source map.
 :)
declare function lmutil:constructLinkItemMap(
                        $link as element(),
                        $topicref as element()?) as map(*) {
   let $linkItem :=
      map{'link': $link,
          'resolvedMap': if ($topicref) then root($topicref)/* else (),
          'processingRole':
             if (df:isTopicRef($link))
                then if ($link/@processing-role)
                        then string($link/@processing-role)
                        else 'normal' (: Default for topicref :)
                else 'normal', (: non-topicref links :)
          'linkContext': lmutil:getLinkContext($link)
         }
    return $linkItem
};

(: Give a document, finds all references to that document that match the
   type of uses as configured in the $useParams.

   @param doc Document to find uses of.
   @param useParams Use filter parameters. Only uses that match the parameters
                    will be reported.
   @return Zero or more use record elements.

   The use parameters are:

   linktype: List of base type names (e.g., 'topicref, xref) or qualified
             class names (e.g., 'map-d/navref') of the types of links
             to report usage for. If unspecifed, all link types are reported.
             The keyword "#conref" indicates uses via @conref or @conkeyref

   format:   List of @format values by which the document is used, e.g. "dita", "ditamap",
             etc. If unspecified, uses are not filtered by @format value.

   scope:    List of @scope values to filter the uses by. If unspecified, uses are not
             filtered by @scope value.

   TBD: Need for direct/indirect filter, other filters.

   FIXME: Need to rationalize the coding pattern for use parameters and
          their use by the useRecordMatcher() function. The code is a little
          confused right now as a result of some quick refactoring.

 :)
declare function lmutil:getUses($elem as element(), $useParams) as element()* {

   let $linktypes := if ($useParams('linktype'))
                        then $useParams('linktype')
                        else ('#any')
   let $formats   := if ($useParams('format'))
                        then $useParams('format')
                        else ('#any')
   let $scopes   := if ($useParams('scope'))
                        then $useParams('scope')
                        else ('#any')

   (: Note that all references are ultimately to elements,
      so resource keys are always for elements, not
      documents. Given an element we can always get its
      containing document.

      In DITA, except for <dita> documents, references to
      documents with no fragment identifier are implicitly
      to the root elements of those documents (i.e., a map
      or topic element).

      The resource key here is used as the name of the
      directory that contains the where-used records for
      this element.
    :)
   let $resKey := lmutil:constructResourceKeyForElement($elem)

   (: Now find all use records for the resource key that match the filter
      specification.

      :)
    let $dbName := bxutil:getMetadataDbNameForDoc(root($elem))
    let $collection := $dbName || $dfstcnst:where-used-dir || '/' || $resKey
    let $records := collection($collection)
                       /dfst:useRecord[lmutil:useRecordMatcher(., $linktypes, $formats, $scopes)]
    return $records

};

(: Given an element with an @id value, construct the unique resource key for it.
   For a given element the resource ID is guaranteed to be unique within
   a snapshot (git commit).

   @param elem Element to get resource ID for.
   @returns Resource key string.

   The resource key is a combination of the absolute URI of the containing
   document and the element's tree position within the document, producing
   a unique key for any element in any document on a given snapshot (version
   in time). Resource keys are not reliably unique across snapshots as the position of
   the element within its containing document could change from version to
   version.

   The resource key is used to look up the element in where-used records, either
   as the element used or the element doing the use (links).

 :)
declare function lmutil:constructResourceKeyForElement($elem as element()) as xs:string {
  let $targetDocHash := hash:md5(document-uri(root($elem)))
  let $treepos := for $anc in ($elem/ancestor-or-self::*)
                      return string(count($anc | $anc/preceding-sibling::*))
  let $key := string-join($treepos, '.')
  return $targetDocHash || '^' || $key

};

(:~
 : Determines if a given where-used record matches the filter
 : specified parameters.
 :
 : FIXME: Use a map to pass the filter parameters rather than
 : individual arguments.
 :)
declare function lmutil:useRecordMatcher($record as element(),
                                         $linktypes as xs:string*,
                                         $formats as xs:string*,
                                         $scopes as xs:string*) as xs:boolean {
   let $result := ((if ($linktypes = '#any')
                       then true()
                       else string($record/@linkType) = $linktypes) and
                   (if ($formats = '#any')
                       then true()
                       else string($record/@format) = $formats) and
                   (if ($scopes = '#any')
                       then true()
                       else string($record/@scope) = $scopes))
  return $result
};

(:~
 : Given a link element, determines the linkContext value for it.
 :)
declare function lmutil:getLinkContext($link as element()) as xs:string {
  if (df:class($link, 'map/topicref'))
     then if ($link/ancestor::*[df:class(., 'map/reltable')])
             then 'reltable'
          else if ($link/@processing-role = ('resource-only'))
               then 'resources'
          else 'navtree'
     else if ($link/ancestor::*[df:class(., 'topic/title')])
          then 'title'
     else if ($link/ancestor::*[df:class(., 'topic/shortdesc')])
          then 'shortdesc'
     else if ($link/ancestor::*[df:class(., 'topic/abstract')])
          then 'abstract'
     else if ($link/ancestor::*[df:class(., 'topic/prolog') or df:class(., 'map/topicmeta')])
          then 'prolog'
     else if ($link/ancestor::*[df:class(., 'topic/body')])
          then 'body'
     else 'unknown-context'

};

(:~
 : Given a link item map, attempts to resolve the link to an element (in DITA a given link
 : can address at most one element).
 :
 : Returns a map with the following members:
 : 'target': A sequence of zero or more elements addressed
 :           by the link.
 : 'log':    A sequence of zero or more log entry elements
 :           generated by the resolution attempt.
 :)
declare function lmutil:resolveDirectLink($linkItem as map(*)) as map(*) {

   let $link as element()? := $linkItem('link')
   return if ($link)
      then
       let $resultMap := if (df:class($link, 'map/topicref') and ($link/@href))
                            then df:resolveTopicRef($link)
                            else df:resolveNonTopicRefDirectLink($link)

       let $targets := $resultMap('target')
       let $log := (<info>Link: {
                   concat('<',
                          name($link), ' ',
                          lmutil:reportAtts($link, ('href', 'keyref', 'keys', 'conref', 'scope')),
                          '>')} [class: "{string($link/@class)}"], doc: "{document-uri(root($link))}"</info>,
                  if ($targets)
                     then <info>  Link resolved</info>
                     else $resultMap('log')
                  )
       return map{'target' : $targets, 'log' : $log, 'link' : $linkItem}
     else
       map{'target' : (),
                 'log' : (),
                 'link' : $linkItem
                }
};

(: Given a link descriptor map and the database that contains it, attempts
   to resolve the link to an element (in DITA a given link
   can address at most one element).

   Returns a map with the following members:
   'target': A sequence of zero or more elements addressed
             by the link.
   'log':    A sequence of zero or more log entry elements
             generated by the resolution attempt.
   'link':   The input link item.
 :)
declare function lmutil:resolveIndirectLink($linkItem as map(*)) as map(*) {

   (: FIXME: Implement this function :)
   let $targets := ()
   let $log := ()
   return map{'target' : $targets, 'log' : $log , 'link' : $linkItem}
};

(:~
 : Given a database, finds all the indirect links. Returns a map
 : the the members:
 : 'links' : A sequence of maps, where each map represents one link.
 : 'log' : A sequence of log entry elements.
 :
 : Each link map has the following items:
 :
 : 'link' : The element that is the link
 : 'rootMap' : The root map document that defines the namespace the link
 :             is resolved in.
 : 'resolvedMap' : The fully-resolved map that defines key space the key
 :                 reference is resolved in.
 : 'keySpace' : The constructed key space for the root map.
 :
 :)
declare function lmutil:findAllIndirectLinks($dbName) as map(*)* {
   (:

1. Find all root maps (maps with no local-scope topicref references or
   maps with peer topicref references). This requires that the direct-reference
   use records are up to date. It also requires that the resolved map and key space
   documents have already been created.

2. For each root map, Walk the resolved map, creating link items for each key-based topicref.
   Resolve topicrefs to topics and process each topic to create link items for each key-based
   link in each topic.

   :)

   let $rootMaps as element()* := lmutil:getRootMaps($dbName)

   let $linksFromTopics :=
       for $map in $rootMaps
           let $resolvedMap := lmutil:getResolvedMapForMap($map)
           let $bos as map(*)* := lmutil:getMapBoundedObjectSet($resolvedMap, ('dita'))
           return for $member in $bos
              return lmutil:getIndirectLinks($bos)

    let $links := $linksFromTopics

(:   let $links := map{ 'link' : (),
                      'resolvedMap' : (),
                    }
:)
   return $links
};

(:~
 : Takes a sequence of map BOS members and, for each member,
 : finds the key-based links in the member and returns a of link
 : structures reflecting those links.
 :
 : @param bos Sequence of maps, one for each use of a topic from a
 : DITA map. Each map has two members, "topicref", and "resource"
 : @return A sequence, possibly empty, of link maps.
 :)
declare function lmutil:getIndirectLinks($bos as map(*)) as map(*)* {
   let $result as map(*)* :=
       for $member in $bos
           return lmutil:getIndirectLinksForBOSMember($member)
   return $result
};

(:~
 : Given a BOS member map for a topic, finds all key-based links in the
 : topic and returns a sequence of link items for the links.
 :
 : @param member BOS member map containing a topic BOS member.
 : @return A sequence, possibly empty, of link items.
 :)
declare function lmutil:getIndirectLinksForBOSMember($member as map(*)) as map(*)* {
   let $topicref := $member('topicref')
   let $topic := $member('resource')
   let $linkElems := $topic//*[@keyref]
   let $linkItems :=
       for $elem in $linkElems
           return lmutil:constructLinkItemMap($elem, $topicref)
   return $linkItems
};

(:~
 : Given a map resolves all the topicrefs to their local-scope resources
 : and returns the set of "BOS members", where each member is a map
 : with the topicref that pointed to the topic and the resolved topic.
 : The topicref is required in order to have the map context, which is required
 : in order to determine the key space within which any key references within the
 : topic are resolved. Note that a BOS member is a unique topicref/resource pair.
 : If the same resource is referenced multiple times in the map there will be one BOS member
 : for each reference.
 :
 : @param map Map to process. Does not resolve submap references, so if
 : you want the resources for a map tree supply the resolved map.
 :
 : @param formats A list of one or more @format values to return in the set.
 : The special value '#non-dita' matches any @format value that is not 'dita'
 : or 'dita-map'. Specify ('dita') to return only topic members of
 : the BOS. Non-DITA resources are represented by the topicrefs that point to
 : them. If not specified, all local-scope resources are returned.
 : @return Sequence of maps, one for each topicref/resource pair
 :)
declare function lmutil:getMapBoundedObjectSet(
                            $map as element(),
                            $formats as xs:string*) as map(*)* {
  let $topicrefs := $map//*[df:isTopicRef(.)]
                           [df:isLocalScope(.)]
                           [not($formats) or
                            df:getEffectiveFormat(.) = $formats]
  let $members :=
      for $topicref in $topicrefs
          let $resource := if (df:getEffectiveFormat($topicref) = ('dita'))
             then lmutil:resolveTopicRefFromResolvedMap($topicref)
             else $topicref
          return map { 'topicref' : $topicref,
                       'resource' : $resource }
  return $members

};

(:~
 : Resolves a topicref from a resolved map. Uses the additional metadata
 : in the resolved map to determine the target document that contains
 : the element.
 :)
declare function lmutil:resolveTopicRefFromResolvedMap($topicref as element())
                                                                    as element()? {
  let $baseURI := string(($topicref/ancestor::*[@origMapURI]/@origMapURI)[1])
  let $targetURI := string($topicref/@href)
  let $resolvedURI := relpath:resolveURI($targetURI, $baseURI)
  let $result := doc($resolvedURI)/*
  return $result
};

(:~
 : Find maps that either have no local-scope topicrefs to them or that have
 : any peer-scope topicrefs to them.
 :
 : @param dbName Name of the content database that holds the map to examine.
 : @return List, possibly empty, of root maps.
 :)
declare function lmutil:getRootMaps($dbName as xs:string) as element()* {
  let $candMaps := collection($dbName)/*[df:class(., 'map/map')]
  let $result :=
      for $candMap in $candMaps
          return if (lmutil:isRootMap($candMap))
                   then $candMap
                   else ()
  return $result
};

(:~
 : Returns true if the specified map element is a root map.
 :
 : Note that there are many ways that root mapness could be determined.
 : The logic used here is any map that has no direct map local-scope references
 : or any peer-scope references. This is based on the presumption that a root
 : map would not normally be useable as local-scope submap. DITA 1.3's new
 : meaning for scope="peer" on topicrefs to maps explicitly means "the referenced
 : map is a root map".
 :
 : Other options would be to have a master map that uses peer map references to
 : explicitly identify root maps and use only that to determine rootness, to
 : maintain some sort of metadata that indicates rootness, use a specific map
 : specialization or map specialization plus descendant element configuration
 : (e.g., bookmaps that have booktitle elements are root maps, all others are
 : not).
 :
 : @param map Map to check for rootness
 : @return true() if the map is a root map.
 :)
declare function lmutil:isRootMap($map as element()) as xs:boolean {
  let $result := ((lmutil:getRefCount($map, $lmutil:useMatchParamsLocalMaps) = 0) or
                  (lmutil:getRefCount($map, $lmutil:useMatchParamsPeerMaps) > 0))
  return $result
};

(:~
 : Gets the number of references to the specified element that match the specified
 : match criteria.
 :
 : @param elem Element to get reference count for.
 : @param matcher Use record matcher to use for selecting references
 : @return Number of matches
 :)
declare function lmutil:getRefCount($elem as element(),
                                    $matchParams as map(*)) as xs:integer {
  let $uses := lmutil:getUses($elem, $matchParams)
  let $result := count($uses)
  return $result
};

(: Construct a string report of the listed attributes :)
declare function lmutil:reportAtts($elem as element(), $attNames) as xs:string {
   let $result := for $att in $elem/@*
                      return if (name($att) = $attNames)
                                then concat(name($att), '="', string($att), '"')
                                else ()
   return string-join($result, ' ')
};

(:~
 : Get the resolved map document for a map. The resolved map must have already
 : been constructed (XQuery doesn't let use do updating actions wherever we
 : want).
 :
 : @param map Content map to get the resolved map for.
 : @return the resolved map or an empty sequence if there is no resolved map
 : (for example, because the map is not a root map).
 :)
declare function lmutil:getResolvedMapForMap($map as element()) as element()? {
  let $resolvedMapURI := lmutil:getResolvedMapURIForMap($map)
  let $metadataDbName := bxutil:getMetadataDbNameForDoc(root($map))

  let $resolvedMap := collection($metadataDbName || $resolvedMapURI)
  return $resolvedMap
};

(:~
 : Construct the database URI to use for a resolved map.
 :
 : @param map DITA map element to get the URI for
 : @returns The URI of the resolved map as a string
 :)
declare function lmutil:getResolvedMapURIForMap(
                        $map as element()) as xs:string {
  let $mapDocHash := hash:md5(document-uri(root($map)))
  return $dfstcnst:resolved-map-dir ||
         "/" || $mapDocHash || ".ditamap"
};

(: End of Module :)

(: =====================================================

   DITA Link Management Utilities

   Utilities that support general DITA link management
   actions.

   Author: W. Eliot Kimber

   Copyright (c) 2015 DITA For Small Teams
   Licensed under Apache License 2

   ===================================================== :)

module namespace lmutil="http://dita-for-small-teams.org/xquery/modules/linkmgmt-utils";

declare namespace dfst="http://dita-for-small-teams.org";

import module namespace df="http://dita-for-small-teams.org/xquery/modules/dita-utils";
import module namespace bxutil="http://dita-for-small-teams.org/xquery/modules/basex-utils";
import module namespace dfstcnst="http://dita-for-small-teams.org/xquery/modules/dfst-constants";
import module namespace relpath="http://dita-for-small-teams.org/xquery/modules/relpath-utils";

declare variable $lmutil:useMatchParamsLocalMaps :=
                               map{'linktype' :'topicref',
                                   'format' : 'ditamap',
                                   'scope' : 'local'
                                  };
declare variable $lmutil:useMatchParamsPeerMaps :=
                               map{'linktype' :'topicref',
                                   'format' : 'ditamap',
                                   'scope' : 'peer'
                                  };

(:~
 : Find all links that use direct URI references to their target resources.
 : Returns list of link item maps
 :
 : Each link item map has the following items:
 :
 : 'link' : The element that is the link
 : 'rootMap' : The root map document that defines the namespace the link
 :             is resolved in.
 : 'resolvedMap' : The fully-resolved map that defines key space the key
 :                 reference is resolved in.
 : 'keySpace' : The constructed key space for the root map.
 : 'processingRole' : For topicrefs, the @processing-role value. For
 :                    other links, 'normal'.
 : 'linkContext': The relevant container type for the link, one of:
 :                - 'title' : Link occurs within a title element
 :                - 'prolog' : Link occurs with topicmeta (maps) or prolog (topics)
 :                - 'shortdesc' : Link occurs within a short description
 :                - 'abstract' : Link occurs within an abstract (but not within a
 :                               short description within an abstract)
 :                - 'body' : Link occurs within a topic body
 :                - 'reltable' : Link is a topicref within a relationship table.
 :                - 'navtree' : Link is a topicref with @processing-role of 'normal' and
 :                              is not within a relationship table
 :                - 'resources' : Link is a topicref with a processing role of "resource-only".
 :  'isDirect' : - 'true' if link is a direct URI reference
 :               - 'false' if link uses a resolvable key reference
 :
 : For direct links the link element is sufficient
 : to allow resolution as use context has no affect on how the link is resolved.
 : For "." ("this topic") fragment IDs, the context is always the containing topic.
 :
 : @param dbName Name of the database that contains the documents to be processed.
 : @param logID ID of the log to put log messages in.
 :)
declare function lmutil:findAllDirectLinks($dbName) as map(*)* {
  let $db := db:open($dbName)
  (: First do direct URI references, which don't require any
     context knowledge:
   :)
  let $links := collection($dbName)//*[df:isTopicRef(.) and not(@keyref)] |
                collection($dbName)//*[contains(@class, ' topic/xref ') and (@href and not(@keyref))] |
                collection($dbName)//*[contains(@class, ' topic/data-about ') and (@href and not(@keyref))] |
                collection($dbName)//*[contains(@class, ' topic/longdescref ') and (@href and not(@keyref))] |
                collection($dbName)//*[@conref]
   (: Construct a link item map for each link element :)
   for $link in $links
       return map{'link': $link,
                  'rootMap': (),
                  'resolvedMap': (),
                  'keySpace': (),
                  'processingRole':
                     if (df:isTopicRef($link))
                        then if ($link/@processing-role)
                                then string($link/@processing-role)
                                else 'normal' (: Default for topicref :)
                        else 'normal', (: non-topicref links :)
                  'linkContext': lmutil:getLinkContext($link)
                 }
};

(: Give a document, finds all references to that document that match the
   type of uses as configured in the $useParams.

   @param doc Document to find uses of.
   @param useParams Use filter parameters. Only uses that match the parameters
                    will be reported.
   @return Zero or more use record elements.

   The use parameters are:

   linktype: List of base type names (e.g., 'topicref, xref) or qualified
             class names (e.g., 'map-d/navref') of the types of links
             to report usage for. If unspecifed, all link types are reported.
             The keyword "#conref" indicates uses via @conref or @conkeyref

   format:   List of @format values by which the document is used, e.g. "dita", "ditamap",
             etc. If unspecified, uses are not filtered by @format value.

   scope:    List of @scope values to filter the uses by. If unspecified, uses are not
             filtered by @scope value.

   TBD: Need for direct/indirect filter, other filters.

   FIXME: Need to rationalize the coding pattern for use parameters and
          their use by the useRecordMatcher() function. The code is a little
          confused right now as a result of some quick refactoring.

 :)
declare function lmutil:getUses($elem as element(), $useParams) as element()* {

   let $linktypes := if ($useParams('linktype'))
                        then $useParams('linktype')
                        else ('#any')
   let $formats   := if ($useParams('format'))
                        then $useParams('format')
                        else ('#any')
   let $scopes   := if ($useParams('scope'))
                        then $useParams('scope')
                        else ('#any')

   (: Note that all references are ultimately to elements,
      so resource keys are always for elements, not
      documents. Given an element we can always get its
      containing document.

      In DITA, except for <dita> documents, references to
      documents with no fragment identifier are implicitly
      to the root elements of those documents (i.e., a map
      or topic element).

      The resource key here is used as the name of the
      directory that contains the where-used records for
      this element.
    :)
   let $resKey := lmutil:constructResourceKeyForElement($elem)

   (: Now find all use records for the resource key that match the filter
      specification.

      :)
    let $dbName := bxutil:getMetadataDbNameForDoc(root($elem))
    let $collection := $dbName || $dfstcnst:where-used-dir || '/' || $resKey
    let $records := collection($collection)
                       /dfst:useRecord[lmutil:useRecordMatcher(., $linktypes, $formats, $scopes)]
    return $records

};

(: Given an element with an @id value, construct the unique resource key for it.
   For a given element the resource ID is guaranteed to be unique within
   a snapshot (git commit).

   @param elem Element to get resource ID for.
   @returns Resource key string.

   The resource key is a combination of the absolute URI of the containing
   document and the element's tree position within the document, producing
   a unique key for any element in any document on a given snapshot (version
   in time). Resource keys are not reliably unique across snapshots as the position of
   the element within its containing document could change from version to
   version.

   The resource key is used to look up the element in where-used records, either
   as the element used or the element doing the use (links).

 :)
declare function lmutil:constructResourceKeyForElement($elem as element()) as xs:string {
  let $targetDocHash := hash:md5(document-uri(root($elem)))
  let $treepos := for $anc in ($elem/ancestor-or-self::*)
                      return string(count($anc | $anc/preceding-sibling::*))
  let $key := string-join($treepos, '.')
  return $targetDocHash || '^' || $key

};

(:~
 : Determines if a given where-used record matches the filter
 : specified parameters.
 :
 : FIXME: Use a map to pass the filter parameters rather than
 : individual arguments.
 :)
declare function lmutil:useRecordMatcher($record as element(),
                                         $linktypes as xs:string*,
                                         $formats as xs:string*,
                                         $scopes as xs:string*) as xs:boolean {
   let $result := ((if ($linktypes = '#any')
                       then true()
                       else string($record/@linkType) = $linktypes) and
                   (if ($formats = '#any')
                       then true()
                       else string($record/@format) = $formats) and
                   (if ($scopes = '#any')
                       then true()
                       else string($record/@scope) = $scopes))
  return $result
};

(:~
 : Given a link element, determines the linkContext value for it.
 :)
declare function lmutil:getLinkContext($link as element()) as xs:string {
  if (df:class($link, 'map/topicref'))
     then if ($link/ancestor::*[df:class(., 'map/reltable')])
             then 'reltable'
          else if ($link/@processing-role = ('resource-only'))
               then 'resources'
          else 'navtree'
     else if ($link/ancestor::*[df:class(., 'topic/title')])
          then 'title'
     else if ($link/ancestor::*[df:class(., 'topic/shortdesc')])
          then 'shortdesc'
     else if ($link/ancestor::*[df:class(., 'topic/abstract')])
          then 'abstract'
     else if ($link/ancestor::*[df:class(., 'topic/prolog') or df:class(., 'map/topicmeta')])
          then 'prolog'
     else if ($link/ancestor::*[df:class(., 'topic/body')])
          then 'body'
     else 'unknown-context'

};

(:~
 : Given a link item map, attempts to resolve the link to an element (in DITA a given link
 : can address at most one element).
 :
 : Returns a map with the following members:
 : 'target': A sequence of zero or more elements addressed
 :           by the link.
 : 'log':    A sequence of zero or more log entry elements
 :           generated by the resolution attempt.
 :)
declare function lmutil:resolveDirectLink($linkItem as map(*)) as map(*) {

   let $link as element()? := $linkItem('link')
   return if ($link)
      then
       let $resultMap := if (df:class($link, 'map/topicref') and ($link/@href))
                            then df:resolveTopicRef($link)
                            else df:resolveNonTopicRefDirectLink($link)

       let $targets := $resultMap('target')
       let $log := (<info>Link: {
                   concat('<',
                          name($link), ' ',
                          lmutil:reportAtts($link, ('href', 'keyref', 'keys', 'conref', 'scope')),
                          '>')} [class: "{string($link/@class)}"], doc: "{document-uri(root($link))}"</info>,
                  if ($targets)
                     then <info>  Link resolved</info>
                     else $resultMap('log')
                  )
       return map{'target' : $targets, 'log' : $log, 'link' : $linkItem}
     else
       map{'target' : (),
                 'log' : (),
                 'link' : $linkItem
                }
};

(: Given a link descriptor map and the database that contains it, attempts
   to resolve the link to an element (in DITA a given link
   can address at most one element).

   Returns a map with the following members:
   'target': A sequence of zero or more elements addressed
             by the link.
   'log':    A sequence of zero or more log entry elements
             generated by the resolution attempt.
   'link':   The input link item.
 :)
declare function lmutil:resolveIndirectLink($linkItem as map(*)) as map(*) {

   (: FIXME: Implement this function :)
   let $targets := ()
   let $log := ()
   return map{'target' : $targets, 'log' : $log , 'link' : $linkItem}
};

(:~
 : Given a database, finds all the indirect links. Returns a map
 : the the members:
 : 'links' : A sequence of maps, where each map represents one link.
 : 'log' : A sequence of log entry elements.
 :
 : Each link map has the following items:
 :
 : 'link' : The element that is the link
 : 'rootMap' : The root map document that defines the namespace the link
 :             is resolved in.
 : 'resolvedMap' : The fully-resolved map that defines key space the key
 :                 reference is resolved in.
 : 'keySpace' : The constructed key space for the root map.
 :
 :)
declare function lmutil:findAllIndirectLinks($dbName) as map(*)* {
   (:

1. Find all root maps (maps with no local-scope topicref references or
   maps with peer topicref references). This requires that the direct-reference
   use records are up to date. It also requires that the resolved map and key space
   documents have already been created.

2. For each root map, Walk the resolved map, creating link items for each key-based topicref.
   Resolve topicrefs to topics and process each topic to create link items for each key-based
   link in each topic.

   :)

   let $rootMaps as element()* := lmutil:getRootMaps($dbName)

   let $linksFromTopics :=
       for $map in $rootMaps
           let $resolvedMap := lmutil:getResolvedMapForMap($map)
           let $bos as map(*)* := lmutil:getMapBoundedObjectSet($resolvedMap, ('dita'))
           return for $member in $bos
              return () (:lmm:getIndirectLinks:)

    let $links := $linksFromTopics

(:   let $links := map{ 'link' : (),
                      'rootMap' : (),
                      'resolvedMap' : (),
                      'keySpace' : ()
                    }
:)
   return $links
};

(:~
 : Given a map resolves all the topicrefs to their local-scope resources
 : and returns the set of "BOS members", where each member is a map
 : with the topicref that pointed to the topic and the resolved topic.
 : The topicref is required in order to have the map context, which is required
 : in order to determine the key space within which any key references within the
 : topic are resolved. Note that a BOS member is a unique topicref/resource pair.
 : If the same resource is referenced multiple times in the map there will be one BOS member
 : for each reference.
 :
 : @param map Map to process. Does not resolve submap references, so if
 : you want the resources for a map tree supply the resolved map.
 :
 : @param formats A list of one or more @format values to return in the set.
 : The special value '#non-dita' matches any @format value that is not 'dita'
 : or 'dita-map'. Specify ('dita') to return only topic members of
 : the BOS. Non-DITA resources are represented by the topicrefs that point to
 : them. If not specified, all local-scope resources are returned.
 : @return Sequence of maps, one for each topicref/resource pair
 :)
declare function lmutil:getMapBoundedObjectSet(
                            $map as element(),
                            $formats as xs:string*) as map(*)* {
  let $topicrefs := $map//*[df:isTopicRef(.)]
                           [df:isLocalScope(.)]
                           [not($formats) or
                            df:getEffectiveFormat(.) = $formats]
  let $members :=
      for $topicref in $topicrefs
          let $resource := if (df:getEffectiveFormat($topicref) = ('dita'))
             then lmutil:resolveTopicRefFromResolvedMap($topicref)
             else $topicref
          return map { 'topicref' : $topicref,
                       'resource' : $resource }
  return $members

};

(:~
 : Resolves a topicref from a resolved map. Uses the additional metadata
 : in the resolved map to determine the target document that contains
 : the element.
 :)
declare function lmutil:resolveTopicRefFromResolvedMap($topicref as element())
                                                                    as element()? {
  let $baseURI := string(($topicref/ancestor::*[@origMapURI]/@origMapURI)[1])
  let $targetURI := string($topicref/@href)
  let $resolvedURI := relpath:resolveURI($targetURI, $baseURI)
  let $result := doc($resolvedURI)/*
  return $result
};

(:~
 : Find maps that either have no local-scope topicrefs to them or that have
 : any peer-scope topicrefs to them.
 :
 : @param dbName Name of the content database that holds the map to examine.
 : @return List, possibly empty, of root maps.
 :)
declare function lmutil:getRootMaps($dbName as xs:string) as element()* {
  let $candMaps := collection($dbName)/*[df:class(., 'map/map')]
  let $result :=
      for $candMap in $candMaps
          return if (lmutil:isRootMap($candMap))
                   then $candMap
                   else ()
  return $result
};

(:~
 : Returns true if the specified map element is a root map.
 :
 : Note that there are many ways that root mapness could be determined.
 : The logic used here is any map that has no direct map local-scope references
 : or any peer-scope references. This is based on the presumption that a root
 : map would not normally be useable as local-scope submap. DITA 1.3's new
 : meaning for scope="peer" on topicrefs to maps explicitly means "the referenced
 : map is a root map".
 :
 : Other options would be to have a master map that uses peer map references to
 : explicitly identify root maps and use only that to determine rootness, to
 : maintain some sort of metadata that indicates rootness, use a specific map
 : specialization or map specialization plus descendant element configuration
 : (e.g., bookmaps that have booktitle elements are root maps, all others are
 : not).
 :
 : @param map Map to check for rootness
 : @return true() if the map is a root map.
 :)
declare function lmutil:isRootMap($map as element()) as xs:boolean {
  let $result := ((lmutil:getRefCount($map, $lmutil:useMatchParamsLocalMaps) = 0) or
                  (lmutil:getRefCount($map, $lmutil:useMatchParamsPeerMaps) > 0))
  return $result
};

(:~
 : Gets the number of references to the specified element that match the specified
 : match criteria.
 :
 : @param elem Element to get reference count for.
 : @param matcher Use record matcher to use for selecting references
 : @return Number of matches
 :)
declare function lmutil:getRefCount($elem as element(),
                                    $matchParams as map(*)) as xs:integer {
  let $uses := lmutil:getUses($elem, $matchParams)
  let $result := count($uses)
  return $result
};

(: Construct a string report of the listed attributes :)
declare function lmutil:reportAtts($elem as element(), $attNames) as xs:string {
   let $result := for $att in $elem/@*
                      return if (name($att) = $attNames)
                                then concat(name($att), '="', string($att), '"')
                                else ()
   return string-join($result, ' ')
};

(:~
 : Get the resolved map document for a map. The resolved map must have already
 : been constructed (XQuery doesn't let use do updating actions wherever we
 : want).
 :
 : @param map Content map to get the resolved map for.
 : @return the resolved map or an empty sequence if there is no resolved map
 : (for example, because the map is not a root map).
 :)
declare function lmutil:getResolvedMapForMap($map as element()) as element()? {
  let $resolvedMapURI := lmutil:getResolvedMapURIForMap($map)
  let $metadataDbName := bxutil:getMetadataDbNameForDoc(root($map))

  let $resolvedMap := collection($metadataDbName || $resolvedMapURI)
  return $resolvedMap
};

(:~
 : Construct the database URI to use for a resolved map.
 :
 : @param map DITA map element to get the URI for
 : @returns The URI of the resolved map as a string
 :)
declare function lmutil:getResolvedMapURIForMap(
                        $map as element()) as xs:string {
  let $mapDocHash := hash:md5(document-uri(root($map)))
  return $dfstcnst:resolved-map-dir ||
         "/" || $mapDocHash || ".ditamap"
};

(: End of Module :)


removing invocation script from repository, it is generated dynamically based on configuration

xquery version "1.0";

import module namespace fcs  = "http://clarin.eu/fcs/1.0" at "fcs.xqm";

let $config := 'etc/config_mdrepo.xml'

return fcs:repo($config)