(:~
: --------------------------------
: The FunctX XQuery Function Library
: --------------------------------
: Copyright (C) 2019-2024 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 pwalmsley@datypic.com.
: @version 1.0.1
: @see http://www.datypic.com/xq
:)
xquery version "1.0";
module namespace functx = "http://www.functx.com";
declare default function namespace "http://www.w3.org/2005/xpath-functions";
declare function functx:add-attributes
( $elements as element()* ,
$attrNames as xs:QName* ,
$attrValues as xs:anyAtomicType* ) as element()* {
for $element in $elements
return element { node-name($element)}
{ for $attrName at $seq in $attrNames
return if ($element/@*[node-name(.) = $attrName])
then ()
else attribute {$attrName}
{$attrValues[$seq]},
$element/@*,
$element/node() }
} ;
declare function functx:add-months
( $date as xs:anyAtomicType? ,
$months as xs:integer ) as xs:date? {
xs:date($date) + functx:yearMonthDuration(0,$months)
} ;
declare function functx:add-or-update-attributes
( $elements as element()* ,
$attrNames as xs:QName* ,
$attrValues as xs:anyAtomicType* ) as element()* {
for $element in $elements
return element { node-name($element)}
{ for $attrName at $seq in $attrNames
return attribute {$attrName}
{$attrValues[$seq]},
$element/@*[not(node-name(.) = $attrNames)],
$element/node() }
} ;
declare function functx:all-whitespace
( $arg as xs:string? ) as xs:boolean {
normalize-space($arg) = ''
} ;
declare function functx:are-distinct-values
( $seq as xs:anyAtomicType* ) as xs:boolean {
count(distinct-values($seq)) = count($seq)
} ;
declare function functx:atomic-type
( $values as xs:anyAtomicType* ) as xs:string* {
for $val in $values
return
(if ($val instance of xs:untypedAtomic) then 'xs: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 xs:dayTimeDuration)
then 'xs:dayTimeDuration'
else if ($val instance of xs:yearMonthDuration)
then 'xs: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')
} ;
declare function functx:avg-empty-is-zero
( $values as xs:anyAtomicType* ,
$allNodes as node()* ) as xs:double {
if (empty($allNodes))
then 0
else sum($values[string(.) != '']) div count($allNodes)
} ;
declare function functx:between-exclusive
( $value as xs:anyAtomicType? ,
$minValue as xs:anyAtomicType ,
$maxValue as xs:anyAtomicType ) as xs:boolean {
$value > $minValue and $value < $maxValue
} ;
declare function functx:between-inclusive
( $value as xs:anyAtomicType? ,
$minValue as xs:anyAtomicType ,
$maxValue as xs:anyAtomicType ) as xs:boolean {
$value >= $minValue and $value <= $maxValue
} ;
declare function functx:camel-case-to-words
( $arg as xs:string? ,
$delim as xs:string ) as xs:string {
concat(substring($arg,1,1),
replace(substring($arg,2),'(\p{Lu})',
concat($delim, '$1')))
} ;
declare function functx:capitalize-first
( $arg as xs:string? ) as xs:string? {
concat(upper-case(substring($arg,1,1)),
substring($arg,2))
} ;
declare function functx:change-element-names-deep
( $nodes as node()* ,
$oldNames as xs:QName* ,
$newNames as xs:QName* ) as node()* {
if (count($oldNames) != count($newNames))
then 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[index-of($oldNames,
node-name($node))],
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
} ;
declare function functx:change-element-ns
( $elements as element()* ,
$newns as xs:string ,
$prefix as xs:string ) as element()* {
for $element in $elements
return
element {QName ($newns,
concat($prefix,
if ($prefix = '')
then ''
else ':',
local-name($element)))}
{$element/@*, $element/node()}
} ;
declare function functx: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
{QName ($newns,
concat($prefix,
if ($prefix = '')
then ''
else ':',
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
} ;
declare function functx:chars
( $arg as xs:string? ) as xs:string* {
for $ch in string-to-codepoints($arg)
return codepoints-to-string($ch)
} ;
declare function functx:contains-any-of
( $arg as xs:string? ,
$searchStrings as xs:string* ) as xs:boolean {
some $searchString in $searchStrings
satisfies contains($arg,$searchString)
} ;
declare function functx:contains-case-insensitive
( $arg as xs:string? ,
$substring as xs:string ) as xs:boolean {
contains(upper-case($arg), upper-case($substring))
} ;
declare function functx:contains-word
( $arg as xs:string? ,
$word as xs:string ) as xs:boolean {
matches(upper-case($arg),
concat('^(.*\W)?',
upper-case(functx:escape-for-regex($word)),
'(\W.*)?$'))
} ;
declare function functx:copy-attributes
( $copyTo as element() ,
$copyFrom as element() ) as element() {
element { node-name($copyTo)}
{ $copyTo/@*[not(node-name(.) = $copyFrom/@*/node-name(.))],
$copyFrom/@*,
$copyTo/node() }
} ;
declare function functx:date
( $year as xs:anyAtomicType ,
$month as xs:anyAtomicType ,
$day as xs:anyAtomicType ) as xs:date {
xs:date(
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)))
} ;
declare function functx:dateTime
( $year as xs:anyAtomicType ,
$month as xs:anyAtomicType ,
$day as xs:anyAtomicType ,
$hour as xs:anyAtomicType ,
$minute as xs:anyAtomicType ,
$second as xs:anyAtomicType ) as xs:dateTime {
xs:dateTime(
concat(functx:date($year,$month,$day),'T',
functx:time($hour,$minute,$second)))
} ;
declare function functx:day-in-year
( $date as xs:anyAtomicType? ) as xs:integer? {
days-from-duration(
xs:date($date) - functx:first-day-of-year($date)) + 1
} ;
declare function functx:day-of-week
( $date as xs:anyAtomicType? ) as xs:integer? {
if (empty($date))
then ()
else xs:integer((xs:date($date) - xs:date('1901-01-06'))
div xs:dayTimeDuration('P1D')) mod 7
} ;
declare function functx:day-of-week-abbrev-en
( $date as xs:anyAtomicType? ) as xs:string? {
('Sun', 'Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat')
[functx:day-of-week($date) + 1]
} ;
declare function functx:day-of-week-name-en
( $date as xs:anyAtomicType? ) as xs:string? {
('Sunday', 'Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday', 'Saturday')
[functx:day-of-week($date) + 1]
} ;
declare function functx:dayTimeDuration
( $days as xs:decimal? ,
$hours as xs:decimal? ,
$minutes as xs:decimal? ,
$seconds as xs:decimal? ) as xs:dayTimeDuration {
(xs:dayTimeDuration('P1D') * functx:if-empty($days,0)) +
(xs:dayTimeDuration('PT1H') * functx:if-empty($hours,0)) +
(xs:dayTimeDuration('PT1M') * functx:if-empty($minutes,0)) +
(xs:dayTimeDuration('PT1S') * functx:if-empty($seconds,0))
} ;
declare function functx:days-in-month
( $date as xs:anyAtomicType? ) as xs:integer? {
if (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)
[month-from-date(xs:date($date))]
} ;
declare function functx:depth-of-node
( $node as node()? ) as xs:integer {
count($node/ancestor-or-self::node())
} ;
declare function functx:distinct-attribute-names
( $nodes as node()* ) as xs:string* {
distinct-values($nodes//@*/name(.))
} ;
declare function functx:distinct-deep
( $nodes as node()* ) as node()* {
for $seq in (1 to count($nodes))
return $nodes[$seq][not(functx:is-node-in-sequence-deep-equal(
.,$nodes[position() < $seq]))]
} ;
declare function functx:distinct-element-names
( $nodes as node()* ) as xs:string* {
distinct-values($nodes/descendant-or-self::*/name(.))
} ;
declare function functx:distinct-element-paths
( $nodes as node()* ) as xs:string* {
distinct-values(functx:path-to-node($nodes/descendant-or-self::*))
} ;
declare function functx:distinct-nodes
( $nodes as node()* ) as node()* {
for $seq in (1 to count($nodes))
return $nodes[$seq][not(functx:is-node-in-sequence(
.,$nodes[position() < $seq]))]
} ;
declare function functx:duration-from-timezone
( $timezone as xs:string ) as xs:dayTimeDuration {
xs:dayTimeDuration(
if (not(matches($timezone,'Z|[\+\-]\d{2}:\d{2}')))
then error(xs:QName('functx:Invalid_Timezone_Value'))
else if ($timezone = 'Z')
then 'PT0S'
else replace($timezone,'\+?(\d{2}):\d{2}','PT$1H')
)
} ;
declare function functx:dynamic-path
( $parent as node() ,
$path as xs:string ) as item()* {
let $nextStep := functx:substring-before-if-contains($path,'/')
let $restOfSteps := substring-after($path,'/')
for $child in
($parent/*[functx:name-test(name(),$nextStep)],
$parent/@*[functx:name-test(name(),
substring-after($nextStep,'@'))])
return if ($restOfSteps)
then functx:dynamic-path($child, $restOfSteps)
else $child
} ;
declare function functx:escape-for-regex
( $arg as xs:string? ) as xs:string {
replace($arg,
'(\.|\[|\]|\\|\||\-|\^|\$|\?|\*|\+|\{|\}|\(|\))','\\$1')
} ;
declare function functx:exclusive-or
( $arg1 as xs:boolean? ,
$arg2 as xs:boolean? ) as xs:boolean? {
$arg1 != $arg2
} ;
declare function functx:first-day-of-month
( $date as xs:anyAtomicType? ) as xs:date? {
functx:date(year-from-date(xs:date($date)),
month-from-date(xs:date($date)),
1)
} ;
declare function functx:first-day-of-year
( $date as xs:anyAtomicType? ) as xs:date? {
functx:date(year-from-date(xs:date($date)), 1, 1)
} ;
declare function functx:first-node
( $nodes as node()* ) as node()? {
($nodes/.)[1]
} ;
declare function functx:follows-not-descendant
( $a as node()? ,
$b as node()? ) as xs:boolean {
$a >> $b and empty($b intersect $a/ancestor::node())
} ;
declare function functx: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 replace($title,'(.*?)\W(.*)', '$2, $1')
else $title
} ;
declare function functx:fragment-from-uri
( $uri as xs:string? ) as xs:string? {
substring-after($uri,'#')
} ;
declare function functx:get-matches
( $string as xs:string? ,
$regex as xs:string ) as xs:string* {
functx:get-matches-and-non-matches($string,$regex)/
self::match/string(.)
} ;
declare function functx: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 (empty($iomf))
then {$string}
else
if ($iomf > 1)
then ({substring($string,1,$iomf - 1)},
functx:get-matches-and-non-matches(
substring($string,$iomf),$regex))
else
let $length :=
string-length($string) -
string-length(functx:replace-first($string, $regex,''))
return ({substring($string,1,$length)},
if (string-length($string) > $length)
then functx:get-matches-and-non-matches(
substring($string,$length + 1),$regex)
else ())
} ;
declare function functx:has-element-only-content
( $element as element() ) as xs:boolean {
not($element/text()[normalize-space(.) != '']) and $element/*
} ;
declare function functx:has-empty-content
( $element as element() ) as xs:boolean {
not($element/node())
} ;
declare function functx:has-mixed-content
( $element as element() ) as xs:boolean {
$element/text()[normalize-space(.) != ''] and $element/*
} ;
declare function functx:has-simple-content
( $element as element() ) as xs:boolean {
$element/text() and not($element/*)
} ;
declare function functx:id-from-element
( $element as element()? ) as xs:string? {
data(($element/@*[id(.) is ..])[1])
} ;
declare function functx:id-untyped
( $node as node()* ,
$id as xs:anyAtomicType ) as element()* {
$node//*[@* = $id]
} ;
declare function functx:if-absent
( $arg as item()* ,
$value as item()* ) as item()* {
if (exists($arg))
then $arg
else $value
} ;
declare function functx:if-empty
( $arg as item()? ,
$value as item()* ) as item()* {
if (string($arg) != '')
then data($arg)
else $value
} ;
declare function functx:index-of-deep-equal-node
( $nodes as node()* ,
$nodeToFind as node() ) as xs:integer* {
for $seq in (1 to count($nodes))
return $seq[deep-equal($nodes[$seq],$nodeToFind)]
} ;
declare function functx:index-of-match-first
( $arg as xs:string? ,
$pattern as xs:string ) as xs:integer? {
if (matches($arg,$pattern))
then string-length(tokenize($arg, $pattern)[1]) + 1
else ()
} ;
declare function functx:index-of-node
( $nodes as node()* ,
$nodeToFind as node() ) as xs:integer* {
for $seq in (1 to count($nodes))
return $seq[$nodes[$seq] is $nodeToFind]
} ;
declare function functx:index-of-string
( $arg as xs:string? ,
$substring as xs:string ) as xs:integer* {
if (contains($arg, $substring))
then (string-length(substring-before($arg, $substring))+1,
for $other in
functx:index-of-string(substring-after($arg, $substring),
$substring)
return
$other +
string-length(substring-before($arg, $substring)) +
string-length($substring))
else ()
} ;
declare function functx:index-of-string-first
( $arg as xs:string? ,
$substring as xs:string ) as xs:integer? {
if (contains($arg, $substring))
then string-length(substring-before($arg, $substring))+1
else ()
} ;
declare function functx:index-of-string-last
( $arg as xs:string? ,
$substring as xs:string ) as xs:integer? {
functx:index-of-string($arg, $substring)[last()]
} ;
declare function functx:insert-string
( $originalString as xs:string? ,
$stringToInsert as xs:string? ,
$pos as xs:integer ) as xs:string {
concat(substring($originalString,1,$pos - 1),
$stringToInsert,
substring($originalString,$pos))
} ;
declare function functx:is-a-number
( $value as xs:anyAtomicType? ) as xs:boolean {
string(number($value)) != 'NaN'
} ;
declare function functx:is-absolute-uri
( $uri as xs:string? ) as xs:boolean {
matches($uri,'^[a-z]+:')
} ;
declare function functx:is-ancestor
( $node1 as node() ,
$node2 as node() ) as xs:boolean {
exists($node1 intersect $node2/ancestor::node())
} ;
declare function functx:is-descendant
( $node1 as node() ,
$node2 as node() ) as xs:boolean {
boolean($node2 intersect $node1/ancestor::node())
} ;
declare function functx:is-leap-year
( $date as xs:anyAtomicType? ) as xs:boolean {
for $year in xs:integer(substring(string($date),1,4))
return ($year mod 4 = 0 and
$year mod 100 != 0) or
$year mod 400 = 0
} ;
declare function functx:is-node-among-descendants
( $node as node()? ,
$seq as node()* ) as xs:boolean {
some $nodeInSeq in $seq/descendant-or-self::*/(.|@*)
satisfies $nodeInSeq is $node
} ;
declare function functx:is-node-among-descendants-deep-equal
( $node as node()? ,
$seq as node()* ) as xs:boolean {
some $nodeInSeq in $seq/descendant-or-self::*/(.|@*)
satisfies deep-equal($nodeInSeq,$node)
} ;
declare function functx:is-node-in-sequence
( $node as node()? ,
$seq as node()* ) as xs:boolean {
some $nodeInSeq in $seq satisfies $nodeInSeq is $node
} ;
declare function functx:is-node-in-sequence-deep-equal
( $node as node()? ,
$seq as node()* ) as xs:boolean {
some $nodeInSeq in $seq satisfies deep-equal($nodeInSeq,$node)
} ;
declare function functx:is-value-in-sequence
( $value as xs:anyAtomicType? ,
$seq as xs:anyAtomicType* ) as xs:boolean {
$value = $seq
} ;
declare function functx:last-day-of-month
( $date as xs:anyAtomicType? ) as xs:date? {
functx:date(year-from-date(xs:date($date)),
month-from-date(xs:date($date)),
functx:days-in-month($date))
} ;
declare function functx:last-day-of-year
( $date as xs:anyAtomicType? ) as xs:date? {
functx:date(year-from-date(xs:date($date)), 12, 31)
} ;
declare function functx:last-node
( $nodes as node()* ) as node()? {
($nodes/.)[last()]
} ;
declare function functx:leaf-elements
( $root as node()? ) as element()* {
$root/descendant-or-self::*[not(*)]
} ;
declare function functx:left-trim
( $arg as xs:string? ) as xs:string {
replace($arg,'^\s+','')
} ;
declare function functx:line-count
( $arg as xs:string? ) as xs:integer {
count(functx:lines($arg))
} ;
declare function functx:lines
( $arg as xs:string? ) as xs:string* {
tokenize($arg, '(\r\n?|\n\r?)')
} ;
declare function functx:max-depth
( $root as node()? ) as xs:integer? {
if ($root/*)
then max($root/*/functx:max-depth(.)) + 1
else 1
} ;
declare function functx:max-determine-type
( $seq as xs:anyAtomicType* ) as xs:anyAtomicType? {
if (every $value in $seq satisfies ($value castable as xs:double))
then max(for $value in $seq return xs:double($value))
else max(for $value in $seq return xs:string($value))
} ;
declare function functx:max-line-length
( $arg as xs:string? ) as xs:integer? {
max(
for $line in functx:lines($arg)
return string-length($line))
} ;
declare function functx:max-node
( $nodes as node()* ) as node()* {
$nodes[. = max($nodes)]
} ;
declare function functx:max-string
( $strings as xs:anyAtomicType* ) as xs:string? {
max(for $string in $strings return string($string))
} ;
declare function functx:min-determine-type
( $seq as xs:anyAtomicType* ) as xs:anyAtomicType? {
if (every $value in $seq satisfies ($value castable as xs:double))
then min(for $value in $seq return xs:double($value))
else min(for $value in $seq return xs:string($value))
} ;
declare function functx:min-node
( $nodes as node()* ) as node()* {
$nodes[. = min($nodes)]
} ;
declare function functx:min-non-empty-string
( $strings as xs:string* ) as xs:string? {
min($strings[. != ''])
} ;
declare function functx:min-string
( $strings as xs:anyAtomicType* ) as xs:string? {
min(for $string in $strings return string($string))
} ;
declare function functx:mmddyyyy-to-date
( $dateString as xs:string? ) as xs:date? {
if (empty($dateString))
then ()
else if (not(matches($dateString,
'^\D*(\d{2})\D*(\d{2})\D*(\d{4})\D*$')))
then error(xs:QName('functx:Invalid_Date_Format'))
else xs:date(replace($dateString,
'^\D*(\d{2})\D*(\d{2})\D*(\d{4})\D*$',
'$3-$1-$2'))
} ;
declare function functx:month-abbrev-en
( $date as xs:anyAtomicType? ) as xs:string? {
('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
[month-from-date(xs:date($date))]
} ;
declare function functx:month-name-en
( $date as xs:anyAtomicType? ) as xs:string? {
('January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December')
[month-from-date(xs:date($date))]
} ;
declare function functx: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 substring-after($name,'*:'))
or
substring-before($testname,':') =
(for $name in $names[contains(.,':*')]
return substring-before($name,':*'))
} ;
declare function functx:namespaces-in-use
( $root as node()? ) as xs:anyURI* {
distinct-values(
$root/descendant-or-self::*/(.|@*)/namespace-uri(.))
} ;
declare function functx:next-day
( $date as xs:anyAtomicType? ) as xs:date? {
xs:date($date) + xs:dayTimeDuration('P1D')
} ;
declare function functx: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'
} ;
declare function functx:non-distinct-values
( $seq as xs:anyAtomicType* ) as xs:anyAtomicType* {
for $val in distinct-values($seq)
return $val[count($seq[. = $val]) > 1]
} ;
declare function functx:number-of-matches
( $arg as xs:string? ,
$pattern as xs:string ) as xs:integer {
if ($arg != '')
then count(tokenize($arg,$pattern)) - 1
else 0
} ;
declare function functx:open-ref-document
( $refNode as node() ) as document-node()? {
if (base-uri($refNode))
then doc(resolve-uri($refNode, base-uri($refNode)))
else if (static-base-uri())
then doc(resolve-uri($refNode, static-base-uri()))
else ()
} ;
declare function functx:ordinal-number-en
( $num as xs:integer? ) as xs:string {
concat(xs:string($num),
if (matches(xs:string($num),'[04-9]$|1[1-3]$')) then 'th'
else if (ends-with(xs:string($num),'1')) then 'st'
else if (ends-with(xs:string($num),'2')) then 'nd'
else if (ends-with(xs:string($num),'3')) then 'rd'
else '')
} ;
declare function functx:pad-integer-to-length
( $integerToPad as xs:anyAtomicType? ,
$length as xs:integer ) as xs:string {
if ($length < string-length(string($integerToPad)))
then error(xs:QName('functx:Integer_Longer_Than_Length'))
else concat
(functx:repeat-string(
'0',$length - string-length(string($integerToPad))),
string($integerToPad))
} ;
declare function functx:pad-string-to-length
( $stringToPad as xs:string? ,
$padChar as xs:string ,
$length as xs:integer ) as xs:string {
substring(
string-join (
($stringToPad, for $i in (1 to $length) return $padChar)
,'')
,1,$length)
} ;
declare function functx:path-to-node
( $nodes as node()* ) as xs:string* {
$nodes/string-join(ancestor-or-self::*/name(.), '/')
} ;
declare function functx:path-to-node-with-pos
( $node as node()? ) as xs:string {
string-join(
for $ancestor in $node/ancestor-or-self::*
let $sibsOfSameName := $ancestor/../*[name() = name($ancestor)]
return concat(name($ancestor),
if (count($sibsOfSameName) <= 1)
then ''
else concat(
'[',functx:index-of-node($sibsOfSameName,$ancestor),']'))
, '/')
} ;
declare function functx:precedes-not-ancestor
( $a as node()? ,
$b as node()? ) as xs:boolean {
$a << $b and empty($a intersect $b/ancestor::node())
} ;
declare function functx:previous-day
( $date as xs:anyAtomicType? ) as xs:date? {
xs:date($date) - xs:dayTimeDuration('P1D')
} ;
declare function functx:remove-attributes
( $elements as element()* ,
$names as xs:string* ) as element()* {
for $element in $elements
return element
{node-name($element)}
{$element/@*[not(functx:name-test(name(),$names))],
$element/node() }
} ;
declare function functx:remove-attributes-deep
( $nodes as node()* ,
$names as xs:string* ) as node()* {
for $node in $nodes
return if ($node instance of element())
then element { node-name($node)}
{ $node/@*[not(functx:name-test(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
} ;
declare function functx:remove-elements
( $elements as element()* ,
$names as xs:string* ) as element()* {
for $element in $elements
return element
{node-name($element)}
{$element/@*,
$element/node()[not(functx:name-test(name(),$names))] }
} ;
declare function functx: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(name($node),$names))
then ()
else element { 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
} ;
declare function functx: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(name($node),$names))
then functx:remove-elements-not-contents($node/node(), $names)
else element {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
} ;
declare function functx:repeat-string
( $stringToRepeat as xs:string? ,
$count as xs:integer ) as xs:string {
string-join((for $i in 1 to $count return $stringToRepeat),
'')
} ;
declare function functx:replace-beginning
( $arg as xs:string? ,
$pattern as xs:string ,
$replacement as xs:string ) as xs:string {
replace($arg, concat('^.*?', $pattern), $replacement)
} ;
declare function functx:replace-element-values
( $elements as element()* ,
$values as xs:anyAtomicType* ) as element()* {
for $element at $seq in $elements
return element { node-name($element)}
{ $element/@*,
$values[$seq] }
} ;
declare function functx:replace-first
( $arg as xs:string? ,
$pattern as xs:string ,
$replacement as xs:string ) as xs:string {
replace($arg, concat('(^.*?)', $pattern),
concat('$1',$replacement))
} ;
declare function functx:replace-multi
( $arg as xs:string? ,
$changeFrom as xs:string* ,
$changeTo as xs:string* ) as xs:string? {
if (count($changeFrom) > 0)
then functx:replace-multi(
replace($arg, $changeFrom[1],
functx:if-absent($changeTo[1],'')),
$changeFrom[position() > 1],
$changeTo[position() > 1])
else $arg
} ;
declare function functx:reverse-string
( $arg as xs:string? ) as xs:string {
codepoints-to-string(reverse(string-to-codepoints($arg)))
} ;
declare function functx:right-trim
( $arg as xs:string? ) as xs:string {
replace($arg,'\s+$','')
} ;
declare function functx:scheme-from-uri
( $uri as xs:string? ) as xs:string {
substring-before($uri,':')
} ;
declare function functx:sequence-deep-equal
( $seq1 as item()* ,
$seq2 as item()* ) as xs:boolean {
every $i in 1 to max((count($seq1),count($seq2)))
satisfies deep-equal($seq1[$i],$seq2[$i])
} ;
declare function functx:sequence-node-equal
( $seq1 as node()* ,
$seq2 as node()* ) as xs:boolean {
every $i in 1 to max((count($seq1),count($seq2)))
satisfies $seq1[$i] is $seq2[$i]
} ;
declare function functx:sequence-node-equal-any-order
( $seq1 as node()* ,
$seq2 as node()* ) as xs:boolean {
not( ($seq1 except $seq2, $seq2 except $seq1))
} ;
declare function functx:sequence-type
( $items as item()* ) as xs:string {
concat(
if (empty($items))
then 'empty-sequence()'
else if (every $val in $items
satisfies $val instance of xs:anyAtomicType)
then if (count(distinct-values(functx:atomic-type($items)))
> 1)
then 'xs:anyAtomicType'
else functx:atomic-type($items[1])
else if (some $val in $items
satisfies $val instance of xs:anyAtomicType)
then 'item()'
else if (count(distinct-values(functx:node-kind($items))) > 1)
then 'node()'
else concat(functx:node-kind($items[1]),'()')
,
if (count($items) > 1)
then '+' else '')
} ;
declare function functx:siblings
( $node as node()? ) as node()* {
$node/../node() except $node
} ;
declare function functx:siblings-same-name
( $element as element()? ) as element()* {
$element/../*[node-name(.) = node-name($element)]
except $element
} ;
declare function functx:sort
( $seq as item()* ) as item()* {
for $item in $seq
order by $item
return $item
} ;
declare function functx:sort-as-numeric
( $seq as item()* ) as item()* {
for $item in $seq
order by number($item)
return $item
} ;
declare function functx:sort-case-insensitive
( $seq as item()* ) as item()* {
for $item in $seq
order by upper-case(string($item))
return $item
} ;
declare function functx:sort-document-order
( $seq as node()* ) as node()* {
$seq/.
} ;
declare function functx:substring-after-if-contains
( $arg as xs:string? ,
$delim as xs:string ) as xs:string? {
if (contains($arg,$delim))
then substring-after($arg,$delim)
else $arg
} ;
declare function functx:substring-after-last
( $arg as xs:string? ,
$delim as xs:string ) as xs:string? {
replace ($arg,concat('^.*',functx:escape-for-regex($delim)),'')
} ;
declare function functx:substring-after-last-match
( $arg as xs:string? ,
$regex as xs:string ) as xs:string? {
replace($arg,concat('^.*',$regex),'')
} ;
declare function functx:substring-after-match
( $arg as xs:string? ,
$regex as xs:string ) as xs:string? {
replace($arg,concat('^.*?',$regex),'')
} ;
declare function functx:substring-before-if-contains
( $arg as xs:string? ,
$delim as xs:string ) as xs:string? {
if (contains($arg,$delim))
then substring-before($arg,$delim)
else $arg
} ;
declare function functx:substring-before-last
( $arg as xs:string? ,
$delim as xs:string ) as xs:string {
if (matches($arg, functx:escape-for-regex($delim)))
then replace($arg,
concat('^(.*)', functx:escape-for-regex($delim),'.*'),
'$1')
else ''
} ;
declare function functx:substring-before-last-match
( $arg as xs:string? ,
$regex as xs:string ) as xs:string? {
replace($arg,concat('^(.*)',$regex,'.*'),'$1')
} ;
declare function functx:substring-before-match
( $arg as xs:string? ,
$regex as xs:string ) as xs:string? {
tokenize($arg,$regex)[1]
} ;
declare function functx:time
( $hour as xs:anyAtomicType ,
$minute as xs:anyAtomicType ,
$second as xs:anyAtomicType ) as xs:time {
xs:time(
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)))
} ;
declare function functx:timezone-from-duration
( $duration as xs:dayTimeDuration ) as xs:string {
if (string($duration) = ('PT0S','-PT0S'))
then 'Z'
else if (matches(string($duration),'-PT[1-9]H'))
then replace(string($duration),'PT([1-9])H','0$1:00')
else if (matches(string($duration),'PT[1-9]H'))
then replace(string($duration),'PT([1-9])H','+0$1:00')
else if (matches(string($duration),'-PT1[0-4]H'))
then replace(string($duration),'PT(1[0-4])H','$1:00')
else if (matches(string($duration),'PT1[0-4]H'))
then replace(string($duration),'PT(1[0-4])H','+$1:00')
else error(xs:QName('functx:Invalid_Duration_Value'))
} ;
declare function functx:total-days-from-duration
( $duration as xs:dayTimeDuration? ) as xs:decimal? {
$duration div xs:dayTimeDuration('P1D')
} ;
declare function functx:total-hours-from-duration
( $duration as xs:dayTimeDuration? ) as xs:decimal? {
$duration div xs:dayTimeDuration('PT1H')
} ;
declare function functx:total-minutes-from-duration
( $duration as xs:dayTimeDuration? ) as xs:decimal? {
$duration div xs:dayTimeDuration('PT1M')
} ;
declare function functx:total-months-from-duration
( $duration as xs:yearMonthDuration? ) as xs:decimal? {
$duration div xs:yearMonthDuration('P1M')
} ;
declare function functx:total-seconds-from-duration
( $duration as xs:dayTimeDuration? ) as xs:decimal? {
$duration div xs:dayTimeDuration('PT1S')
} ;
declare function functx:total-years-from-duration
( $duration as xs:yearMonthDuration? ) as xs:decimal? {
$duration div xs:yearMonthDuration('P1Y')
} ;
declare function functx:trim
( $arg as xs:string? ) as xs:string {
replace(replace($arg,'\s+$',''),'^\s+','')
} ;
declare function functx:update-attributes
( $elements as element()* ,
$attrNames as xs:QName* ,
$attrValues as xs:anyAtomicType* ) as element()* {
for $element in $elements
return element { node-name($element)}
{ for $attrName at $seq in $attrNames
return if ($element/@*[node-name(.) = $attrName])
then attribute {$attrName}
{$attrValues[$seq]}
else (),
$element/@*[not(node-name(.) = $attrNames)],
$element/node() }
} ;
declare function functx:value-except
( $arg1 as xs:anyAtomicType* ,
$arg2 as xs:anyAtomicType* ) as xs:anyAtomicType* {
distinct-values($arg1[not(.=$arg2)])
} ;
declare function functx:value-intersect
( $arg1 as xs:anyAtomicType* ,
$arg2 as xs:anyAtomicType* ) as xs:anyAtomicType* {
distinct-values($arg1[.=$arg2])
} ;
declare function functx:value-union
( $arg1 as xs:anyAtomicType* ,
$arg2 as xs:anyAtomicType* ) as xs:anyAtomicType* {
distinct-values(($arg1, $arg2))
} ;
declare function functx:word-count
( $arg as xs:string? ) as xs:integer {
count(tokenize($arg, '\W+')[. != ''])
} ;
declare function functx:words-to-camel-case
( $arg as xs:string? ) as xs:string {
string-join((tokenize($arg,'\s+')[1],
for $word in tokenize($arg,'\s+')[position() > 1]
return functx:capitalize-first($word))
,'')
} ;
declare function functx:wrap-values-in-elements
( $values as xs:anyAtomicType* ,
$elementName as xs:QName ) as element()* {
for $value in $values
return element {$elementName} {$value}
} ;
declare function functx:yearMonthDuration
( $years as xs:decimal? ,
$months as xs:integer? ) as xs:yearMonthDuration {
(xs:yearMonthDuration('P1M') * functx:if-empty($months,0)) +
(xs:yearMonthDuration('P1Y') * functx:if-empty($years,0))
} ;