(:~ : -------------------------------- : The FunctX XQuery Function Library : -------------------------------- : Copyright (C) 2007 Datypic : This library is free software; you can redistribute it and/or : modify it under the terms of the GNU Lesser General Public : License as published by the Free Software Foundation; either : version 2.1 of the License. : This library is distributed in the hope that it will be useful, : but WITHOUT ANY WARRANTY; without even the implied warranty of : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU : Lesser General Public License for more details. : You should have received a copy of the GNU Lesser General Public : License along with this library; if not, write to the Free Software : Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA : For more information on the FunctX XQuery library, contact contrib@functx.com. : @version 1.0 : @see http://www.xqueryfunctions.com :) module "http://www.functx.com" declare namespace functx = "http://www.functx.com" define function add-attributes ( $elements as element()* , $attrNames as xs:QName* , $attrValues as xdt:anyAtomicType* ) as element()? { for $element in $elements return element { fn:node-name($element)} { for $attrName at $seq in $attrNames return if ($element/@*[fn:node-name(.) = $attrName]) then () else attribute {$attrName} {$attrValues[$seq]}, $element/@*, $element/node() } } define function add-months ( $date as xdt:anyAtomicType? , $months as xs:integer ) as xs:date? { xs:date($date) + functx:yearMonthDuration(0,$months) } define function add-or-update-attributes ( $elements as element()* , $attrNames as xs:QName* , $attrValues as xdt:anyAtomicType* ) as element()? { for $element in $elements return element { fn:node-name($element)} { for $attrName at $seq in $attrNames return attribute {$attrName} {$attrValues[$seq]}, $element/@*[fn:not(fn:node-name(.) = $attrNames)], $element/node() } } define function all-whitespace ( $arg as xs:string? ) as xs:boolean { fn:normalize-space($arg) = '' } define function are-distinct-values ( $seq as xdt:anyAtomicType* ) as xs:boolean { fn:count(fn:distinct-values($seq)) = fn:count($seq) } define function atomic-type ( $values as xdt:anyAtomicType* ) as xs:string* { for $val in $values return (if ($val instance of xdt:untypedAtomic) then 'xdt:untypedAtomic' else if ($val instance of xs:anyURI) then 'xs:anyURI' else if ($val instance of xs:ENTITY) then 'xs:ENTITY' else if ($val instance of xs:ID) then 'xs:ID' else if ($val instance of xs:NMTOKEN) then 'xs:NMTOKEN' else if ($val instance of xs:language) then 'xs:language' else if ($val instance of xs:NCName) then 'xs:NCName' else if ($val instance of xs:Name) then 'xs:Name' else if ($val instance of xs:token) then 'xs:token' else if ($val instance of xs:normalizedString) then 'xs:normalizedString' else if ($val instance of xs:string) then 'xs:string' else if ($val instance of xs:QName) then 'xs:QName' else if ($val instance of xs:boolean) then 'xs:boolean' else if ($val instance of xs:base64Binary) then 'xs:base64Binary' else if ($val instance of xs:hexBinary) then 'xs:hexBinary' else if ($val instance of xs:byte) then 'xs:byte' else if ($val instance of xs:short) then 'xs:short' else if ($val instance of xs:int) then 'xs:int' else if ($val instance of xs:long) then 'xs:long' else if ($val instance of xs:unsignedByte) then 'xs:unsignedByte' else if ($val instance of xs:unsignedShort) then 'xs:unsignedShort' else if ($val instance of xs:unsignedInt) then 'xs:unsignedInt' else if ($val instance of xs:unsignedLong) then 'xs:unsignedLong' else if ($val instance of xs:positiveInteger) then 'xs:positiveInteger' else if ($val instance of xs:nonNegativeInteger) then 'xs:nonNegativeInteger' else if ($val instance of xs:negativeInteger) then 'xs:negativeInteger' else if ($val instance of xs:nonPositiveInteger) then 'xs:nonPositiveInteger' else if ($val instance of xs:integer) then 'xs:integer' else if ($val instance of xs:decimal) then 'xs:decimal' else if ($val instance of xs:float) then 'xs:float' else if ($val instance of xs:double) then 'xs:double' else if ($val instance of xs:date) then 'xs:date' else if ($val instance of xs:time) then 'xs:time' else if ($val instance of xs:dateTime) then 'xs:dateTime' else if ($val instance of xdt:dayTimeDuration) then 'xdt:dayTimeDuration' else if ($val instance of xdt:yearMonthDuration) then 'xdt:yearMonthDuration' else if ($val instance of xs:duration) then 'xs:duration' else if ($val instance of xs:gMonth) then 'xs:gMonth' else if ($val instance of xs:gYear) then 'xs:gYear' else if ($val instance of xs:gYearMonth) then 'xs:gYearMonth' else if ($val instance of xs:gDay) then 'xs:gDay' else if ($val instance of xs:gMonthDay) then 'xs:gMonthDay' else 'unknown') } define function avg-empty-is-zero ( $values as xdt:anyAtomicType* , $allNodes as node()* ) as xs:double { if (fn:empty($allNodes)) then 0 else fn:sum($values[fn:string(.) != '']) div fn:count($allNodes) } define function between-exclusive ( $value as xdt:anyAtomicType? , $minValue as xdt:anyAtomicType , $maxValue as xdt:anyAtomicType ) as xs:boolean { $value > $minValue and $value < $maxValue } define function between-inclusive ( $value as xdt:anyAtomicType? , $minValue as xdt:anyAtomicType , $maxValue as xdt:anyAtomicType ) as xs:boolean { $value >= $minValue and $value <= $maxValue } define function camel-case-to-words ( $arg as xs:string? , $delim as xs:string ) as xs:string { fn:concat(fn:substring($arg,1,1), fn:replace(fn:substring($arg,2),'(\p{Lu})', fn:concat($delim, '$1'))) } define function capitalize-first ( $arg as xs:string? ) as xs:string? { fn:concat(fn:upper-case(fn:substring($arg,1,1)), fn:substring($arg,2)) } define function change-element-names-deep ( $nodes as node()* , $oldNames as xs:QName* , $newNames as xs:QName* ) as node()* { if (fn:count($oldNames) != fn:count($newNames)) then fn:error(xs:QName('functx:Different_number_of_names')) else for $node in $nodes return if ($node instance of element()) then element {functx:if-empty ($newNames[fn:index-of($oldNames, fn:node-name($node))], fn:node-name($node)) } {$node/@*, functx:change-element-names-deep($node/node(), $oldNames, $newNames)} else if ($node instance of document-node()) then functx:change-element-names-deep($node/node(), $oldNames, $newNames) else $node } define function change-element-ns-deep ( $nodes as node()* , $newns as xs:string , $prefix as xs:string ) as node()* { for $node in $nodes return if ($node instance of element()) then (element {fn:expanded-QName ($newns, fn:concat($prefix, if ($prefix = '') then '' else ':', fn:local-name($node)))} {$node/@*, functx:change-element-ns-deep($node/node(), $newns, $prefix)}) else if ($node instance of document-node()) then functx:change-element-ns-deep($node/node(), $newns, $prefix) else $node } define function change-element-ns ( $elements as element()* , $newns as xs:string , $prefix as xs:string ) as element()? { for $element in $elements return element {fn:expanded-QName ($newns, fn:concat($prefix, if ($prefix = '') then '' else ':', fn:local-name($element)))} {$element/@*, $element/node()} } define function chars ( $arg as xs:string? ) as xs:string* { for $ch in fn:string-to-codepoints($arg) return fn:codepoints-to-string($ch) } define function contains-any-of ( $arg as xs:string? , $searchStrings as xs:string* ) as xs:boolean { some $searchString in $searchStrings satisfies fn:contains($arg,$searchString) } define function contains-case-insensitive ( $arg as xs:string? , $substring as xs:string ) as xs:boolean? { fn:contains(fn:upper-case($arg), fn:upper-case($substring)) } define function contains-word ( $arg as xs:string? , $word as xs:string ) as xs:boolean { fn:matches(fn:upper-case($arg), fn:concat('^(.*\W)?', fn:upper-case(functx:escape-for-regex($word)), '(\W.*)?$')) } define function copy-attributes ( $copyTo as element() , $copyFrom as element() ) as element() { element { fn:node-name($copyTo)} { $copyTo/@* [fn:not(fn:node-name(.) = (for $n in $copyFrom/@* return fn:node-name($n)))], $copyFrom/@*, $copyTo/node() } } define function date ( $year as xdt:anyAtomicType , $month as xdt:anyAtomicType , $day as xdt:anyAtomicType ) as xs:date { xs:date( fn:concat( functx:pad-integer-to-length(xs:integer($year),4),'-', functx:pad-integer-to-length(xs:integer($month),2),'-', functx:pad-integer-to-length(xs:integer($day),2))) } define function dateTime ( $year as xdt:anyAtomicType , $month as xdt:anyAtomicType , $day as xdt:anyAtomicType , $hour as xdt:anyAtomicType , $minute as xdt:anyAtomicType , $second as xdt:anyAtomicType ) as xs:dateTime { xs:dateTime( fn:concat(functx:date($year,$month,$day),'T', functx:time($hour,$minute,$second))) } define function day-in-year ( $date as xdt:anyAtomicType? ) as xs:integer? { fn:get-days-from-dayTimeDuration( xs:date($date) - functx:first-day-of-year($date)) + 1 } define function day-of-week-abbrev-en ( $date as xdt:anyAtomicType? ) as xs:string? { ('Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat') [functx:day-of-week($date) + 1] } define function day-of-week-name-en ( $date as xdt:anyAtomicType? ) as xs:string? { ('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday') [functx:day-of-week($date) + 1] } define function day-of-week ( $date as xdt:anyAtomicType? ) as xs:integer? { if (fn:empty($date)) then () else xs:integer((xs:date($date) - xs:date('1901-01-06')) div xdt:dayTimeDuration('P1D')) mod 7 } define function dayTimeDuration ( $days as xs:decimal? , $hours as xs:decimal? , $minutes as xs:decimal? , $seconds as xs:decimal? ) as xdt:dayTimeDuration { (xdt:dayTimeDuration('P1D') * functx:if-empty($days,0)) + (xdt:dayTimeDuration('PT1H') * functx:if-empty($hours,0)) + (xdt:dayTimeDuration('PT1M') * functx:if-empty($minutes,0)) + (xdt:dayTimeDuration('PT1S') * functx:if-empty($seconds,0)) } define function days-in-month ( $date as xdt:anyAtomicType? ) as xs:integer? { if (fn:get-month-from-date(xs:date($date)) = 2 and functx:is-leap-year($date)) then 29 else (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31) [fn:get-month-from-date(xs:date($date))] } define function depth-of-node ( $node as node()? ) as xs:integer { fn:count($node/ancestor-or-self::node()) } define function distinct-attribute-names ( $nodes as node()* ) as xs:string* { fn:distinct-values(for $attr in $nodes//@* return fn:name($attr)) } define function distinct-deep ( $nodes as node()* ) as node()* { for $seq in (1 to fn:count($nodes)) return $nodes[$seq][fn:not(functx:is-node-in-sequence-deep-equal( .,$nodes[fn:position() < $seq]))] } define function distinct-element-names ( $nodes as node()* ) as xs:string* { fn:distinct-values(for $el in $nodes/descendant-or-self::* return fn:name($el)) } define function distinct-element-paths ( $nodes as node()* ) as xs:string* { fn:distinct-values(functx:path-to-node($nodes/descendant-or-self::*)) } define function distinct-nodes ( $nodes as node()* ) as node()* { for $seq in (1 to fn:count($nodes)) return $nodes[$seq][fn:not(functx:is-node-in-sequence( .,$nodes[fn:position() < $seq]))] } define function duration-from-timezone ( $timezone as xs:string ) as xdt:dayTimeDuration { xdt:dayTimeDuration( if (fn:not(fn:matches($timezone,'Z|[\+\-]\d{2}:\d{2}'))) then fn:error(xs:QName('functx:Invalid_Timezone_Value')) else if ($timezone = 'Z') then 'PT0S' else fn:replace($timezone,'\+?(\d{2}):\d{2}','PT$1H') ) } define function dynamic-path ( $parent as node() , $path as xs:string ) as item()* { let $nextStep := functx:substring-before-if-contains($path,'/') let $restOfSteps := fn:substring-after($path,'/') for $child in ($parent/*[functx:name-test(fn:name(),$nextStep)], $parent/@*[functx:name-test(fn:name(), fn:substring-after($nextStep,'@'))]) return if ($restOfSteps) then functx:dynamic-path($child, $restOfSteps) else $child } define function escape-for-regex ( $arg as xs:string? ) as xs:string { fn:replace($arg, '(\.|\[|\]|\\|\||\-|\^|\$|\?|\*|\+|\{|\}|\(|\))','\$1') } define function exclusive-or ( $arg1 as xs:boolean? , $arg2 as xs:boolean? ) as xs:boolean? { $arg1 != $arg2 } define function first-day-of-month ( $date as xdt:anyAtomicType? ) as xs:date? { functx:date(fn:get-year-from-date(xs:date($date)), fn:get-month-from-date(xs:date($date)), 1) } define function first-day-of-year ( $date as xdt:anyAtomicType? ) as xs:date? { functx:date(fn:get-year-from-date(xs:date($date)), 1, 1) } define function first-node ( $nodes as node()* ) as node()? { ($nodes/.)[1] } define function follows-not-descendant ( $a as node()? , $b as node()? ) as xs:boolean { $a >> $b and fn:empty($b intersect $a/ancestor::node()) } define function format-as-title-en ( $titles as xs:string* ) as xs:string* { let $wordsToMoveToEnd := ('A', 'An', 'The') for $title in $titles let $firstWord := functx:substring-before-match($title,'\W') return if ($firstWord = $wordsToMoveToEnd) then fn:replace($title,'(.*?)\W(.*)', '$2, $1') else $title } define function fragment-from-uri ( $uri as xs:string? ) as xs:string? { fn:substring-after($uri,'#') } define function get-matches-and-non-matches ( $string as xs:string? , $regex as xs:string ) as element()* { let $iomf := functx:index-of-match-first($string, $regex) return if (fn:empty($iomf)) then {$string} else if ($iomf > 1) then ({fn:substring($string,1,$iomf - 1)}, functx:get-matches-and-non-matches( fn:substring($string,$iomf),$regex)) else let $length := fn:string-length($string) - fn:string-length(functx:replace-first($string, $regex,'')) return ({fn:substring($string,1,$length)}, if (fn:string-length($string) > $length) then functx:get-matches-and-non-matches( fn:substring($string,$length + 1),$regex) else ()) } define function get-matches ( $string as xs:string? , $regex as xs:string ) as xs:string* { for $match in functx:get-matches-and-non-matches($string,$regex)/self::match return fn:string($match) } define function has-element-only-content ( $element as element() ) as xs:boolean { fn:not($element/text()[fn:normalize-space(.) != '']) and $element/* } define function has-empty-content ( $element as element() ) as xs:boolean { fn:not($element/node()) } define function has-mixed-content ( $element as element() ) as xs:boolean { $element/text()[fn:normalize-space(.) != ''] and $element/* } define function has-simple-content ( $element as element() ) as xs:boolean { $element/text() and fn:not($element/*) } define function id-from-element ( $element as element()? ) as xs:string? { fn:data(($element/@*[fn:id(.) is ..])[1]) } define function id-untyped ( $node as node()* , $id as xdt:anyAtomicType ) as element()* { $node//*[@* = $id] } define function if-absent ( $arg as item()* , $value as item()* ) as item()* { if (fn:exists($arg)) then $arg else $value } define function if-empty ( $arg as item()? , $value as item()* ) as item()* { if (fn:string($arg) != '') then fn:data($arg) else $value } define function index-of-deep-equal-node ( $nodes as node()* , $nodeToFind as node() ) as xs:integer* { for $seq in (1 to fn:count($nodes)) return $seq[fn:deep-equal($nodes[$seq],$nodeToFind)] } define function index-of-match-first ( $arg as xs:string? , $pattern as xs:string ) as xs:integer? { if (fn:matches($arg,$pattern)) then fn:string-length(fn:tokenize($arg, $pattern)[1]) + 1 else () } define function index-of-node ( $nodes as node()* , $nodeToFind as node() ) as xs:integer* { for $seq in (1 to fn:count($nodes)) return $seq[$nodes[$seq] is $nodeToFind] } define function index-of-string-first ( $arg as xs:string? , $substring as xs:string ) as xs:integer? { if (fn:contains($arg, $substring)) then fn:string-length(fn:substring-before($arg, $substring))+1 else () } define function index-of-string-last ( $arg as xs:string? , $substring as xs:string ) as xs:integer? { functx:index-of-string($arg, $substring)[fn:last()] } define function index-of-string ( $arg as xs:string? , $substring as xs:string ) as xs:integer* { if (fn:contains($arg, $substring)) then (fn:string-length(fn:substring-before($arg, $substring))+1, for $other in functx:index-of-string(fn:substring-after($arg, $substring), $substring) return $other + fn:string-length(fn:substring-before($arg, $substring)) + fn:string-length($substring)) else () } define function insert-string ( $originalString as xs:string? , $stringToInsert as xs:string? , $pos as xs:integer ) as xs:string { fn:concat(fn:substring($originalString,1,$pos - 1), $stringToInsert, fn:substring($originalString,$pos)) } define function is-a-number ( $value as xdt:anyAtomicType? ) as xs:boolean { fn:string(fn:number($value)) != 'NaN' } define function is-absolute-uri ( $uri as xs:string? ) as xs:boolean { fn:matches($uri,'^[a-z]+:') } define function is-ancestor ( $node1 as node() , $node2 as node() ) as xs:boolean { fn:exists($node1 intersect $node2/ancestor::node()) } define function is-descendant ( $node1 as node() , $node2 as node() ) as xs:boolean { fn:boolean($node2 intersect $node1/ancestor::node()) } define function is-leap-year ( $date as xdt:anyAtomicType? ) as xs:boolean { for $year in xs:integer(fn:substring(fn:string($date),1,4)) return ($year mod 4 = 0 and $year mod 100 != 0) or $year mod 400 = 0 } define function is-node-among-descendants-deep-equal ( $node as node()? , $seq as node()* ) as xs:boolean { some $nodeInSeq in $seq/descendant-or-self::*/(.|@*) satisfies fn:deep-equal($nodeInSeq,$node) } define function is-node-among-descendants ( $node as node()? , $seq as node()* ) as xs:boolean { some $nodeInSeq in $seq/descendant-or-self::*/(.|@*) satisfies $nodeInSeq is $node } define function is-node-in-sequence-deep-equal ( $node as node()? , $seq as node()* ) as xs:boolean { some $nodeInSeq in $seq satisfies fn:deep-equal($nodeInSeq,$node) } define function is-node-in-sequence ( $node as node()? , $seq as node()* ) as xs:boolean { some $nodeInSeq in $seq satisfies $nodeInSeq is $node } define function is-value-in-sequence ( $value as xdt:anyAtomicType? , $seq as xdt:anyAtomicType* ) as xs:boolean { $value = $seq } define function last-day-of-month ( $date as xdt:anyAtomicType? ) as xs:date? { functx:date(fn:get-year-from-date(xs:date($date)), fn:get-month-from-date(xs:date($date)), functx:days-in-month($date)) } define function last-day-of-year ( $date as xdt:anyAtomicType? ) as xs:date? { functx:date(fn:get-year-from-date(xs:date($date)), 12, 31) } define function last-node ( $nodes as node()* ) as node()? { ($nodes/.)[fn:last()] } define function leaf-elements ( $root as node()? ) as element()* { $root/descendant-or-self::*[fn:not(*)] } define function left-trim ( $arg as xs:string? ) as xs:string { fn:replace($arg,'^\s+','') } define function line-count ( $arg as xs:string? ) as xs:integer { fn:count(functx:lines($arg)) } define function lines ( $arg as xs:string? ) as xs:string* { fn:tokenize($arg, '(\r\n?|\n\r?)') } define function max-depth ( $root as node()? ) as xs:integer? { if ($root/*) then fn:max(for $node in $root/* return functx:max-depth($node)) + 1 else 1 } define function max-determine-type ( $seq as xdt:anyAtomicType* ) as xdt:anyAtomicType? { if (every $value in $seq satisfies ($value castable as xs:double)) then fn:max(for $value in $seq return xs:double($value)) else fn:max(for $value in $seq return xs:string($value)) } define function max-line-length ( $arg as xs:string? ) as xs:integer { fn:max( for $line in functx:lines($arg) return fn:string-length($line)) } define function max-node ( $nodes as node()* ) as node()* { $nodes[. = fn:max($nodes)] } define function max-string ( $strings as xdt:anyAtomicType* ) as xs:string? { fn:max(for $string in $strings return fn:string($string)) } define function min-determine-type ( $seq as xdt:anyAtomicType* ) as xdt:anyAtomicType? { if (every $value in $seq satisfies ($value castable as xs:double)) then fn:min(for $value in $seq return xs:double($value)) else fn:min(for $value in $seq return xs:string($value)) } define function min-node ( $nodes as node()* ) as node()* { $nodes[. = fn:min($nodes)] } define function min-non-empty-string ( $strings as xs:string* ) as xs:string? { fn:min($strings[. != '']) } define function min-string ( $strings as xdt:anyAtomicType* ) as xs:string? { fn:min(for $string in $strings return fn:string($string)) } define function mmddyyyy-to-date ( $dateString as xs:string? ) as xs:date? { if (fn:empty($dateString)) then () else if (fn:not(fn:matches($dateString, '^\D*(\d{2})\D*(\d{2})\D*(\d{4})\D*$'))) then fn:error(xs:QName('functx:Invalid_Date_Format')) else xs:date(fn:replace($dateString, '^\D*(\d{2})\D*(\d{2})\D*(\d{4})\D*$', '$3-$1-$2')) } define function month-abbrev-en ( $date as xdt:anyAtomicType? ) as xs:string? { ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') [fn:get-month-from-date(xs:date($date))] } define function month-name-en ( $date as xdt:anyAtomicType? ) as xs:string? { ('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December') [fn:get-month-from-date(xs:date($date))] } define function name-test ( $testname as xs:string? , $names as xs:string* ) as xs:boolean { $testname = $names or $names = '*' or functx:substring-after-if-contains($testname,':') = (for $name in $names return fn:substring-after($name,'*:')) or fn:substring-before($testname,':') = (for $name in $names[fn:contains(.,':*')] return fn:substring-before($name,':*')) } define function namespaces-in-use ( $root as node()? ) as xs:anyURI* { fn:distinct-values( for $node in $root/descendant-or-self::*/(.|@*) return fn:namespace-uri($node)) } define function next-day ( $date as xdt:anyAtomicType? ) as xs:date? { xs:date($date) + xdt:dayTimeDuration('P1D') } define function node-kind ( $nodes as node()* ) as xs:string* { for $node in $nodes return if ($node instance of element()) then 'element' else if ($node instance of attribute()) then 'attribute' else if ($node instance of text()) then 'text' else if ($node instance of document-node()) then 'document-node' else if ($node instance of comment()) then 'comment' else if ($node instance of processing-instruction()) then 'processing-instruction' else 'unknown' } define function non-distinct-values ( $seq as xdt:anyAtomicType* ) as xdt:anyAtomicType* { for $val in fn:distinct-values($seq) return $val[fn:count($seq[. = $val]) > 1] } define function number-of-matches ( $arg as xs:string? , $pattern as xs:string ) as xs:integer { fn:count(fn:tokenize($arg,$pattern)) - 1 } define function open-ref-document ( $refNode as node() ) as document-node() { if (fn:base-uri($refNode)) then fn:doc(fn:resolve-uri($refNode, fn:base-uri($refNode))) else fn:doc(fn:resolve-uri($refNode ,fn:string(fn:base-uri()))) } define function ordinal-number-en ( $num as xs:integer? ) as xs:string { fn:concat(xs:string($num), if (fn:matches(xs:string($num),'[04-9]$|1[1-3]$')) then 'th' else if (fn:ends-with(xs:string($num),'1')) then 'st' else if (fn:ends-with(xs:string($num),'2')) then 'nd' else if (fn:ends-with(xs:string($num),'3')) then 'rd' else '') } define function pad-integer-to-length ( $integerToPad as xdt:anyAtomicType? , $length as xs:integer ) as xs:string { if ($length < fn:string-length(fn:string($integerToPad))) then fn:error(xs:QName('functx:Integer_Longer_Than_Length')) else fn:concat (functx:repeat-string( '0',$length - fn:string-length(fn:string($integerToPad))), fn:string($integerToPad)) } define function pad-string-to-length ( $stringToPad as xs:string? , $padChar as xs:string , $length as xs:integer ) as xs:string { fn:substring( fn:string-join ( ($stringToPad, for $i in (1 to $length) return $padChar) ,'') ,1,$length) } define function path-to-node-with-pos ( $node as node()? ) as xs:string { fn:string-join( for $ancestor in $node/ancestor-or-self::* let $sibsOfSameName := $ancestor/../*[fn:name() = fn:name($ancestor)] return fn:concat(fn:name($ancestor), if (fn:count($sibsOfSameName) <= 1) then '' else fn:concat( '[',functx:index-of-node($sibsOfSameName,$ancestor),']')) , '/') } define function path-to-node ( $nodes as node()* ) as xs:string* { for $node in $nodes return fn:string-join(for $node in $node/ancestor-or-self::* return fn:name($node), '/') } define function precedes-not-ancestor ( $a as node()? , $b as node()? ) as xs:boolean { $a << $b and fn:empty($a intersect $b/ancestor::node()) } define function previous-day ( $date as xdt:anyAtomicType? ) as xs:date? { xs:date($date) - xdt:dayTimeDuration('P1D') } define function remove-attributes-deep ( $nodes as node()* , $names as xs:string* ) as node()* { for $node in $nodes return if ($node instance of element()) then element { fn:node-name($node)} { $node/@*[fn:not(functx:name-test(fn:name(),$names))], functx:remove-attributes-deep($node/node(), $names)} else if ($node instance of document-node()) then functx:remove-attributes-deep($node/node(), $names) else $node } define function remove-attributes ( $elements as element()* , $names as xs:string* ) as element() { for $element in $elements return element {fn:node-name($element)} {$element/@*[fn:not(functx:name-test(fn:name(),$names))], $element/node() } } define function remove-elements-deep ( $nodes as node()* , $names as xs:string* ) as node()* { for $node in $nodes return if ($node instance of element()) then if (functx:name-test(fn:name($node),$names)) then () else element { fn:node-name($node)} { $node/@*, functx:remove-elements-deep($node/node(), $names)} else if ($node instance of document-node()) then functx:remove-elements-deep($node/node(), $names) else $node } define function remove-elements-not-contents ( $nodes as node()* , $names as xs:string* ) as node()* { for $node in $nodes return if ($node instance of element()) then if (functx:name-test(fn:name($node),$names)) then functx:remove-elements-not-contents($node/node(), $names) else element {fn:node-name($node)} {$node/@*, functx:remove-elements-not-contents($node/node(),$names)} else if ($node instance of document-node()) then functx:remove-elements-not-contents($node/node(), $names) else $node } define function remove-elements ( $elements as element()* , $names as xs:string* ) as element()* { for $element in $elements return element {fn:node-name($element)} {$element/@*, $element/node()[fn:not(functx:name-test(fn:name(),$names))] } } define function repeat-string ( $stringToRepeat as xs:string? , $count as xs:integer ) as xs:string { if ($count > 0) then fn:string-join((for $i in 1 to $count return $stringToRepeat), '') else '' } define function replace-beginning ( $arg as xs:string? , $pattern as xs:string , $replacement as xs:string ) as xs:string { fn:replace($arg, fn:concat('^.*?', $pattern), $replacement) } define function replace-element-values ( $elements as element()* , $values as xdt:anyAtomicType* ) as element()* { for $element at $seq in $elements return element { fn:node-name($element)} { $element/@*, $values[$seq] } } define function replace-first ( $arg as xs:string? , $pattern as xs:string , $replacement as xs:string ) as xs:string { fn:replace($arg, fn:concat('(^.*?)', $pattern), fn:concat('$1',$replacement)) } define function replace-multi ( $arg as xs:string? , $changeFrom as xs:string* , $changeTo as xs:string* ) as xs:string? { if (fn:count($changeFrom) > 0) then functx:replace-multi( fn:replace($arg, $changeFrom[1], functx:if-absent($changeTo[1],'')), $changeFrom[fn:position() > 1], $changeTo[fn:position() > 1]) else $arg } define function reverse-string ( $arg as xs:string? ) as xs:string { fn:string-join( for $i in (0 to (fn:string-length($arg) - 1)) return fn:substring($arg, fn:string-length($arg) - $i,1), '') } define function right-trim ( $arg as xs:string? ) as xs:string { fn:replace($arg,'\s+$','') } define function scheme-from-uri ( $uri as xs:string? ) as xs:string? { fn:substring-before($uri,':') } define function sequence-deep-equal ( $seq1 as item()* , $seq2 as item()* ) as xs:boolean { every $i in 1 to fn:max((fn:count($seq1),fn:count($seq2))) satisfies fn:deep-equal($seq1[$i],$seq2[$i]) } define function sequence-node-equal-any-order ( $seq1 as node()* , $seq2 as node()* ) as xs:boolean { fn:not( ($seq1 except $seq2, $seq2 except $seq1)) } define function sequence-node-equal ( $seq1 as node()* , $seq2 as node()* ) as xs:boolean { every $i in 1 to fn:max((fn:count($seq1),fn:count($seq2))) satisfies $seq1[$i] is $seq2[$i] } define function sequence-type ( $items as item()* ) as xs:string { fn:concat( if (fn:empty($items)) then 'empty-sequence()' else if (every $val in $items satisfies $val instance of xdt:anyAtomicType) then if (fn:count(fn:distinct-values(functx:atomic-type($items))) > 1) then 'xdt:anyAtomicType' else functx:atomic-type($items[1]) else if (some $val in $items satisfies $val instance of xdt:anyAtomicType) then 'item()' else if (fn:count(fn:distinct-values(functx:node-kind($items))) > 1) then 'node()' else fn:concat(functx:node-kind($items[1]),'()') , if (fn:count($items) > 1) then '+' else '') } define function siblings-same-name ( $element as element()? ) as element()* { $element/../*[fn:node-name(.) = fn:node-name($element)] except $element } define function siblings ( $node as node()? ) as node()* { $node/../node() except $node } define function sort-as-numeric ( $seq as item()* ) as item()* { for $item in $seq order by fn:number($item) return $item } define function sort-case-insensitive ( $seq as item()* ) as item()* { for $item in $seq order by fn:upper-case(fn:string($item)) return $item } define function sort-document-order ( $seq as node()* ) as node()* { $seq/. } define function sort ( $seq as item()* ) as item()* { for $item in $seq order by $item return $item } define function substring-after-if-contains ( $arg as xs:string? , $delim as xs:string ) as xs:string? { if (fn:contains($arg,$delim)) then fn:substring-after($arg,$delim) else $arg } define function substring-after-last-match ( $arg as xs:string? , $regex as xs:string ) as xs:string { fn:replace($arg,fn:concat('^.*',$regex),'') } define function substring-after-last ( $arg as xs:string? , $delim as xs:string ) as xs:string { fn:replace ($arg,fn:concat('^.*',functx:escape-for-regex($delim)),'') } define function substring-after-match ( $arg as xs:string? , $regex as xs:string ) as xs:string? { fn:replace($arg,fn:concat('^.*?',$regex),'') } define function substring-before-if-contains ( $arg as xs:string? , $delim as xs:string ) as xs:string? { if (fn:contains($arg,$delim)) then fn:substring-before($arg,$delim) else $arg } define function substring-before-last-match ( $arg as xs:string? , $regex as xs:string ) as xs:string? { fn:replace($arg,fn:concat('^(.*)',$regex,'.*'),'$1') } define function substring-before-last ( $arg as xs:string? , $delim as xs:string ) as xs:string { if (fn:matches($arg, functx:escape-for-regex($delim))) then fn:replace($arg, fn:concat('^(.*)', functx:escape-for-regex($delim),'.*'), '$1') else '' } define function substring-before-match ( $arg as xs:string? , $regex as xs:string ) as xs:string { fn:tokenize($arg,$regex)[1] } define function time ( $hour as xdt:anyAtomicType , $minute as xdt:anyAtomicType , $second as xdt:anyAtomicType ) as xs:time { xs:time( fn:concat( functx:pad-integer-to-length(xs:integer($hour),2),':', functx:pad-integer-to-length(xs:integer($minute),2),':', functx:pad-integer-to-length(xs:integer($second),2))) } define function timezone-from-duration ( $duration as xdt:dayTimeDuration ) as xs:string { if (fn:string($duration) = ('PT0S','-PT0S')) then 'Z' else if (fn:matches(fn:string($duration),'-PT[1-9]H')) then fn:replace(fn:string($duration),'PT([1-9])H','0$1:00') else if (fn:matches(fn:string($duration),'PT[1-9]H')) then fn:replace(fn:string($duration),'PT([1-9])H','+0$1:00') else if (fn:matches(fn:string($duration),'-PT1[0-4]H')) then fn:replace(fn:string($duration),'PT(1[0-4])H','$1:00') else if (fn:matches(fn:string($duration),'PT1[0-4]H')) then fn:replace(fn:string($duration),'PT(1[0-4])H','+$1:00') else fn:error(xs:QName('functx:Invalid_Duration_Value')) } define function total-days-from-duration ( $duration as xdt:dayTimeDuration? ) as xs:decimal? { $duration div xdt:dayTimeDuration('P1D') } define function total-hours-from-duration ( $duration as xdt:dayTimeDuration? ) as xs:decimal? { $duration div xdt:dayTimeDuration('PT1H') } define function total-minutes-from-duration ( $duration as xdt:dayTimeDuration? ) as xs:decimal? { $duration div xdt:dayTimeDuration('PT1M') } define function total-months-from-duration ( $duration as xdt:yearMonthDuration? ) as xs:decimal? { $duration div xdt:yearMonthDuration('P1M') } define function total-seconds-from-duration ( $duration as xdt:dayTimeDuration? ) as xs:decimal? { $duration div xdt:dayTimeDuration('PT1S') } define function total-years-from-duration ( $duration as xdt:yearMonthDuration? ) as xs:decimal? { $duration div xdt:yearMonthDuration('P1Y') } define function trim ( $arg as xs:string? ) as xs:string { fn:replace(fn:replace($arg,'\s+$',''),'^\s+','') } define function update-attributes ( $elements as element()* , $attrNames as xs:QName* , $attrValues as xdt:anyAtomicType* ) as element()? { for $element in $elements return element { fn:node-name($element)} { for $attrName at $seq in $attrNames return if ($element/@*[fn:node-name(.) = $attrName]) then attribute {$attrName} {$attrValues[$seq]} else (), $element/@*[fn:not(fn:node-name(.) = $attrNames)], $element/node() } } define function value-except ( $arg1 as xdt:anyAtomicType* , $arg2 as xdt:anyAtomicType* ) as xdt:anyAtomicType* { fn:distinct-values($arg1[fn:not(.=$arg2)]) } define function value-intersect ( $arg1 as xdt:anyAtomicType* , $arg2 as xdt:anyAtomicType* ) as xdt:anyAtomicType* { fn:distinct-values($arg1[.=$arg2]) } define function value-union ( $arg1 as xdt:anyAtomicType* , $arg2 as xdt:anyAtomicType* ) as xdt:anyAtomicType* { fn:distinct-values(($arg1, $arg2)) } define function word-count ( $arg as xs:string? ) as xs:integer { fn:count(fn:tokenize($arg, '\W+')[. != '']) } define function words-to-camel-case ( $arg as xs:string? ) as xs:string { fn:string-join((fn:tokenize($arg,'\s+')[1], for $word in fn:tokenize($arg,'\s+')[fn:position() > 1] return functx:capitalize-first($word)) ,'') } define function wrap-values-in-elements ( $values as xdt:anyAtomicType* , $elementName as xs:QName ) as element()* { for $value in $values return element {$elementName} {$value} } define function yearMonthDuration ( $years as xs:decimal? , $months as xs:integer? ) as xdt:yearMonthDuration { (xdt:yearMonthDuration('P1M') * functx:if-empty($months,0)) + (xdt:yearMonthDuration('P1Y') * functx:if-empty($years,0)) }