Project

Profile

Help

Bug #6348 » shared-functions.xslt

Johan Gheys, 2024-02-15 11:48

 
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:arch="http://expath.org/ns/archive"
xmlns:array="http://www.w3.org/2005/xpath-functions/array"
xmlns:ext="http://www.infrabel.be/saxon/extension"
xmlns:file="http://expath.org/ns/file"
xmlns:map="http://www.w3.org/2005/xpath-functions/map"
xmlns:math="http://www.w3.org/2005/xpath-functions/math"
xmlns:saxon="http://saxon.sf.net/"
xmlns:shared="urn:shared-functions"
xmlns:thread="java:java.lang.Thread"
xmlns:uuid="java:java.util.UUID"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
extension-element-prefixes="saxon"
version="2.0">
<xsl:import href="/shared/extension-function-api.xslt"/>
<xsl:variable name="apos" as="xs:string">'</xsl:variable>
<xsl:variable name="quot" as="xs:string">"</xsl:variable>
<xsl:variable name="pipe" select="'|'"/>
<xsl:variable name="separator" select="'-'"/>
<xsl:variable name="EOL" select="'&#10;'"/>
<xsl:variable name="TAB" select="'&#x9;'"/>
<xsl:variable name="TNO" select="'2078-12-31'"/>
<xsl:variable name="output-parameters" select="shared:output-parameters()"/>
<xsl:key name="with-id" match="*" use="@id"/>
<xsl:key name="with-key" match="*" use="@key"/>
<xsl:strip-space elements="*"/>

<xsl:template mode="shared:copy-no-namespaces" match="@*|*">
<xsl:copy copy-namespaces="no">
<xsl:apply-templates select="@*|*" mode="#current"/>
</xsl:copy>
</xsl:template>

<xsl:function name="shared:acosd" as="xs:double?">
<xsl:param name="cos" as="xs:double?"/>
<xsl:sequence select="if (exists($cos)) then shared:radians-to-degrees(math:acos($cos)) else ()"/>
</xsl:function>

<xsl:function name="shared:add-digest" as="element()*">
<xsl:param name="element" as="element()*"/>
<xsl:for-each select="$element">
<xsl:sequence select="shared:add-or-update-attributes(., 'digest', shared:uuid(.))"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:add-digest-for-element-without-validity" as="element()*">
<xsl:param name="element" as="element()*"/>
<xsl:for-each select="$element">
<xsl:variable name="digest" select="shared:uuid(shared:remove-attributes(., ('validFromDate', 'validToDate')))"/>
<xsl:sequence select="shared:add-or-update-attributes(., 'digest', $digest)"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:add-or-update-attributes">
<xsl:param name="elements"/>
<xsl:param name="attrNames"/>
<xsl:param name="attrValues"/>
<xsl:for-each select="$elements">
<xsl:copy>
<xsl:sequence select="@*[not(name(.) = $attrNames)]"/>
<xsl:for-each select="$attrNames">
<xsl:variable name="seq" select="position()"/>
<xsl:attribute name="{.}" select="$attrValues[$seq]"/>
</xsl:for-each>
<xsl:sequence select="node()"/>
</xsl:copy>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:always-false" as="xs:boolean">
<xsl:sequence select="current-date() lt xs:date('2000-01-01')"/>
</xsl:function>

<xsl:function name="shared:asind" as="xs:double?">
<xsl:param name="sin" as="xs:double?"/>
<xsl:sequence select="if (exists($sin)) then shared:radians-to-degrees(math:asin($sin)) else ()"/>
</xsl:function>

<xsl:function name="shared:atand" as="xs:double?">
<xsl:param name="tan" as="xs:double?"/>
<xsl:sequence select="if (exists($tan)) then shared:radians-to-degrees(math:atan($tan)) else ()"/>
</xsl:function>

<xsl:function name="shared:camel-case" as="xs:string">
<xsl:param name="arg" as="xs:string?"/>
<xsl:variable name="camelCaseWords" as="xs:string*">
<xsl:for-each select="tokenize($arg, '\s+|_|-')">
<xsl:sequence select="if (position() eq 1) then lower-case(.) else concat(upper-case(substring(., 1, 1)), lower-case(substring(., 2)))"/>
</xsl:for-each>
</xsl:variable>
<xsl:sequence select="string-join($camelCaseWords, '')"/>
</xsl:function>

<xsl:function name="shared:chars" as="xs:string*">
<!-- Same as functx:chars -->
<xsl:param name="arg" as="xs:string?"/>
<xsl:sequence select="for $ch in string-to-codepoints($arg) return codepoints-to-string($ch)"/>
</xsl:function>

<xsl:function name="shared:clone" as="element()*">
<xsl:param name="pItem" as="element()*"/>
<xsl:for-each select="$pItem">
<!-- Delete unwanted namespace nodes like xmlns="" by adding namespace-uri() (e.g. when drawing a graphicalLevelCrossing)
See http://lenzconsulting.com/namespaces-in-xslt -->
<xsl:element name="{local-name()}" namespace="{namespace-uri()}">
<xsl:sequence select="@*"/>
<xsl:for-each select="*">
<xsl:sequence select="shared:clone(.)"/>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:clone" as="element()*">
<xsl:param name="pItem" as="element()*"/>
<xsl:param name="pValidFromDate" as="xs:string?"/>
<xsl:param name="pValidToDate" as="xs:string?"/>
<xsl:for-each select="$pItem">
<xsl:element name="{name()}" namespace="{namespace-uri()}">
<xsl:sequence select="@* except (@validFromDate, @validToDate)"/>
<xsl:if test="exists(@validFromDate) and exists($pValidFromDate)">
<xsl:attribute name="validFromDate" select="shared:max(($pValidFromDate, @validFromDate))"/>
</xsl:if>
<xsl:if test="exists(@validToDate) and exists($pValidToDate)">
<xsl:attribute name="validToDate" select="shared:min(($pValidToDate, @validToDate))"/>
</xsl:if>
<xsl:for-each select="*">
<xsl:sequence select="shared:clone(., $pValidFromDate, $pValidToDate)"/>
</xsl:for-each>
</xsl:element>
</xsl:for-each>
</xsl:function>

<xsl:template name="shared:compile-stylesheet">
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="startTime" select="saxon:timestamp()"/>
<xsl:sequence select="$startTime[shared:always-false()]"/>
<xsl:sequence select="shared:compile-stylesheet($options)"/>
<xsl:sequence select="shared:log-execution-time($startTime)"/>
</xsl:template>

<xsl:function name="shared:compile-stylesheet">
<xsl:param name="options" as="map(*)"/>
<xsl:sequence select="shared:log('DEBUG', concat('Compiling ', $options?stylesheet, ' to ', $options?sef))"/>
<xsl:variable name="executable" select="shared:find-java()"/>
<xsl:variable name="arguments" as="xs:string*">
<xsl:sequence select="'-cp'"/>
<xsl:sequence select="shared:find-classpath()"/>
<xsl:sequence select="'net.sf.saxon.Transform'"/>
<xsl:sequence select="'-target:JS'"/>
<xsl:sequence select="'-nogo'"/>
<xsl:sequence select="'-relocate:on'"/>
<xsl:sequence select="'-ns:##html5'"/>
<xsl:sequence select="concat('-xsl:', $options?stylesheet)"/>
<xsl:sequence select="concat('-export:', $options?sef)"/>
</xsl:variable>
<saxon:do action="ext:exec-external($executable, $arguments)"/>
</xsl:function>

<xsl:function name="shared:copy">
<xsl:param name="source" as="xs:string"/>
<xsl:param name="target" as="xs:string"/>
<xsl:if test="file:exists($source)">
<xsl:sequence select="shared:log('DEBUG', concat('Copying ', $source, ' to ', $target))"/>
<xsl:sequence select="file:copy($source, $target)"/>
</xsl:if>
</xsl:function>

<xsl:function name="shared:copy-children">
<xsl:param name="source" as="xs:string"/>
<xsl:param name="target" as="xs:string"/>
<xsl:if test="file:is-dir($source) and file:is-dir($target)">
<xsl:for-each select="shared:list($source)">
<xsl:sequence select="shared:copy(concat($source, .), $target)"/>
</xsl:for-each>
</xsl:if>
</xsl:function>

<xsl:function name="shared:copy-files">
<xsl:param name="source" as="xs:string"/>
<xsl:param name="target" as="xs:string"/>
<xsl:if test="file:is-dir($source) and file:is-dir($target)">
<xsl:for-each select="shared:list-files($source)">
<xsl:sequence select="shared:copy(concat($source, .), $target)"/>
</xsl:for-each>
</xsl:if>
</xsl:function>

<xsl:function name="shared:cosd" as="xs:double?">
<xsl:param name="angleInDegrees" as="xs:double?"/>
<xsl:sequence select="if (exists($angleInDegrees)) then math:cos(shared:degrees-to-radians($angleInDegrees)) else ()"/>
</xsl:function>

<xsl:function name="shared:create-dir">
<xsl:param name="dir" as="xs:string"/>
<xsl:sequence select="shared:log('DEBUG', concat('Creating ', $dir))"/>
<xsl:sequence select="file:create-dir($dir)"/>
</xsl:function>

<xsl:function name="shared:cumulative-sum" as="item()*">
<xsl:param name="items" as="item()*"/>
<xsl:sequence select="shared:cumulative-sum-impl($items, 1, 0, ())"/>
</xsl:function>

<xsl:function name="shared:cumulative-sum-impl" as="item()*">
<xsl:param name="items" as="item()*"/>
<xsl:param name="index" as="xs:integer"/>
<xsl:param name="sum" as="item()"/>
<xsl:param name="result" as="item()*"/>
<xsl:variable name="item" as="item()?" select="$items[$index]"/>
<xsl:choose>
<xsl:when test="empty($item)">
<xsl:sequence select="$result"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="value" select="$item + $sum"/>
<xsl:variable name="next" select="$result, $value"/>
<xsl:sequence select="shared:cumulative-sum-impl($items, $index + 1, $value, $next)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="shared:dates-between" as="xs:string*">
<xsl:param name="from-date" as="xs:string"/>
<xsl:param name="to-date" as="xs:string"/>
<xsl:variable name="nb-of-midnights" select="xs:integer((xs:date($to-date) - xs:date($from-date)) div xs:dayTimeDuration('P1D'))"/>
<xsl:for-each select="0 to $nb-of-midnights">
<xsl:sequence select="shared:shift-day($from-date, .)"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:day-of-week" as="xs:integer">
<xsl:param name="date" as="xs:string"/>
<xsl:sequence select="xs:integer(format-date(xs:date($date), '[F1]', (), 'ISO', ()))"/>
</xsl:function>

<xsl:function name="shared:degrees-to-radians" as="xs:double">
<xsl:param name="angleInDegrees" as="xs:double"/>
<xsl:sequence select="$angleInDegrees div 180 * math:pi()"/>
</xsl:function>

<xsl:function name="shared:delete">
<xsl:param name="path" as="xs:string"/>
<xsl:sequence select="shared:delete($path, true())"/>
</xsl:function>

<xsl:function name="shared:delete">
<xsl:param name="path" as="xs:string"/>
<xsl:param name="recursive" as="xs:boolean"/>
<xsl:if test="file:exists($path)">
<xsl:sequence select="shared:log('DEBUG', concat('Deleting ', $path))"/>
<xsl:sequence select="file:delete($path, $recursive)"/>
</xsl:if>
</xsl:function>

<xsl:function name="shared:delete-files">
<xsl:param name="path" as="xs:string"/>
<xsl:if test="file:is-dir($path)">
<xsl:for-each select="shared:list-files($path)">
<xsl:sequence select="shared:delete(concat($path, .))"/>
</xsl:for-each>
</xsl:if>
</xsl:function>

<xsl:function name="shared:digits-from-string" as="xs:string?">
<xsl:param name="string" as="xs:string?"/>
<!-- Double Translate Method first proposed by Michael Kay -->
<xsl:for-each select="$string">
<xsl:sequence select="translate(., translate(., '0123456789', ''), '')"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:distinct-sorted-values" as="xs:anyAtomicType*">
<xsl:param name="sequence" as="xs:anyAtomicType*"/>
<xsl:param name="data-type" as="xs:string"/>
<xsl:for-each-group select="$sequence" group-by=".">
<xsl:sort select="current-grouping-key()" data-type="{$data-type}"/>
<xsl:sequence select="current-group()[1]"/>
</xsl:for-each-group>
</xsl:function>

<xsl:function name="shared:doc" as="document-node()?">
<xsl:param name="uri" as="xs:string"/>
<xsl:choose>
<xsl:when test="doc-available($uri)">
<xsl:sequence select="saxon:discard-document(doc($uri))"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="shared:log('DEBUG', concat('*** MISSING ', $uri))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="shared:download">
<xsl:param name="remoteSource" as="xs:string"/>
<xsl:param name="localTarget" as="xs:string"/>
<xsl:param name="hostname" as="xs:string"/>
<xsl:param name="username" as="xs:string"/>
<xsl:param name="password" as="xs:string?"/>
<xsl:sequence select="shared:log('DEBUG', concat('Downloading ', $remoteSource, ' from ', $hostname, ' to ', $localTarget))"/>
<xsl:sequence select="ext:download($remoteSource, $localTarget, $hostname, $username, $password)"/>
</xsl:function>

<xsl:function name="shared:dummy-doc" as="document-node()">
<xsl:document>
<dummy/>
</xsl:document>
</xsl:function>

<xsl:function name="shared:escape">
<xsl:param name="text" as="xs:string"/>
<xsl:sequence select="replace(replace(replace($text, '\|', '-pipe-'), '/', '-slash-'), '\\', '-backslash-')"/>
</xsl:function>

<xsl:function name="shared:extract-nodeIntervals" as="element()*">
<xsl:param name="entities" as="element()*"/>
<xsl:param name="minNodeSequenceNo" as="xs:integer"/>
<xsl:param name="maxNodeSequenceNo" as="xs:integer"/>
<xsl:variable name="nodeSequenceNos" as="xs:integer*"
select="$minNodeSequenceNo, sort(distinct-values($entities/(@fromNodeSequenceNo, @toNodeSequenceNo)!xs:integer(.)[. gt $minNodeSequenceNo and . lt $maxNodeSequenceNo])), $maxNodeSequenceNo"/>
<xsl:for-each select="$nodeSequenceNos">
<xsl:variable name="index" select="position()"/>
<xsl:if test="$index ne last()">
<xsl:sequence select="shared:nodeInterval(., $nodeSequenceNos[$index + 1])"/>
</xsl:if>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:extract-resource">
<xsl:param name="source" as="xs:string"/>
<xsl:param name="target" as="xs:string"/>
<xsl:sequence select="shared:log('DEBUG', concat('Extracting resource ', $source, ' to ', $target))"/>
<xsl:variable name="jar-uri" select="substring-before(static-base-uri(), '/')"/>
<xsl:variable name="source-uri" select="concat($jar-uri, $source)"/>
<xsl:sequence select="file:write-binary($target, saxon:read-binary-resource($source-uri))"/>
</xsl:function>

<xsl:function name="shared:extract-valid-from-dates" as="xs:string*">
<xsl:param name="entityVersions" as="element()*"/>
<xsl:variable name="validFromDates" as="xs:string*" select="distinct-values($entityVersions/@validFromDate)"/>
<xsl:variable name="validToDates" as="xs:string*" select="distinct-values($entityVersions/@validToDate[. != $TNO])"/>
<xsl:variable name="shiftedValidToDates" select="for $date in $validToDates return shared:next-day($date)"/>
<xsl:sequence select="sort(distinct-values(($validFromDates, $shiftedValidToDates)))"/>
</xsl:function>

<xsl:function name="shared:extract-validities" as="element()*">
<xsl:param name="entityVersions" as="element()*"/>
<xsl:variable name="minValidFromDate" select="shared:min($entityVersions/@validFromDate)"/>
<xsl:variable name="maxValidToDate" select="shared:max($entityVersions/@validToDate)"/>
<xsl:sequence select="shared:extract-validities($entityVersions, $minValidFromDate, $maxValidToDate)"/>
</xsl:function>

<xsl:function name="shared:extract-validities" as="element()*">
<xsl:param name="entityVersions" as="element()*"/>
<xsl:param name="refValidFromDate" as="xs:string"/>
<xsl:param name="refValidToDate" as="xs:string"/>
<xsl:variable name="validFromDates" as="xs:string*" select="distinct-values($entityVersions/@validFromDate[. gt $refValidFromDate and . le $refValidToDate])"/>
<xsl:variable name="validToDates" as="xs:string*" select="distinct-values($entityVersions/@validToDate[. ge $refValidFromDate and . lt $refValidToDate])"/>
<xsl:variable name="shiftedValidFromDates" as="xs:string*" select="$validFromDates!shared:previous-day(.)"/>
<xsl:variable name="shiftedValidToDates" as="xs:string*" select="$validToDates!shared:next-day(.)"/>
<xsl:variable name="mergedValidFromDates" as="xs:string*" select="distinct-values(($refValidFromDate, $validFromDates, $shiftedValidToDates))"/>
<xsl:variable name="mergedValidToDates" as="xs:string*" select="distinct-values(($refValidToDate, $validToDates, $shiftedValidFromDates))"/>
<xsl:variable name="dates" as="xs:string*" select="sort(($mergedValidFromDates, $mergedValidToDates))"/>
<xsl:for-each select="$dates">
<xsl:variable name="index" select="position()"/>
<xsl:if test="($index mod 2) = 1">
<xsl:sequence select="shared:validity(., $dates[$index + 1])"/>
</xsl:if>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:file-extension" as="xs:string?">
<xsl:param name="uri" as="xs:string?"/>
<!-- \ escape char in regex is needed for '.' -->
<xsl:sequence select="tokenize($uri, '\.')[last()]"/>
</xsl:function>

<xsl:function name="shared:find-classpath" as="xs:string">
<xsl:sequence select="system-property('java.class.path')"/>
</xsl:function>

<xsl:function name="shared:find-java" as="xs:string">
<xsl:sequence select="string-join((system-property('java.home'), 'bin', 'java'), system-property('file.separator'))"/>
</xsl:function>

<xsl:function name="shared:fo-to-pdf">
<xsl:param name="foFileName" as="xs:string"/>
<xsl:param name="pdfFileName" as="xs:string"/>
<xsl:sequence select="shared:log('DEBUG', concat('Transforming ', $foFileName, ' to ', $pdfFileName))"/>
<xsl:sequence select="ext:fo-to-pdf($foFileName, $pdfFileName)"/>
</xsl:function>

<xsl:function name="shared:get-angle-from-points" as="xs:double">
<xsl:param name="points" as="xs:double*"/>
<xsl:variable name="x1" select="$points[1]"/>
<xsl:variable name="y1" select="$points[2]"/>
<xsl:variable name="x2" select="$points[3]"/>
<xsl:variable name="y2" select="$points[4]"/>
<xsl:choose>
<xsl:when test="$x1 eq $x2 and $y1 eq $y2">
<xsl:sequence select="0"/>
</xsl:when>
<xsl:when test="$x1 eq $x2 and $y1 ne $y2">
<xsl:sequence select="-90"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="tan" select="($y2 - $y1) div ($x2 - $x1)"/>
<xsl:sequence select="shared:atand($tan)"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="shared:get-attribute">
<xsl:param name="element"/>
<xsl:param name="attrName"/>
<xsl:for-each select="$element">
<xsl:sequence select="@*[name(.) = $attrName]"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:get-nodeIntervals" as="element()*">
<xsl:param name="nodeRef" as="element()*"/>
<xsl:param name="entityRefs" as="element()*"/>
<xsl:sequence select="shared:extract-nodeIntervals($entityRefs, $nodeRef[1]/@sequenceNo, $nodeRef[last()]/@sequenceNo)"/>
</xsl:function>

<xsl:function name="shared:get-validities" as="element()*">
<xsl:param name="element" as="element()"/>
<xsl:param name="entityVersions" as="element()*"/>
<xsl:for-each select="$element">
<xsl:sequence select="shared:extract-validities($entityVersions, @validFromDate, @validToDate)"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:get-validities" as="element()*">
<xsl:param name="element" as="element()"/>
<xsl:param name="entityVersions" as="element()*"/>
<xsl:param name="purgeDate" as="xs:string"/>
<xsl:sequence select="shared:get-validities($element, $entityVersions)[@validToDate ge $purgeDate]"/>
</xsl:function>

<xsl:function name="shared:get-vector-bit" as="xs:integer">
<xsl:param name="vector" as="element()*"/>
<xsl:param name="layer" as="xs:string"/>
<xsl:param name="date" as="xs:string"/>
<xsl:variable name="vectorBit" as="xs:string?">
<xsl:for-each select="$vector">
<xsl:variable name="refVector" select="@*[local-name() eq concat($layer, 'Vector')]"/>
<xsl:if test="exists($refVector)">
<xsl:variable name="dateTo" select="shared:shift-day(@dateFrom, string-length($refVector) - 1)"/>
<xsl:if test="$date ge @dateFrom and $date le $dateTo">
<xsl:variable name="index" select="days-from-duration(xs:date($date) - xs:date(@dateFrom)) + 1"/>
<xsl:sequence select="substring($refVector, $index, 1)"/>
</xsl:if>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:sequence select="xs:integer(($vectorBit, '0')[1])"/>
</xsl:function>

<xsl:function name="shared:get-vector-dates" as="xs:string*">
<xsl:param name="vector" as="element()*"/>
<xsl:param name="layer" as="xs:string"/>
<xsl:for-each select="$vector">
<xsl:variable name="refVector" select="@*[local-name() eq concat($layer, 'Vector')]"/>
<xsl:if test="exists($refVector)">
<xsl:variable name="dateFrom" select="@dateFrom"/>
<xsl:variable name="vectorBit" select="shared:chars($refVector)"/>
<xsl:for-each select="$vectorBit">
<xsl:sequence select="if (. ne '0') then shared:shift-day($dateFrom, position() - 1) else ()"/>
</xsl:for-each>
</xsl:if>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:get-vector-dates-for-layerVPK" as="xs:string*">
<xsl:param name="layerVPK" as="element()"/>
<xsl:param name="layer" as="xs:string"/>
<xsl:variable name="refVector" select="$layerVPK/*[local-name() eq concat($layer, 'Layer')]/*:vector"/>
<xsl:for-each select="$refVector">
<xsl:variable name="dateFrom" select="@dateFrom"/>
<xsl:variable name="vectorBit" select="shared:chars(@bitValue)"/>
<xsl:for-each select="$vectorBit">
<xsl:sequence select="if (. ne '0') then shared:shift-day($dateFrom, position() - 1) else ()"/>
</xsl:for-each>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:group-dates-by-week" as="element(week)*">
<xsl:param name="date" as="xs:string*"/>
<xsl:for-each-group select="$date" group-adjacent="shared:week-of-year(.)">
<xsl:element name="week">
<xsl:attribute name="id" select="current-grouping-key()"/>
<xsl:value-of select="current-group()"/>
</xsl:element>
</xsl:for-each-group>
</xsl:function>

<xsl:function name="shared:has-result-vector-running-days" as="xs:boolean">
<xsl:param name="vector" as="element()"/>
<xsl:sequence select="string-length(translate($vector/@resultVector, '0', '')) gt 0"/>
</xsl:function>

<xsl:function name="shared:has-validity-overlap" as="xs:boolean">
<xsl:param name="element-1" as="element()"/>
<xsl:param name="element-2" as="element()"/>
<xsl:sequence select="$element-1/(@validToDate, @dateTo, @toDate) ge $element-2/(@validFromDate, @dateFrom, @fromDate) and $element-2/(@validToDate, @dateTo, @toDate) ge $element-1/(@validFromDate, @dateFrom, @fromDate)"/>
</xsl:function>

<xsl:function name="shared:header" as="element()*">
<xsl:param name="element" as="element()*"/>
<xsl:for-each select="$element">
<xsl:copy>
<xsl:sequence select="@*"/>
</xsl:copy>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:in-filter" as="xs:boolean">
<xsl:param name="vector" as="element()*"/>
<xsl:param name="layer" as="xs:string"/>
<xsl:param name="date" as="xs:string"/>
<xsl:variable name="resultBit" select="shared:get-vector-bit($vector, $layer, $date)"/>
<xsl:sequence select="$resultBit ne 0"/>
</xsl:function>

<xsl:function name="shared:is-absolute-uri" as="xs:boolean">
<xsl:param name="uri" as="xs:string?"/>
<xsl:sequence select="matches($uri, '^[a-z]+:')"/>
</xsl:function>

<xsl:function name="shared:is-valid" as="xs:boolean">
<xsl:param name="entity" as="element()"/>
<xsl:param name="validityDate" as="xs:string?"/>
<xsl:sequence select="$validityDate ge $entity/@validFromDate and $validityDate le $entity/@validToDate"/>
</xsl:function>

<xsl:template name="shared:json-to-xml">
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="startTime" select="saxon:timestamp()"/>
<xsl:sequence select="$startTime[shared:always-false()]"/>
<xsl:call-template name="shared:write">
<xsl:with-param name="output" select="shared:json-to-xml($options)"/>
</xsl:call-template>
<xsl:sequence select="shared:log-execution-time($startTime)"/>
</xsl:template>

<xsl:function name="shared:json-to-xml" as="map(*)">
<xsl:param name="options" as="map(*)"/>
<xsl:sequence select="shared:log('DEBUG', concat('Transforming ', $options?source, ' to ', $options?target))"/>
<xsl:map-entry key="$options?target">
<xsl:document>
<xsl:sequence select="json-to-xml(unparsed-text($options?source))"/>
</xsl:document>
</xsl:map-entry>
</xsl:function>

<xsl:function name="shared:key" as="node()*">
<xsl:param name="key-name" as="xs:string"/>
<xsl:param name="key-value" as="xs:anyAtomicType*"/>
<xsl:param name="top" as="node()?"/>
<xsl:sequence select="$top/key($key-name, $key-value)"/>
</xsl:function>

<xsl:function name="shared:list" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:sequence select="shared:list($dir, false(), '*')"/>
</xsl:function>

<xsl:function name="shared:list" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:param name="recursive" as="xs:boolean"/>
<xsl:sequence select="shared:list($dir, $recursive, '*')"/>
</xsl:function>

<xsl:function name="shared:list" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:param name="recursive" as="xs:boolean"/>
<xsl:param name="pattern" as="xs:string"/>
<xsl:if test="file:exists($dir)">
<xsl:for-each select="file:list($dir, $recursive, $pattern)">
<xsl:sort select="."/>
<xsl:sequence select="translate(., '\', '/')"/>
</xsl:for-each>
</xsl:if>
</xsl:function>

<xsl:function name="shared:list-files" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:sequence select="shared:list-files($dir, false(), '*')"/>
</xsl:function>

<xsl:function name="shared:list-files" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:param name="recursive" as="xs:boolean"/>
<xsl:sequence select="shared:list-files($dir, $recursive, '*')"/>
</xsl:function>

<xsl:function name="shared:list-files" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:param name="recursive" as="xs:boolean"/>
<xsl:param name="pattern" as="xs:string"/>
<xsl:sequence select="shared:list($dir, $recursive, $pattern)[file:is-file(concat($dir, .))]"/>
</xsl:function>

<xsl:function name="shared:list-folders" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:sequence select="shared:list-folders($dir, false(), '*')"/>
</xsl:function>

<xsl:function name="shared:list-folders" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:param name="recursive" as="xs:boolean"/>
<xsl:sequence select="shared:list-folders($dir, $recursive, '*')"/>
</xsl:function>

<xsl:function name="shared:list-folders" as="xs:string*">
<xsl:param name="dir" as="xs:string"/>
<xsl:param name="recursive" as="xs:boolean"/>
<xsl:param name="pattern" as="xs:string"/>
<xsl:sequence select="shared:list($dir, $recursive, $pattern)[file:is-dir(concat($dir, .))]"/>
</xsl:function>

<xsl:function name="shared:log" as="empty-sequence()">
<xsl:param name="level" as="xs:string"/>
<xsl:param name="message" as="item()*"/>
<xsl:sequence select="ext:log($level, serialize($message))"/>
</xsl:function>

<xsl:function name="shared:log-error" as="empty-sequence()">
<xsl:param name="description" as="xs:string"/>
<xsl:param name="module" as="xs:string"/>
<xsl:param name="line-number" as="xs:integer?"/>
<xsl:sequence select="shared:log('ERROR', concat('description: ', $description))"/>
<xsl:sequence select="shared:log('ERROR', concat('module: ', $module))"/>
<xsl:sequence select="shared:log('ERROR', concat('line-number: ', $line-number))"/>
</xsl:function>

<xsl:function name="shared:log-execution-time" as="empty-sequence()">
<xsl:param name="startTime" as="xs:dateTime"/>
<!-- Use xs:dayTimeDuration instead of xs:duration as said in Saxon's documentation -->
<xsl:sequence select="shared:log('DEBUG', concat('Execution time: ', (saxon:timestamp() - $startTime) div xs:dayTimeDuration('PT1S'), 's'))"/>
</xsl:function>

<xsl:function name="shared:log-stylesheet-params" as="empty-sequence()">
<xsl:param name="stylesheet-params" as="map(*)?"/>
<xsl:if test="exists($stylesheet-params)">
<xsl:for-each select="map:keys($stylesheet-params)">
<xsl:sort select="string(.)"/>
<xsl:variable name="value" select="map:get($stylesheet-params, .)"/>
<xsl:variable name="parameter-value"
select="
if (empty($value)) then '()' else
if ($value instance of map(*)+) then concat('map(#', count($value), ')') else
if ($value instance of node()) then ($value/saxon:system-id(), path($value))[1] else
if (contains(lower-case(string(.)), 'password')) then replace($value, '.', '*') else
string-join($value, ' ')"/>
<xsl:sequence select="shared:log('INFO', concat($TAB, 'parameter ',., '=',$parameter-value))"/>
</xsl:for-each>
</xsl:if>
</xsl:function>

<xsl:function name="shared:log-transform-options" as="empty-sequence()">
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="source-location" select="map:get($options, 'source-location')"/>
<xsl:variable name="base-output-uri" select="map:get($options, 'base-output-uri')"/>
<xsl:variable name="stylesheet-location" select="map:get($options, 'stylesheet-location')"/>
<xsl:variable name="stylesheet-params" select="map:get($options, 'stylesheet-params')"/>
<xsl:choose>
<xsl:when test="exists($source-location) and exists($base-output-uri)">
<xsl:sequence select="shared:log('INFO', concat('Transforming ',$source-location))"/>
<xsl:sequence select="shared:log('INFO', concat($TAB, 'into ',$base-output-uri))"/>
<xsl:sequence select="shared:log('INFO', concat($TAB, 'using ',$stylesheet-location))"/>
</xsl:when>
<xsl:when test="exists($source-location) and empty($base-output-uri)">
<xsl:sequence select="shared:log('INFO', concat('Transforming ',$source-location))"/>
<xsl:sequence select="shared:log('INFO', concat($TAB, 'using ',$stylesheet-location))"/>
</xsl:when>
<xsl:when test="empty($source-location) and exists($base-output-uri)">
<xsl:sequence select="shared:log('INFO', concat('Generating ',$base-output-uri))"/>
<xsl:sequence select="shared:log('INFO', concat($TAB, 'using ',$stylesheet-location))"/>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="shared:log('INFO', concat('Launching ',$stylesheet-location))"/>
</xsl:otherwise>
</xsl:choose>
<xsl:sequence select="shared:log-stylesheet-params($stylesheet-params)"/>
</xsl:function>

<xsl:function name="shared:map-to-json" as="map(*)*">
<xsl:param name="map" as="map(*)*"/>
<xsl:for-each select="$map">
<xsl:variable name="current-map" select="."/>
<xsl:map>
<xsl:for-each select="map:keys(.)">
<xsl:variable name="value" select="$current-map(.)"/>
<xsl:choose>
<xsl:when test="count($value) le 1">
<xsl:map-entry key="." select="$value"/>
</xsl:when>
<xsl:otherwise>
<xsl:map-entry key="." select="array{$value}"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:map>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:max" as="item()?">
<xsl:param name="pItems" as="item()*"/>
<xsl:for-each select="$pItems">
<xsl:sort select="." order="descending"/>
<xsl:if test="position() eq 1">
<xsl:sequence select="."/>
</xsl:if>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:merge-versions" as="element()*">
<xsl:param name="versions" as="element()*"/>
<xsl:variable name="versions-xml">
<xsl:sequence select="$versions"/>
</xsl:variable>
<xsl:for-each-group select="$versions-xml/*" group-starting-with="*[not(preceding-sibling::*[1]/@digest = @digest and shared:next-day(preceding-sibling::*[1]/@validToDate) = @validFromDate)]">
<xsl:choose>
<xsl:when test="count(current-group()) = 1">
<xsl:sequence select="current-group()"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="newValidFromDate" select="current-group()[1]/@validFromDate"/>
<xsl:variable name="newValidToDate" select="current-group()[last()]/@validToDate"/>
<xsl:sequence select="shared:add-or-update-attributes(current-group()[1], ('validFromDate', 'validToDate'), ($newValidFromDate, $newValidToDate))"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:function>

<xsl:function name="shared:merge-versions-without-digest" as="element()*">
<xsl:param name="versions" as="element()*"/>
<xsl:choose>
<xsl:when test="count($versions) le 1">
<xsl:sequence select="$versions"/>
</xsl:when>
<xsl:otherwise>
<xsl:variable name="versionsWithDigest" select="shared:add-digest-for-element-without-validity($versions)"/>
<xsl:variable name="mergedVersions" select="shared:merge-versions($versionsWithDigest)"/>
<xsl:sequence select="shared:remove-attributes($mergedVersions, 'digest')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="shared:min" as="item()?">
<xsl:param name="pItems" as="item()*"/>
<xsl:for-each select="$pItems">
<xsl:sort select="." order="ascending"/>
<xsl:if test="position() eq 1">
<xsl:sequence select="."/>
</xsl:if>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:month-of-year" as="xs:string">
<xsl:param name="date" as="xs:string"/>
<xsl:sequence select="format-date(xs:date($date), '[Y]-[M,2]')"/>
</xsl:function>

<xsl:function name="shared:move" as="empty-sequence()">
<xsl:param name="source" as="xs:string"/>
<xsl:param name="target" as="xs:string"/>
<xsl:if test="file:exists($source) and $source ne $target">
<xsl:sequence select="shared:log('DEBUG', concat('Moving ', $source, ' to ', $target))"/>
<xsl:sequence select="file:move($source, $target)"/>
</xsl:if>
</xsl:function>

<xsl:function name="shared:move-files">
<xsl:param name="source" as="xs:string"/>
<xsl:param name="target" as="xs:string"/>
<xsl:if test="file:is-dir($source) and file:is-dir($target)">
<xsl:for-each select="shared:list-files($source)">
<xsl:sequence select="shared:move(concat($source, .), $target)"/>
</xsl:for-each>
</xsl:if>
</xsl:function>

<xsl:function name="shared:next-day" as="xs:string">
<xsl:param name="date" as="xs:string"/>
<xsl:sequence select="shared:shift-day($date, 1)"/>
</xsl:function>

<xsl:function name="shared:nodeInterval" as="element()">
<xsl:param name="fromNodeSequenceNo" as="xs:integer"/>
<xsl:param name="toNodeSequenceNo" as="xs:integer"/>
<xsl:element name="nodeInterval">
<xsl:attribute name="fromNodeSequenceNo" select="$fromNodeSequenceNo"/>
<xsl:attribute name="toNodeSequenceNo" select="$toNodeSequenceNo"/>
</xsl:element>
</xsl:function>

<xsl:function name="shared:open-file" as="node()?">
<xsl:param name="pathAndFileName" as="xs:string?"/>
<xsl:for-each select="$pathAndFileName">
<xsl:variable name="uri" select="shared:path-to-uri(.)"/>
<xsl:sequence select="shared:doc($uri)"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:open-file" as="document-node()?">
<xsl:param name="baseURI" as="xs:string?"/>
<xsl:param name="fileName" as="xs:string?"/>
<xsl:if test="exists($baseURI) and exists($fileName)">
<xsl:variable name="pathAndFileName" select="concat($baseURI, $fileName)"/>
<xsl:sequence select="shared:open-file($pathAndFileName)"/>
</xsl:if>
</xsl:function>

<xsl:function name="shared:open-file-without-logging" as="document-node()?">
<xsl:param name="pathAndFileName" as="xs:string"/>
<xsl:variable name="uri" select="shared:path-to-uri($pathAndFileName)"/>
<xsl:sequence select="if (doc-available($uri)) then saxon:discard-document(doc($uri)) else ()"/>
</xsl:function>

<xsl:function name="shared:open-file-without-logging" as="document-node()?">
<xsl:param name="baseURI" as="xs:string?"/>
<xsl:param name="fileName" as="xs:string?"/>
<xsl:if test="exists($baseURI) and exists($fileName)">
<xsl:variable name="pathAndFileName" select="concat($baseURI, $fileName)"/>
<xsl:sequence select="shared:open-file-without-logging($pathAndFileName)"/>
</xsl:if>
</xsl:function>

<xsl:function name="shared:open-files" as="document-node()*">
<xsl:param name="baseURI" as="xs:string"/>
<xsl:param name="pattern" as="xs:string"/>
<xsl:variable name="uri" select="shared:path-to-uri($baseURI)"/>
<xsl:variable name="files" select="collection(concat($uri, '?select=', $pattern))"/>
<xsl:sequence select="shared:log('DEBUG', concat('Nb of loaded files = ', count($files)))"/>
<xsl:for-each select="$files">
<xsl:sort select="saxon:system-id()"/>
<xsl:sequence select="saxon:discard-document(.)"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:output-parameters" as="map(*)">
<xsl:map>
<xsl:map-entry key="'xml'">
<xsl:map>
<xsl:map-entry key="'method'" select="'xml'"/>
<xsl:map-entry key="'omit-xml-declaration'" select="false()"/>
<xsl:map-entry key="'indent'" select="true()"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'indent-spaces')" select="0"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'line-length')" select="100000"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'attribute-order')" select="'*'"/>
</xsl:map>
</xsl:map-entry>
<xsl:map-entry key="'uuid'">
<xsl:map>
<xsl:map-entry key="'method'" select="'xml'"/>
<xsl:map-entry key="'omit-xml-declaration'" select="true()"/>
<xsl:map-entry key="'indent'" select="true()"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'indent-spaces')" select="0"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'line-length')" select="100000"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'attribute-order')" select="'*'"/>
</xsl:map>
</xsl:map-entry>
<xsl:map-entry key="'html'">
<xsl:map>
<xsl:map-entry key="'method'" select="'html'"/>
<xsl:map-entry key="'indent'" select="true()"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'indent-spaces')" select="0"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'line-length')" select="100000"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'attribute-order')" select="'*'"/>
</xsl:map>
</xsl:map-entry>
<xsl:map-entry key="'xhtml'">
<xsl:map>
<xsl:map-entry key="'method'" select="'xhtml'"/>
<xsl:map-entry key="'indent'" select="true()"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'indent-spaces')" select="0"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'line-length')" select="100000"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'attribute-order')" select="'*'"/>
</xsl:map>
</xsl:map-entry>
<xsl:map-entry key="'json'">
<xsl:map>
<xsl:map-entry key="'method'" select="'json'"/>
<xsl:map-entry key="QName('http://saxon.sf.net/', 'property-order')" select="'*'"/>
</xsl:map>
</xsl:map-entry>
<xsl:map-entry key="'txt'">
<xsl:map>
<xsl:map-entry key="'method'" select="'text'"/>
<xsl:map-entry key="'byte-order-mark'" select="true()"/>
</xsl:map>
</xsl:map-entry>
<xsl:map-entry key="'csv'">
<xsl:map>
<xsl:map-entry key="'method'" select="'text'"/>
<xsl:map-entry key="'byte-order-mark'" select="true()"/>
</xsl:map>
</xsl:map-entry>
<xsl:map-entry key="'hkt'">
<xsl:map>
<xsl:map-entry key="'method'" select="'text'"/>
</xsl:map>
</xsl:map-entry>
</xsl:map>
</xsl:function>

<xsl:function name="shared:output-parameters-for-uri" as="map(*)">
<xsl:param name="uri" as="xs:string?"/>
<xsl:sequence select="($output-parameters(lower-case(shared:file-extension($uri))), $output-parameters?xml)[1]"/>
</xsl:function>

<xsl:function name="shared:path-to-uri" as="xs:string">
<xsl:param name="path" as="xs:string"/>
<xsl:sequence select="file:path-to-uri($path)"/>
</xsl:function>

<xsl:function name="shared:path-to-uri" as="xs:string">
<xsl:param name="baseURI" as="xs:string"/>
<xsl:param name="fileName" as="xs:string"/>
<xsl:variable name="path" select="concat($baseURI, $fileName)"/>
<xsl:sequence select="file:path-to-uri($path)"/>
</xsl:function>

<xsl:template name="shared:pdf-to-layer">
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="startTime" select="saxon:timestamp()"/>
<xsl:sequence select="$startTime[shared:always-false()]"/>
<xsl:sequence select="shared:pdf-to-layer($options)"/>
<xsl:sequence select="shared:log-execution-time($startTime)"/>
</xsl:template>

<xsl:function name="shared:pdf-to-layer">
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="n" select="count($options?inputFileName)"/>
<xsl:sequence select="shared:log('DEBUG', string-join(('Transforming', $n, 'pdf''s to 1 pdf', $options?outputFileName, 'with', $n, 'layers', $options?layerName), ' '))"/>
<saxon:do action="ext:pdf-to-layer($options?inputFileName, $options?layerName, $options?layerOn, $options?outputFileName)"/>
</xsl:function>

<xsl:function name="shared:previous-day" as="xs:string">
<xsl:param name="date" as="xs:string"/>
<xsl:sequence select="shared:shift-day($date, -1)"/>
</xsl:function>

<xsl:function name="shared:radians-to-degrees" as="xs:double">
<xsl:param name="angleInRadians" as="xs:double"/>
<xsl:sequence select="$angleInRadians div math:pi() * 180"/>
</xsl:function>

<xsl:function name="shared:remove-attributes" as="element()*">
<xsl:param name="elements" as="element()*"/>
<xsl:param name="names" as="xs:string*"/>
<xsl:for-each select="$elements">
<xsl:copy>
<xsl:sequence select="(@*[not(name() = $names)], node())"/>
</xsl:copy>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:serialize-map" as="xs:string*">
<xsl:param name="map" as="map(*)*"/>
<xsl:for-each select="$map">
<xsl:sequence select="serialize(shared:map-to-json(.), $output-parameters?json)"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:shift-day" as="xs:string">
<xsl:param name="date" as="xs:string"/>
<xsl:param name="nbOfDays" as="xs:integer"/>
<xsl:choose>
<xsl:when test="$nbOfDays gt 0">
<!-- Add abs to avoid compilation warning -->
<xsl:value-of select="xs:date($date) + xs:dayTimeDuration(concat('P', abs($nbOfDays), 'D'))"/>
</xsl:when>
<xsl:when test="$nbOfDays lt 0">
<xsl:value-of select="xs:date($date) - xs:dayTimeDuration(concat('P', abs($nbOfDays), 'D'))"/>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="$date"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>

<xsl:function name="shared:sind" as="xs:double?">
<xsl:param name="angleInDegrees" as="xs:double?"/>
<xsl:sequence select="if (exists($angleInDegrees)) then math:sin(shared:degrees-to-radians($angleInDegrees)) else ()"/>
</xsl:function>

<xsl:function name="shared:sleep">
<xsl:param name="seconds" as="xs:integer"/>
<xsl:sequence select="shared:log('DEBUG', concat('Sleeping for ', $seconds, ' seconds...'))"/>
<xsl:sequence select="thread:sleep(1000*$seconds)"/>
</xsl:function>

<xsl:function name="shared:svg-to-bbox" as="map(*)*">
<xsl:param name="svgUri" as="xs:string"/>
<xsl:sequence select="shared:svg-to-bbox($svgUri, ())"/>
</xsl:function>

<xsl:function name="shared:svg-to-bbox" as="map(*)*">
<xsl:param name="svgUri" as="xs:string"/>
<xsl:param name="queryId" as="xs:string*"/>
<xsl:variable name="response" select="ext:svg-to-bbox($svgUri, $queryId)"/>
<xsl:sequence select="shared:log('DEBUG', $response)"/>
<xsl:sequence select="array:flatten(parse-json($response))"/>
</xsl:function>

<xsl:function name="shared:tand" as="xs:double?">
<xsl:param name="angleInDegrees" as="xs:double?"/>
<xsl:sequence select="if (exists($angleInDegrees)) then math:tan(shared:degrees-to-radians($angleInDegrees)) else ()"/>
</xsl:function>

<xsl:function name="shared:to-folder" as="xs:string?">
<xsl:param name="property" as="xs:string?"/>
<xsl:for-each select="normalize-space($property)[string-length() gt 0]">
<xsl:for-each select="translate(., '\', '/')">
<xsl:sequence select="if (ends-with(., '/')) then . else concat(., '/')"/>
</xsl:for-each>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:total-minutes-from-time" as="xs:integer">
<xsl:param name="time" as="xs:string"/>
<!-- hours-from-time() returns 0 for 24:00:00 -->
<xsl:sequence select="60*xs:integer(substring($time, 1, 2)) + minutes-from-time(xs:time($time))"/>
</xsl:function>

<xsl:template name="shared:transform">
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="startTime" select="saxon:timestamp()"/>
<xsl:sequence select="$startTime[shared:always-false()]"/>
<xsl:variable name="extended-options" select="map:merge((map:entry('delivery-format', 'serialized'), $options))"/>
<xsl:call-template name="shared:write">
<xsl:with-param name="output" select="shared:transform($extended-options)"/>
</xsl:call-template>
<xsl:sequence select="shared:log-execution-time($startTime)"/>
</xsl:template>

<xsl:function name="shared:transform" as="map(*)">
<xsl:param name="options" as="map(*)"/>
<xsl:sequence select="shared:log-transform-options($options)"/>
<xsl:sequence select="transform($options)"/>
</xsl:function>

<xsl:template name="shared:transform-with-saxon">
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="startTime" select="saxon:timestamp()"/>
<xsl:sequence select="$startTime[shared:always-false()]"/>
<xsl:call-template name="shared:write">
<xsl:with-param name="output" select="shared:transform-with-saxon($options)"/>
</xsl:call-template>
<xsl:sequence select="shared:log-execution-time($startTime)"/>
</xsl:template>

<xsl:function name="shared:transform-with-saxon" as="map(*)">
<xsl:param name="options" as="map(*)"/>
<xsl:sequence select="shared:log-transform-options($options)"/>
<xsl:variable name="stylesheet" select="saxon:compile-stylesheet(doc($options?stylesheet-location))"/>
<xsl:variable name="source"
select="if (exists($options?source-node)) then $options?source-node else if (exists($options?source-location)) then shared:doc($options?source-location) else shared:dummy-doc()"/>
<xsl:variable name="output-document" as="document-node()?" select="saxon:transform($stylesheet, $source, $options?stylesheet-params)"/>
<xsl:variable name="base-output-uri" select="($options?base-output-uri, 'output')[1]"/>
<xsl:variable name="serialization-params" select="shared:output-parameters-for-uri($base-output-uri)"/>
<xsl:map-entry key="$base-output-uri" select="serialize($output-document, $serialization-params)"/>
</xsl:function>

<xsl:function name="shared:unzip">
<xsl:param name="source-file" as="xs:string"/>
<xsl:param name="target-folder" as="xs:string"/>
<!-- Code inspired by arch:to-files(), see http://expath.org/spec/archive/editor#fn.to.files Archive Module, EXPath Candidate Module 12 May 2014 -->
<xsl:sequence select="shared:log('DEBUG', concat('Unzipping ', $source-file, ' to ', $target-folder))"/>
<xsl:variable name="archive" select="file:read-binary($source-file)"/>
<xsl:variable name="entries" select="arch:entries($archive)"/>
<xsl:variable name="dirs" select="$entries[ends-with(., '/')]"/>
<xsl:for-each select="$dirs">
<xsl:sequence select="file:create-dir(concat($target-folder, .))"/>
</xsl:for-each>
<!-- Tuning: extract all binary data in one call -->
<xsl:variable name="files" select="$entries except $dirs"/>
<xsl:variable name="binary-data" select="arch:extract-binary($archive, $files)"/>
<xsl:for-each select="$files">
<xsl:variable name="index" select="position()"/>
<xsl:sequence select="file:write-binary(concat($target-folder, .), $binary-data[$index])"/>
</xsl:for-each>
</xsl:function>

<xsl:function name="shared:unzip-ext">
<xsl:param name="source-file" as="xs:string"/>
<xsl:param name="target-folder" as="xs:string"/>
<xsl:sequence select="shared:log('DEBUG', concat('Unzipping ', $source-file, ' to ', $target-folder))"/>
<xsl:sequence select="ext:unzip($source-file, $target-folder)"/>
</xsl:function>

<xsl:function name="shared:uuid" as="xs:string">
<xsl:param name="element" as="item()*"/>
<xsl:sequence select="ext:UUID(serialize($element, $output-parameters?uuid))"/>
</xsl:function>

<xsl:function name="shared:uuid-new" as="xs:string">
<xsl:param name="element" as="item()*"/>
<xsl:sequence select="uuid:nameUUIDFromBytes(string-to-codepoints(serialize($element, $output-parameters?uuid))!xs:unsignedByte(.))?toString()"/>
</xsl:function>

<xsl:function name="shared:week-of-year" as="xs:string">
<xsl:param name="date" as="xs:string"/>
<xsl:variable name="day-of-week" select="shared:day-of-week($date)"/>
<xsl:variable name="ref-Thursday" select="xs:date(shared:shift-day($date, 4 - $day-of-week))"/>
<xsl:sequence select="format-date($ref-Thursday, '[Y]-[W,2]', (), 'ISO', ())"/>
</xsl:function>

<xsl:template name="shared:validate">
<xsl:param name="options" as="map(*)"/>
<xsl:variable name="startTime" select="saxon:timestamp()"/>
<xsl:sequence select="$startTime[shared:always-false()]"/>
<xsl:sequence select="shared:validate($options)"/>
<xsl:sequence select="shared:log-execution-time($startTime)"/>
</xsl:template>

<xsl:function name="shared:validate">
<xsl:param name="options" as="map(*)"/>
<xsl:sequence select="shared:log('DEBUG', concat('Validating ', $options?xml, ' with ', $options?xsd))"/>
<xsl:sequence select="ext:validate($options?xml, $options?xsd)"/>
</xsl:function>

<xsl:function name="shared:validity" as="element()?">
<xsl:param name="validFromDate" as="xs:string"/>
<xsl:param name="validToDate" as="xs:string"/>
<xsl:if test="$validFromDate le $validToDate">
<xsl:element name="validity">
<xsl:attribute name="validFromDate" select="$validFromDate"/>
<xsl:attribute name="validToDate" select="$validToDate"/>
</xsl:element>
</xsl:if>
</xsl:function>

<xsl:function name="shared:validity-intersection" as="element()?">
<xsl:param name="element-1" as="element()"/>
<xsl:param name="element-2" as="element()"/>
<xsl:variable name="validFromDate" select="max(($element-1, $element-2)/(@validFromDate, @dateFrom, @fromDate)!string())"/>
<xsl:variable name="validToDate" select="min(($element-1, $element-2)/(@validToDate, @dateTo, @toDate)!string())"/>
<xsl:sequence select="shared:validity($validFromDate, $validToDate)"/>
</xsl:function>

<xsl:template name="shared:write">
<xsl:param name="output" as="map(*)"/>
<xsl:for-each select="map:keys($output)">
<xsl:sort select="."/>
<xsl:sequence select="shared:write(., map:get($output, .))"/>
</xsl:for-each>
</xsl:template>

<xsl:function name="shared:write">
<xsl:param name="href" as="xs:string"/>
<xsl:param name="value" as="xs:string"/>
<!-- Exclude default added "output" when base-output-uri is absent by checking is-absolute-uri() -->
<xsl:if test="shared:is-absolute-uri($href)">
<xsl:sequence select="shared:log('DEBUG', concat('Exporting ', $href))"/>
<xsl:sequence select="file:write-text($href, $value)"/>
</xsl:if>
</xsl:function>

<xsl:function name="shared:zip">
<xsl:param name="source-folder" as="xs:string"/>
<xsl:param name="target-file" as="xs:string"/>
<xsl:sequence select="shared:zip($source-folder, '', $target-file)"/>
</xsl:function>

<xsl:function name="shared:zip">
<xsl:param name="source-folder" as="xs:string"/>
<xsl:param name="source-files" as="xs:string*"/>
<xsl:param name="target-file" as="xs:string"/>
<!-- Code inspired by arch:from-files(), see http://expath.org/spec/archive/editor#fn.from.files Archive Module, EXPath Candidate Module 12 May 2014 -->
<xsl:sequence select="shared:log('DEBUG', concat('Zipping ', $source-folder, ' to ', $target-file))"/>
<xsl:variable name="absolute.files" select="for $f in $source-files return concat($source-folder, $f)"/>
<xsl:variable name="all" as="xs:string*" select="for $f in $absolute.files return if(file:is-dir($f)) then (for $f1 in shared:list($f,true()) return concat($f,$f1)) else $f"/>
<xsl:variable name="content" as="xs:base64Binary*" select="for $f in $all return if(file:is-dir($f)) then xs:base64Binary('') else file:read-binary($f)"/>
<xsl:variable name="relative.names" select="for $n in $all return replace($n, $source-folder, '')"/>
<xsl:variable name="archive" select="arch:create($relative.names, $content)"/>
<xsl:sequence select="file:write-binary($target-file, $archive)"/>
</xsl:function>

<xsl:function name="shared:zip-ext">
<xsl:param name="source-folder" as="xs:string"/>
<xsl:param name="target-file" as="xs:string"/>
<xsl:sequence select="shared:log('DEBUG', concat('Zipping ', $source-folder, ' to ', $target-file))"/>
<xsl:sequence select="ext:zip($source-folder, $target-file)"/>
</xsl:function>

</xsl:stylesheet>
(2-2/2)