<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:array="http://www.w3.org/2005/xpath-functions/array" xmlns:pf="http://www.daisy.org/ns/pipeline/functions" xmlns:d="http://www.daisy.org/ns/pipeline/data" xmlns:ncx="http://www.daisy.org/z3986/2005/ncx/" xmlns:smil="http://www.w3.org/2001/SMIL20/" xmlns:f="f" version="3.0"> <xsl:param name="smils" as="document-node(element(smil:smil))*" required="yes"/> <xsl:param name="file-limit" as="array(xs:integer)" required="no" select="[-1,-1,-1]"/> <xsl:param name="level-offset" as="xs:integer" required="no" select="0"/> <xsl:param name="max-folder-name-length" as="xs:integer" required="no" select="58"/> <xsl:param name="max-file-name-length" as="xs:integer" required="no" select="185"/> <xsl:include href="http://www.daisy.org/pipeline/modules/common-utils/library.xsl"/> <xsl:include href="http://www.daisy.org/pipeline/modules/file-utils/library.xsl"/> <xsl:include href="http://www.daisy.org/pipeline/modules/smil-utils/clock-functions.xsl"/> <xsl:variable name="depth" select="array:size($file-limit)"> </xsl:variable> <xsl:variable name="media-type" select="'audio/mpeg'"/> <xsl:variable name="file-extension" select="'mp3'"/> <xsl:variable name="smil-doc-index-from-base-uri" as="map(xs:string,xs:integer)"> <xsl:map> <xsl:for-each select="$smils"> <xsl:map-entry key="pf:normalize-uri(base-uri(.))" select="position()"/> </xsl:for-each> </xsl:map> </xsl:variable> <xsl:key name="id" match="smil:par[@id]|smil:seq[@id]" use="@id"/> <xsl:template match="/ncx:ncx"> <d:fileset> <xsl:iterate select="ncx:navMap//ncx:navPoint"> <xsl:param name="playOrder" as="xs:integer" select="0"/> <xsl:param name="from-smil-doc" as="xs:integer" select="0"/> <xsl:param name="from-smil-elem" as="element()?" select="()"/> <xsl:param name="dest-file" as="xs:integer*" select="(1,for $x in 1 to $depth return 0)"/> <xsl:param name="label" as="xs:string" select="''"/> <xsl:param name="clips-for-dest-file" as="element(smil:audio)*" select="()"/> <xsl:on-completion> <xsl:variable name="clips-for-dest-file" as="element(smil:audio)*"> <xsl:sequence select="$clips-for-dest-file"/> <xsl:if test="exists($from-smil-elem)"> <xsl:call-template name="audio-clips-between"> <xsl:with-param name="from-smil-doc" select="$from-smil-doc"/> <xsl:with-param name="from-smil-elem" select="$from-smil-elem"/> <xsl:with-param name="to-smil-doc" select="count($smils)"/> <xsl:with-param name="to-smil-elem" select="()"/> </xsl:call-template> </xsl:if> </xsl:variable> <xsl:call-template name="add-clips-to-dest-file"> <xsl:with-param name="audio-clips" select="$clips-for-dest-file"/> <xsl:with-param name="dest-file"> <xsl:call-template name="dest-file"> <xsl:with-param name="index" select="$dest-file"/> <xsl:with-param name="label" select="$label"/> </xsl:call-template> </xsl:with-param> </xsl:call-template> </xsl:on-completion> <xsl:variable name="next-playOrder" as="xs:integer" select="xs:integer(@playOrder)"/> <xsl:choose> <xsl:when test="$next-playOrder<$playOrder"> <xsl:call-template name="pf:error"> <xsl:with-param name="msg">Unexpected play order in navMap: playOrder="{}" is followed by playOrder="{}"</xsl:with-param> <xsl:with-param name="args" select="($playOrder,$next-playOrder)"/> </xsl:call-template> </xsl:when> <xsl:when test="$next-playOrder=$playOrder"> <xsl:next-iteration/> </xsl:when> <xsl:otherwise> <xsl:variable name="src" select="pf:normalize-uri(resolve-uri(ncx:content/@src,base-uri(.)))"/> <xsl:variable name="to-smil-doc" as="xs:integer?" select="$smil-doc-index-from-base-uri(substring-before($src,'#'))"/> <xsl:variable name="to-smil-elem" as="element()?" select="if (exists($to-smil-doc)) then key('id',substring-after($src,'#'),$smils[$to-smil-doc]) else ()"/> <xsl:if test="not(exists($to-smil-elem))"> <xsl:call-template name="pf:error"> <xsl:with-param name="msg">NCX invalid: "src" attribute contains a broken reference (or references a SMIL that is not in the spine): {}</xsl:with-param> <xsl:with-param name="args" select="ncx:content/@src"/> </xsl:call-template> </xsl:if> <xsl:variable name="clips-for-dest-file" as="element(smil:audio)*"> <xsl:sequence select="$clips-for-dest-file"/> <xsl:if test="exists($from-smil-elem)"> <xsl:call-template name="audio-clips-between"> <xsl:with-param name="from-smil-doc" select="$from-smil-doc"/> <xsl:with-param name="from-smil-elem" select="$from-smil-elem"/> <xsl:with-param name="to-smil-doc" select="$to-smil-doc"/> <xsl:with-param name="to-smil-elem" select="$to-smil-elem"/> </xsl:call-template> </xsl:if> </xsl:variable> <xsl:variable name="level" as="xs:integer" select="1 + count(ancestor::ncx:navPoint) + $level-offset"/> <xsl:choose> <xsl:when test="$level<=$depth"> <xsl:call-template name="add-clips-to-dest-file"> <xsl:with-param name="audio-clips" select="$clips-for-dest-file"/> <xsl:with-param name="dest-file"> <xsl:call-template name="dest-file"> <xsl:with-param name="index" select="$dest-file"/> <xsl:with-param name="label" select="$label"/> </xsl:call-template> </xsl:with-param> </xsl:call-template> <xsl:variable name="dest-file" as="xs:integer*"> <xsl:choose> <xsl:when test="empty($clips-for-dest-file) and $level<$depth and (every $x in $level + 1 to $depth satisfies $dest-file[1 + $x] eq 1)"> <xsl:sequence select="$dest-file"/> </xsl:when> <xsl:otherwise> <xsl:iterate select="reverse(0 to $level)"> <xsl:variable name="level" as="xs:integer" select="."/> <xsl:choose> <xsl:when test="$level>0 and $file-limit[$level]>0 and $dest-file[1 + $level]=$file-limit[$level]"> <xsl:next-iteration/> </xsl:when> <xsl:otherwise> <xsl:break select="(for $x in 0 to $level - 1 return max((1,$dest-file[1 + $x])), $dest-file[1 + $level] + 1, for $x in $level + 1 to $depth return 1)"/> </xsl:otherwise> </xsl:choose> </xsl:iterate> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:next-iteration> <xsl:with-param name="playOrder" select="$next-playOrder"/> <xsl:with-param name="from-smil-doc" select="$to-smil-doc"/> <xsl:with-param name="from-smil-elem" select="$to-smil-elem"/> <xsl:with-param name="dest-file" select="$dest-file"/> <xsl:with-param name="label" select="normalize-space(string(ncx:navLabel/ncx:text))"/> <xsl:with-param name="clips-for-dest-file" select="()"/> </xsl:next-iteration> </xsl:when> <xsl:otherwise> <xsl:next-iteration> <xsl:with-param name="playOrder" select="$next-playOrder"/> <xsl:with-param name="from-smil-doc" select="$to-smil-doc"/> <xsl:with-param name="from-smil-elem" select="$to-smil-elem"/> <xsl:with-param name="clips-for-dest-file" select="$clips-for-dest-file"/> </xsl:next-iteration> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:iterate> </d:fileset> </xsl:template> <xsl:template name="audio-clips-between" as="element(smil:audio)*"> <xsl:param name="from-smil-doc" as="xs:integer" required="yes"/> <xsl:param name="from-smil-elem" as="element()" required="yes"/> <xsl:param name="to-smil-doc" as="xs:integer" required="yes"/> <xsl:param name="to-smil-elem" as="element()?" required="yes"/> <xsl:choose> <xsl:when test="exists($to-smil-elem)"> <xsl:choose> <xsl:when test="$from-smil-doc>$to-smil-doc"> <xsl:call-template name="pf:error"> <xsl:with-param name="msg">NCX invalid: play order of navMap inconsistent with reading order defined in spine.</xsl:with-param> </xsl:call-template> </xsl:when> <xsl:when test="$from-smil-doc=$to-smil-doc"> <xsl:if test="not($from-smil-elem/(following::*|descendant::*) intersect $to-smil-elem)"> <xsl:call-template name="pf:error"> <xsl:with-param name="msg">NCX invalid: play order of navMap inconsistent with reading order defined in spine.</xsl:with-param> </xsl:call-template> </xsl:if> <xsl:sequence select="$from-smil-elem/(following::*|descendant::*) intersect $to-smil-elem/preceding::smil:audio"/> </xsl:when> <xsl:otherwise> <xsl:sequence select="$from-smil-elem/(following::smil:audio|descendant::smil:audio)"/> <xsl:sequence select="for $x in ($from-smil-doc + 1) to ($to-smil-doc - 1) return $smils[$x]//smil:audio"/> <xsl:sequence select="$to-smil-elem/preceding::smil:audio"/> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:sequence select="$from-smil-elem/(following::smil:audio|descendant::smil:audio)"/> <xsl:sequence select="for $x in ($from-smil-doc + 1) to $to-smil-doc return $smils[$x]//smil:audio"/> </xsl:otherwise> </xsl:choose> </xsl:template> <xsl:template name="add-clips-to-dest-file" as="element(d:file)*"> <xsl:param name="audio-clips" as="element(smil:audio)*" required="yes"/> <xsl:param name="dest-file" as="xs:string" required="yes"/> <xsl:variable name="clips" as="element(d:clip)*"> <xsl:iterate select="$audio-clips"> <xsl:param name="clipBegin" as="xs:decimal" select="0"/> <xsl:variable name="original-clipBegin" as="xs:decimal" select="pf:smil-clock-value-to-seconds(@clipBegin)"/> <xsl:variable name="original-clipEnd" as="xs:decimal" select="pf:smil-clock-value-to-seconds(@clipEnd)"/> <xsl:variable name="clipEnd" as="xs:decimal" select="$clipBegin + $original-clipEnd - $original-clipBegin"/> <d:clip original-href="{pf:normalize-uri(resolve-uri(@src,base-uri(.)))}" original-clipBegin="{$original-clipBegin}" original-clipEnd="{$original-clipEnd}" clipBegin="{$clipBegin}" clipEnd="{$clipEnd}"/> <xsl:next-iteration> <xsl:with-param name="clipBegin" select="$clipEnd"/> </xsl:next-iteration> </xsl:iterate> </xsl:variable> <xsl:for-each-group select="$clips" group-adjacent="@original-href"> <d:file href="{$dest-file}" original-href="{@original-href}" media-type="{$media-type}"> <xsl:for-each select="current-group()"> <xsl:copy> <xsl:sequence select="@clipBegin|@clipEnd|@original-clipBegin|@original-clipEnd"/> </xsl:copy> </xsl:for-each> </d:file> </xsl:for-each-group> </xsl:template> <xsl:template name="dest-file" as="xs:string"> <xsl:param name="index" as="xs:integer*" required="yes"/> <xsl:param name="label" as="xs:string" required="yes"/> <xsl:variable name="volume" as="xs:integer" select="$index[1]"/> <xsl:variable name="folder-index" as="xs:integer*" select="$index[position()>1 and position()<last()]"/> <xsl:variable name="file-index" as="xs:integer" select="$index[last()]"/> <xsl:sequence select="concat( 'volume%20', $volume, '/', string-join(for $x in $folder-index return substring( format-number($x,'000'), 1, $max-folder-name-length), '/'), '/', encode-for-uri( substring( concat( format-number($file-index,'000'), ' ', f:safe-file-name($label)), 1, $max-file-name-length)), '.', $file-extension)"/> </xsl:template> <xsl:function name="f:safe-file-name" as="xs:string"> <xsl:param name="file-name" as="xs:string"/> <xsl:sequence select="replace($file-name,'[/\\:\?\*<>"]','_')"/> </xsl:function> </xsl:stylesheet>