(:~ : -------------------------------- : 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" (:~ : Adds attributes to XML elements : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_add-attributes.html : @param $elements the element(s) to which you wish to add the attribute : @param $attrNames the name(s) of the attribute(s) to add : @param $attrValues the value(s) of the attribute(s) to add :) 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() } } (:~ : Adds months to a date : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_add-months.html : @param $date the date : @param $months the number of months to add :) define function add-months ( $date as xdt:anyAtomicType? , $months as xs:integer ) as xs:date? { xs:date($date) + functx:yearMonthDuration(0,$months) } (:~ : Adds attributes to XML elements : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_add-or-update-attributes.html : @param $elements the element(s) to which you wish to add the attribute : @param $attrNames the name(s) of the attribute(s) to add : @param $attrValues the value(s) of the attribute(s) to add :) 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() } } (:~ : Whether a value is all whitespace or a zero-length string : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_all-whitespace.html : @param $arg the string (or node) to test :) define function all-whitespace ( $arg as xs:string? ) as xs:boolean { fn:normalize-space($arg) = '' } (:~ : Whether all the values in a sequence are distinct : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_are-distinct-values.html : @param $seq the sequence of values :) define function are-distinct-values ( $seq as xdt:anyAtomicType* ) as xs:boolean { fn:count(fn:distinct-values($seq)) = fn:count($seq) } (:~ : The built-in type of an atomic value : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_atomic-type.html : @param $values the value(s) whose type you want to determine :) 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') } (:~ : The average, counting "empty" values as zero : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_avg-empty-is-zero.html : @param $values the values to be averaged : @param $allNodes the sequence of all nodes to find the average over :) 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) } (:~ : Whether a value is between two provided values : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_between-exclusive.html : @param $value the value to be tested : @param $minValue the minimum value : @param $maxValue the maximum value :) define function between-exclusive ( $value as xdt:anyAtomicType? , $minValue as xdt:anyAtomicType , $maxValue as xdt:anyAtomicType ) as xs:boolean { $value > $minValue and $value < $maxValue } (:~ : Whether a value is between two provided values, or equal to one of them : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_between-inclusive.html : @param $value the value to be tested : @param $minValue the minimum value : @param $maxValue the maximum value :) define function between-inclusive ( $value as xdt:anyAtomicType? , $minValue as xdt:anyAtomicType , $maxValue as xdt:anyAtomicType ) as xs:boolean { $value >= $minValue and $value <= $maxValue } (:~ : Turns a camelCase string into space-separated words : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_camel-case-to-words.html : @param $arg the string to modify : @param $delim the delimiter for the words (e.g. a space) :) 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'))) } (:~ : Capitalizes the first character of a string : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_capitalize-first.html : @param $arg the word or phrase to capitalize :) 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)) } (:~ : Changes the names of elements in an XML fragment : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_change-element-names-deep.html : @param $nodes the element(s) to change : @param $oldNames the sequence of names to change from : @param $newNames the sequence of names to change to :) 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 } (:~ : Changes the namespace of XML elements and its descendants : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_change-element-ns-deep.html : @param $nodes the nodes to change : @param $newns the new namespace : @param $prefix the prefix to use for the new namespace :) 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 } (:~ : Changes the namespace of XML elements : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_change-element-ns.html : @param $elements the elements to change : @param $newns the new namespace : @param $prefix the prefix to use for the new namespace :) 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()} } (:~ : Converts a string to a sequence of characters : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_chars.html : @param $arg the string to split :) define function chars ( $arg as xs:string? ) as xs:string* { for $ch in fn:string-to-codepoints($arg) return fn:codepoints-to-string($ch) } (:~ : Whether a string contains any of a sequence of strings : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_contains-any-of.html : @param $arg the string to test : @param $searchStrings the strings to look for :) 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) } (:~ : Whether one string contains another, without regard to case : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_contains-case-insensitive.html : @param $arg the string to search : @param $substring the substring to find :) 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)) } (:~ : Whether one string contains another, as a separate word : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_contains-word.html : @param $arg the string to search : @param $word the word to find :) 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.*)?$')) } (:~ : Copies attributes from one element to another : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_copy-attributes.html : @param $copyTo the element to copy attributes to : @param $copyFrom the element to copy attributes from :) 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() } } (:~ : Construct a date from a year, month and day : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_date.html : @param $year the year : @param $month the month : @param $day the day :) 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))) } (:~ : Construct a date/time from individual components : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_datetime.html : @param $year the year : @param $month the month : @param $day the day : @param $hour the hour : @param $minute the minute : @param $second the second :) 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))) } (:~ : The day of the year (a number between 1 and 366) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_day-in-year.html : @param $date the date :) 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 } (:~ : The abbreviated day of the week, from a date, in English : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_day-of-week-abbrev-en.html : @param $date the date :) 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] } (:~ : The name of the day of the week, from a date, in English : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_day-of-week-name-en.html : @param $date the date :) 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] } (:~ : The day of the week, from a date : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_day-of-week.html : @param $date the date :) 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 } (:~ : Construct a dayTimeDuration from a number of days, hours, etc. : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_daytimeduration.html : @param $days the number of days : @param $hours the number of hours : @param $minutes the number of minutes : @param $seconds the number of seconds :) 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)) } (:~ : Number of days in the month : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_days-in-month.html : @param $date the date :) 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))] } (:~ : The depth (level) of a node in an XML tree : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_depth-of-node.html : @param $node the node to check :) define function depth-of-node ( $node as node()? ) as xs:integer { fn:count($node/ancestor-or-self::node()) } (:~ : The distinct names of all attributes in an XML fragment : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_distinct-attribute-names.html : @param $nodes the root to start from :) define function distinct-attribute-names ( $nodes as node()* ) as xs:string* { fn:distinct-values(for $attr in $nodes//@* return fn:name($attr)) } (:~ : The XML nodes with distinct values, taking into account attributes and descendants : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_distinct-deep.html : @param $nodes the sequence of nodes to test :) 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]))] } (:~ : The distinct names of all elements in an XML fragment : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_distinct-element-names.html : @param $nodes the root(s) to start from :) 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)) } (:~ : The distinct paths of all descendant elements in an XML fragment : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_distinct-element-paths.html : @param $nodes the root(s) to start from :) define function distinct-element-paths ( $nodes as node()* ) as xs:string* { fn:distinct-values(functx:path-to-node($nodes/descendant-or-self::*)) } (:~ : The distinct XML nodes in a sequence (by node identity) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_distinct-nodes.html : @param $nodes the node sequence :) 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]))] } (:~ : Converts a timezone like "-05:00" or "Z" into xs:dayTimeDuration : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_duration-from-timezone.html : @param $timezone the time zone, in (+|-)HH:MM format :) 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') ) } (:~ : Dynamically evaluates a simple XPath path : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_dynamic-path.html : @param $parent the root to start from : @param $path the path expression :) 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 } (:~ : Escapes regex special characters : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_escape-for-regex.html : @param $arg the string to escape :) define function escape-for-regex ( $arg as xs:string? ) as xs:string { fn:replace($arg, '(\.|\[|\]|\\|\||\-|\^|\$|\?|\*|\+|\{|\}|\(|\))','\$1') } (:~ : Whether one (and only one) of two boolean values is true : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_exclusive-or.html : @param $arg1 the first boolean value : @param $arg2 the second boolean value :) define function exclusive-or ( $arg1 as xs:boolean? , $arg2 as xs:boolean? ) as xs:boolean? { $arg1 != $arg2 } (:~ : The first day of the month of a date : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_first-day-of-month.html : @param $date the date :) 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) } (:~ : The first day of the year of a date : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_first-day-of-year.html : @param $date the date :) 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) } (:~ : The XML node in a sequence that appears first in document order : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_first-node.html : @param $nodes the sequence of nodes :) define function first-node ( $nodes as node()* ) as node()? { ($nodes/.)[1] } (:~ : Whether an XML node follows another without being its descendant : : @author W3C XML Query Working Group : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_follows-not-descendant.html : @param $a the first node : @param $b the second node :) define function follows-not-descendant ( $a as node()? , $b as node()? ) as xs:boolean { $a >> $b and fn:empty($b intersect $a/ancestor::node()) } (:~ : Moves title words like "the" and "a" to the end of strings : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_format-as-title-en.html : @param $titles the titles to format :) 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 } (:~ : Returns the fragment from a URI : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_fragment-from-uri.html : @param $uri the URI :) define function fragment-from-uri ( $uri as xs:string? ) as xs:string? { fn:substring-after($uri,'#') } (:~ : Splits a string into matching and non-matching regions : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_get-matches-and-non-matches.html : @param $string the string to split : @param $regex the pattern :) 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 ()) } (:~ : Return the matching regions of a string : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_get-matches.html : @param $string the string to split : @param $regex the pattern :) 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) } (:~ : Whether an element has element-only content : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_has-element-only-content.html : @param $element the XML element to test :) define function has-element-only-content ( $element as element() ) as xs:boolean { fn:not($element/text()[fn:normalize-space(.) != '']) and $element/* } (:~ : Whether an element has empty content : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_has-empty-content.html : @param $element the XML element to test :) define function has-empty-content ( $element as element() ) as xs:boolean { fn:not($element/node()) } (:~ : Whether an element has mixed content : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_has-mixed-content.html : @param $element the XML element to test :) define function has-mixed-content ( $element as element() ) as xs:boolean { $element/text()[fn:normalize-space(.) != ''] and $element/* } (:~ : Whether an element has simple content : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_has-simple-content.html : @param $element the XML element to test :) define function has-simple-content ( $element as element() ) as xs:boolean { $element/text() and fn:not($element/*) } (:~ : Gets the ID of an XML element : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_id-from-element.html : @param $element the element :) define function id-from-element ( $element as element()? ) as xs:string? { fn:data(($element/@*[fn:id(.) is ..])[1]) } (:~ : Gets XML element(s) that have an attribute with a particular value : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_id-untyped.html : @param $node the root node(s) to start from : @param $id the "id" to find :) define function id-untyped ( $node as node()* , $id as xdt:anyAtomicType ) as element()* { $node//*[@* = $id] } (:~ : The first argument if it is not empty, otherwise the second argument : : @author W3C XML Query WG : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_if-absent.html : @param $arg the item(s) that may be absent : @param $value the item(s) to use if the item is absent :) define function if-absent ( $arg as item()* , $value as item()* ) as item()* { if (fn:exists($arg)) then $arg else $value } (:~ : The first argument if it is not blank, otherwise the second argument : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_if-empty.html : @param $arg the node that may be empty : @param $value the item(s) to use if the node is empty :) define function if-empty ( $arg as item()? , $value as item()* ) as item()* { if (fn:string($arg) != '') then fn:data($arg) else $value } (:~ : The position of a node in a sequence, based on contents and attributes : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-deep-equal-node.html : @param $nodes the node sequence : @param $nodeToFind the node to find in the sequence :) 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)] } (:~ : The first position of a matching substring : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-match-first.html : @param $arg the string : @param $pattern the pattern to match :) 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 () } (:~ : The position of a node in a sequence, based on node identity : : @author W3C XML Query Working Group : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-node.html : @param $nodes the node sequence : @param $nodeToFind the node to find in the sequence :) 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] } (:~ : The first position of a substring : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-string-first.html : @param $arg the string : @param $substring the substring to find :) 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 () } (:~ : The last position of a substring : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-string-last.html : @param $arg the string : @param $substring the substring to find :) 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()] } (:~ : The position(s) of a substring : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_index-of-string.html : @param $arg the string : @param $substring the substring to find :) 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 () } (:~ : Inserts a string at a specified position : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_insert-string.html : @param $originalString the original string to insert into : @param $stringToInsert the string to insert : @param $pos the position :) 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)) } (:~ : Whether a value is numeric : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-a-number.html : @param $value the value to test :) define function is-a-number ( $value as xdt:anyAtomicType? ) as xs:boolean { fn:string(fn:number($value)) != 'NaN' } (:~ : Whether a URI is absolute : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-absolute-uri.html : @param $uri the URI to test :) define function is-absolute-uri ( $uri as xs:string? ) as xs:boolean { fn:matches($uri,'^[a-z]+:') } (:~ : Whether an XML node is an ancestor of another node : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-ancestor.html : @param $node1 the first node : @param $node2 the second node :) define function is-ancestor ( $node1 as node() , $node2 as node() ) as xs:boolean { fn:exists($node1 intersect $node2/ancestor::node()) } (:~ : Whether an XML node is a descendant of another node : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-descendant.html : @param $node1 the first node : @param $node2 the second node :) define function is-descendant ( $node1 as node() , $node2 as node() ) as xs:boolean { fn:boolean($node2 intersect $node1/ancestor::node()) } (:~ : Whether a date falls in a leap year : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-leap-year.html : @param $date the date or year :) 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 } (:~ : Whether an XML node is among the descendants of a sequence, based on contents and attributes : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-node-among-descendants-deep-equal.html : @param $node the node to test : @param $seq the sequence of nodes to search :) 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) } (:~ : Whether an XML node is among the descendants of a sequence, based on node identity : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-node-among-descendants.html : @param $node the node to test : @param $seq the sequence of nodes to search :) 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 } (:~ : Whether an XML node is in a sequence, based on contents and attributes : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-node-in-sequence-deep-equal.html : @param $node the node to test : @param $seq the sequence of nodes to search :) 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) } (:~ : Whether an XML node is in a sequence, based on node identity : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-node-in-sequence.html : @param $node the node to test : @param $seq the sequence of nodes to search :) define function is-node-in-sequence ( $node as node()? , $seq as node()* ) as xs:boolean { some $nodeInSeq in $seq satisfies $nodeInSeq is $node } (:~ : Whether an atomic value appears in a sequence : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_is-value-in-sequence.html : @param $value the atomic value to test : @param $seq the sequence of values to search :) define function is-value-in-sequence ( $value as xdt:anyAtomicType? , $seq as xdt:anyAtomicType* ) as xs:boolean { $value = $seq } (:~ : The last day of the month of a date : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_last-day-of-month.html : @param $date the date :) 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)) } (:~ : The last day of the month of a date : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_last-day-of-year.html : @param $date the 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) } (:~ : The XML node in a sequence that is last in document order : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_last-node.html : @param $nodes the sequence of nodes :) define function last-node ( $nodes as node()* ) as node()? { ($nodes/.)[fn:last()] } (:~ : All XML elements that don't have any child elements : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_leaf-elements.html : @param $root the root :) define function leaf-elements ( $root as node()? ) as element()* { $root/descendant-or-self::*[fn:not(*)] } (:~ : Trims leading whitespace : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_left-trim.html : @param $arg the string to trim :) define function left-trim ( $arg as xs:string? ) as xs:string { fn:replace($arg,'^\s+','') } (:~ : The number of lines : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_line-count.html : @param $arg the string to test :) define function line-count ( $arg as xs:string? ) as xs:integer { fn:count(functx:lines($arg)) } (:~ : Split a string into separate lines : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_lines.html : @param $arg the string to split :) define function lines ( $arg as xs:string? ) as xs:string* { fn:tokenize($arg, '(\r\n?|\n\r?)') } (:~ : The maximum depth of elements in an XML tree : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_max-depth.html : @param $root the root to start from :) 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 } (:~ : The maximum value in a sequence, figuring out its type (numeric or string) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_max-determine-type.html : @param $seq the sequence of values to test :) 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)) } (:~ : The maximum line length : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_max-line-length.html : @param $arg the string to test :) 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)) } (:~ : The XML node whose typed value is the maximum : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_max-node.html : @param $nodes the sequence of nodes to test :) define function max-node ( $nodes as node()* ) as node()* { $nodes[. = fn:max($nodes)] } (:~ : The maximum of a sequence of values, treating them like strings : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_max-string.html : @param $strings the sequence of values :) define function max-string ( $strings as xdt:anyAtomicType* ) as xs:string? { fn:max(for $string in $strings return fn:string($string)) } (:~ : The minimum value in a sequence, figuring out its type (numeric or string) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_min-determine-type.html : @param $seq the sequence of values to test :) 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)) } (:~ : The XML node whose typed value is the minimum : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_min-node.html : @param $nodes the sequence of nodes to test :) define function min-node ( $nodes as node()* ) as node()* { $nodes[. = fn:min($nodes)] } (:~ : The minimum of a sequence of strings, ignoring "empty" values : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_min-non-empty-string.html : @param $strings the sequence of strings to search :) define function min-non-empty-string ( $strings as xs:string* ) as xs:string? { fn:min($strings[. != '']) } (:~ : The minimum of a sequence of values, treating them like strings : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_min-string.html : @param $strings the sequence of strings :) define function min-string ( $strings as xdt:anyAtomicType* ) as xs:string? { fn:min(for $string in $strings return fn:string($string)) } (:~ : Converts a string with format MMDDYYYY (with any delimiters) to a date : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_mmddyyyy-to-date.html : @param $dateString the MMDDYYYY 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')) } (:~ : The month of a date as an abbreviated word (Jan, Feb, etc.) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_month-abbrev-en.html : @param $date the date :) 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))] } (:~ : The month of a date as a word (January, February, etc.) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_month-name-en.html : @param $date the 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))] } (:~ : Whether a name matches a list of names or name wildcards : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_name-test.html : @param $testname the name to test : @param $names the list of names or name wildcards :) 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,':*')) } (:~ : A list of namespaces used in element/attribute names in an XML fragment : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_namespaces-in-use.html : @param $root the root node to start from :) 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)) } (:~ : The next day : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_next-day.html : @param $date the date :) define function next-day ( $date as xdt:anyAtomicType? ) as xs:date? { xs:date($date) + xdt:dayTimeDuration('P1D') } (:~ : The XML node kind (element, attribute, text, etc.) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_node-kind.html : @param $nodes the node(s) whose kind you want to determine :) 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' } (:~ : Returns any values that appear more than once in a sequence : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_non-distinct-values.html : @param $seq the sequence of values :) 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] } (:~ : The number of regions that match a pattern : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_number-of-matches.html : @param $arg the string to test : @param $pattern the regular expression :) define function number-of-matches ( $arg as xs:string? , $pattern as xs:string ) as xs:integer { fn:count(fn:tokenize($arg,$pattern)) - 1 } (:~ : Resolves a relative URI and references it, returning an XML document : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_open-ref-document.html : @param $refNode a node whose value is a relative URI reference :) 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()))) } (:~ : Reformats a number as an ordinal number, e.g. 1st, 2nd, 3rd. : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_ordinal-number-en.html : @param $num the number :) 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 '') } (:~ : Pads an integer to a desired length by adding leading zeros : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_pad-integer-to-length.html : @param $integerToPad the integer to pad : @param $length the desired length :) 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)) } (:~ : Pads a string to a desired length : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_pad-string-to-length.html : @param $stringToPad the string to pad : @param $padChar the character(s) to use as padding : @param $length the desired length :) 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) } (:~ : A unique path to an XML node (or sequence of nodes) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_path-to-node-with-pos.html : @param $node the node sequence :) 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),']')) , '/') } (:~ : A path to an XML node (or sequence of nodes) : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_path-to-node.html : @param $nodes the node sequence :) 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), '/') } (:~ : Whether an XML node precedes another without being its ancestor : : @author W3C XML Query Working Group : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_precedes-not-ancestor.html : @param $a the first node : @param $b the second 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()) } (:~ : The previous day : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_previous-day.html : @param $date the date :) define function previous-day ( $date as xdt:anyAtomicType? ) as xs:date? { xs:date($date) - xdt:dayTimeDuration('P1D') } (:~ : Removes attributes from an XML fragment, based on name : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_remove-attributes-deep.html : @param $nodes the root(s) to start from : @param $names the names of the attributes to remove, or * for all attributes :) 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 } (:~ : Removes attributes from an XML element, based on name : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_remove-attributes.html : @param $elements the element(s) from which to remove the attributes : @param $names the names of the attributes to remove, or * for all attributes :) 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() } } (:~ : Removes descendant elements from an XML node, based on name : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_remove-elements-deep.html : @param $nodes root(s) to start from : @param $names the names of the elements to remove :) 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 } (:~ : Removes descendant XML elements but keeps their content : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_remove-elements-not-contents.html : @param $nodes the root(s) to start from : @param $names the names of the elements to remove :) 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 } (:~ : Removes child elements from an XML node, based on name : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_remove-elements.html : @param $elements the element(s) from which you wish to remove the children : @param $names the names of the child elements to remove :) 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))] } } (:~ : Repeats a string a given number of times : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_repeat-string.html : @param $stringToRepeat the string to repeat : @param $count the desired number of copies :) 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 '' } (:~ : Replaces the beginning of a string, up to a matched pattern : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_replace-beginning.html : @param $arg the entire string to change : @param $pattern the pattern of characters to replace up to : @param $replacement the replacement string :) 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) } (:~ : Updates the content of one or more elements : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_replace-element-values.html : @param $elements the elements whose content you wish to replace : @param $values the replacement values :) 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] } } (:~ : Replaces the first match of a pattern : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_replace-first.html : @param $arg the entire string to change : @param $pattern the pattern of characters to replace : @param $replacement the replacement string :) 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)) } (:~ : Performs multiple replacements, using pairs of replace parameters : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_replace-multi.html : @param $arg the string to manipulate : @param $changeFrom the sequence of strings or patterns to change from : @param $changeTo the sequence of strings to change to :) 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 } (:~ : Reverses the order of characters : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_reverse-string.html : @param $arg the string to reverse :) 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), '') } (:~ : Trims trailing whitespace : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_right-trim.html : @param $arg the string to trim :) define function right-trim ( $arg as xs:string? ) as xs:string { fn:replace($arg,'\s+$','') } (:~ : Returns the scheme from a URI : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_scheme-from-uri.html : @param $uri the URI :) define function scheme-from-uri ( $uri as xs:string? ) as xs:string? { fn:substring-before($uri,':') } (:~ : Whether two sequences have the same XML node content and/or values : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_sequence-deep-equal.html : @param $seq1 the first sequence : @param $seq2 the second sequence :) 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]) } (:~ : Whether two sequences contain the same XML nodes, regardless of order : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_sequence-node-equal-any-order.html : @param $seq1 the first sequence of nodes : @param $seq2 the second sequence of nodes :) define function sequence-node-equal-any-order ( $seq1 as node()* , $seq2 as node()* ) as xs:boolean { fn:not( ($seq1 except $seq2, $seq2 except $seq1)) } (:~ : Whether two sequences contain the same XML nodes, in the same order : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_sequence-node-equal.html : @param $seq1 the first sequence of nodes : @param $seq2 the second sequence of nodes :) 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] } (:~ : The sequence type that represents a sequence of nodes or values : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_sequence-type.html : @param $items the items whose sequence type you want to determine :) 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 '') } (:~ : The siblings of an XML element that have the same name : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_siblings-same-name.html : @param $element the node :) define function siblings-same-name ( $element as element()? ) as element()* { $element/../*[fn:node-name(.) = fn:node-name($element)] except $element } (:~ : The siblings of an XML node : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_siblings.html : @param $node the node :) define function siblings ( $node as node()? ) as node()* { $node/../node() except $node } (:~ : Sorts a sequence of numeric values or nodes : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_sort-as-numeric.html : @param $seq the sequence to sort :) define function sort-as-numeric ( $seq as item()* ) as item()* { for $item in $seq order by fn:number($item) return $item } (:~ : Sorts a sequence of values or nodes regardless of capitalization : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_sort-case-insensitive.html : @param $seq the sequence to sort :) define function sort-case-insensitive ( $seq as item()* ) as item()* { for $item in $seq order by fn:upper-case(fn:string($item)) return $item } (:~ : Sorts a sequence of nodes in document order : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_sort-document-order.html : @param $seq the sequence to sort :) define function sort-document-order ( $seq as node()* ) as node()* { $seq/. } (:~ : Sorts a sequence of values or nodes : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_sort.html : @param $seq the sequence to sort :) define function sort ( $seq as item()* ) as item()* { for $item in $seq order by $item return $item } (:~ : Performs substring-after, returning the entire string if it does not contain the delimiter : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_substring-after-if-contains.html : @param $arg the string to substring : @param $delim the delimiter :) 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 } (:~ : The substring after the last text that matches a regex : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_substring-after-last-match.html : @param $arg the string to substring : @param $regex the regular expression :) define function substring-after-last-match ( $arg as xs:string? , $regex as xs:string ) as xs:string { fn:replace($arg,fn:concat('^.*',$regex),'') } (:~ : The substring after the last occurrence of a delimiter : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_substring-after-last.html : @param $arg the string to substring : @param $delim the delimiter :) 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)),'') } (:~ : The substring after the first text that matches a regex : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_substring-after-match.html : @param $arg the string to substring : @param $regex the regular expression :) define function substring-after-match ( $arg as xs:string? , $regex as xs:string ) as xs:string? { fn:replace($arg,fn:concat('^.*?',$regex),'') } (:~ : Performs substring-before, returning the entire string if it does not contain the delimiter : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_substring-before-if-contains.html : @param $arg the string to substring : @param $delim the delimiter :) 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 } (:~ : The substring after the first text that matches a regex : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_substring-before-last-match.html : @param $arg the string to substring : @param $regex the regular expression :) define function substring-before-last-match ( $arg as xs:string? , $regex as xs:string ) as xs:string? { fn:replace($arg,fn:concat('^(.*)',$regex,'.*'),'$1') } (:~ : The substring before the last occurrence of a delimiter : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_substring-before-last.html : @param $arg the string to substring : @param $delim the delimiter :) 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 '' } (:~ : The substring before the last text that matches a regex : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_substring-before-match.html : @param $arg the string to substring : @param $regex the regular expression :) define function substring-before-match ( $arg as xs:string? , $regex as xs:string ) as xs:string { fn:tokenize($arg,$regex)[1] } (:~ : Construct a time from an hour, minute and second : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_time.html : @param $hour the hour : @param $minute the minute : @param $second the second :) 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))) } (:~ : Converts an xs:dayTimeDuration into a timezone like "-05:00" or "Z" : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_timezone-from-duration.html : @param $duration the duration :) 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')) } (:~ : The total number of days in a dayTimeDuration : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_total-days-from-duration.html : @param $duration the duration :) define function total-days-from-duration ( $duration as xdt:dayTimeDuration? ) as xs:decimal? { $duration div xdt:dayTimeDuration('P1D') } (:~ : The total number of hours in a dayTimeDuration : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_total-hours-from-duration.html : @param $duration the duration :) define function total-hours-from-duration ( $duration as xdt:dayTimeDuration? ) as xs:decimal? { $duration div xdt:dayTimeDuration('PT1H') } (:~ : The total number of minutes in a dayTimeDuration : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_total-minutes-from-duration.html : @param $duration the duration :) define function total-minutes-from-duration ( $duration as xdt:dayTimeDuration? ) as xs:decimal? { $duration div xdt:dayTimeDuration('PT1M') } (:~ : The total number of months in a yearMonthDuration : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_total-months-from-duration.html : @param $duration the duration :) define function total-months-from-duration ( $duration as xdt:yearMonthDuration? ) as xs:decimal? { $duration div xdt:yearMonthDuration('P1M') } (:~ : The total number of seconds in a dayTimeDuration : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_total-seconds-from-duration.html : @param $duration the duration :) define function total-seconds-from-duration ( $duration as xdt:dayTimeDuration? ) as xs:decimal? { $duration div xdt:dayTimeDuration('PT1S') } (:~ : The total number of years in a yearMonthDuration : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_total-years-from-duration.html : @param $duration the duration :) define function total-years-from-duration ( $duration as xdt:yearMonthDuration? ) as xs:decimal? { $duration div xdt:yearMonthDuration('P1Y') } (:~ : Trims leading and trailing whitespace : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_trim.html : @param $arg the string to trim :) define function trim ( $arg as xs:string? ) as xs:string { fn:replace(fn:replace($arg,'\s+$',''),'^\s+','') } (:~ : Updates the attribute value of an XML element : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_update-attributes.html : @param $elements the element(s) for which you wish to update the attribute : @param $attrNames the name(s) of the attribute(s) to add : @param $attrValues the value(s) of the attribute(s) to add :) 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() } } (:~ : The values in one sequence that aren't in another sequence : : @author W3C XML Query Working Group : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_value-except.html : @param $arg1 the first sequence : @param $arg2 the second sequence :) define function value-except ( $arg1 as xdt:anyAtomicType* , $arg2 as xdt:anyAtomicType* ) as xdt:anyAtomicType* { fn:distinct-values($arg1[fn:not(.=$arg2)]) } (:~ : The intersection of two sequences of values : : @author W3C XML Query Working Group : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_value-intersect.html : @param $arg1 the first sequence : @param $arg2 the second sequence :) define function value-intersect ( $arg1 as xdt:anyAtomicType* , $arg2 as xdt:anyAtomicType* ) as xdt:anyAtomicType* { fn:distinct-values($arg1[.=$arg2]) } (:~ : The union of two sequences of values : : @author W3C XML Query Working Group : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_value-union.html : @param $arg1 the first sequence : @param $arg2 the second sequence :) define function value-union ( $arg1 as xdt:anyAtomicType* , $arg2 as xdt:anyAtomicType* ) as xdt:anyAtomicType* { fn:distinct-values(($arg1, $arg2)) } (:~ : The number of words : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_word-count.html : @param $arg the string to measure :) define function word-count ( $arg as xs:string? ) as xs:integer { fn:count(fn:tokenize($arg, '\W+')[. != '']) } (:~ : Turns a string of words into camelCase : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_words-to-camel-case.html : @param $arg the string to modify :) 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)) ,'') } (:~ : Wraps a sequence of atomic values in XML elements : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_wrap-values-in-elements.html : @param $values the values to wrap in elements : @param $elementName the name of the elements to construct :) define function wrap-values-in-elements ( $values as xdt:anyAtomicType* , $elementName as xs:QName ) as element()* { for $value in $values return element {$elementName} {$value} } (:~ : Construct a yearMonthDuration from a number of years and months : : @author Priscilla Walmsley, Datypic : @version 1.0 : @see http://www.xqueryfunctions.com/xq/functx_yearmonthduration.html : @param $years the number of years : @param $months the number of months :) 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)) }