← all demos
xsl:stylesheet(
default-mode="m:docbook",
exclude-result-prefixes="array db f fp l m map mp v vp xs",
version="3.0",
xmlns="http://www.w3.org/1999/xhtml",
xmlns:array="http://www.w3.org/2005/xpath-functions/array",
xmlns:db="http://docbook.org/ns/docbook",
xmlns:err="http://www.w3.org/2005/xqt-errors",
xmlns:f="http://docbook.org/ns/docbook/functions",
xmlns:fp="http://docbook.org/ns/docbook/functions/private",
xmlns:l="http://docbook.org/ns/docbook/l10n",
xmlns:m="http://docbook.org/ns/docbook/modes",
xmlns:map="http://www.w3.org/2005/xpath-functions/map",
xmlns:mp="http://docbook.org/ns/docbook/modes/private",
xmlns:v="http://docbook.org/ns/docbook/variables",
xmlns:vp="http://docbook.org/ns/docbook/variables/private",
xmlns:xs="http://www.w3.org/2001/XMLSchema",
xmlns:xsl="http://www.w3.org/1999/XSL/Transform")
xsl:include(href="../standalone-functions.xsl")
xsl:key(match="*", name="id", use="@xml:id")
xsl:key(match="*", name="genid", use="generate-id(.)")
vp:translate-suppress-elements := tokenize($translate-suppress-elements, '\s+')
function f:translate-attribute(node as element()) -> boolean?:
if local-name($node) = $vp:translate-suppress-elements:
<-- false()
function f:attributes(node as element(), attributes as attribute()*) -> attribute()*:
<-- f:attributes($node, $attributes, local-name($node), ())
function f:attributes -> attribute()*:
param node as element()
param attributes as attribute()*
param extra-classes as string*
param exclude-classes as string*
names := distinct-values($attributes/node-name())
foreach $names:
namespace := namespace-uri-from-QName(.)
name := .
values as string* := $attributes[node-name()=$name]/string()
if exists($values):
@{$name}
if not(QName('', 'class') = $attributes/node-name()):
roles := (tokenize(normalize-space(string-join($extra-classes, ' '))),
tokenize(normalize-space($node/@role)),
if ($node/@revisionflag)
then 'rev'||$node/@revisionflag
else ())
exclude := tokenize(normalize-space(string-join($exclude-classes, ' ')))
classes as string* :=
foreach distinct-values($roles):
xsl:sort(select=".")
if not(. = $exclude):
<-- .
if exists($classes):
@class
translate := f:translate-attribute($node)
if exists($translate):
@translate
function f:is-true(value) -> boolean (visibility="public"):
choose:
when empty($value):
<- false()
when $value castable as xs:boolean:
<- xs:boolean($value)
when $value castable as xs:integer:
<- xs:integer($value) != 0
when string($value) = ('true', 'yes'):
<- true()
when string($value) = ('false', 'no'):
<- false()
else:
xsl:message(expand-text="yes", terminate="yes") = Warning: interpreting ‘{$value}’ as true.
<- true()
function f:orderedlist-startingnumber(list as element(db:orderedlist)) -> integer:
choose:
when not($list/@continuation = 'continues'):
<-- 1
when empty($list/preceding::db:orderedlist):
xsl:message
"Warning: orderedlist continuation=continues,"
"but no preceding list"
<-- 1
else:
plist := $list/outermost(preceding::db:orderedlist)[last()]
<-- f:orderedlist-startingnumber($plist)
+ count($plist/db:listitem)
function f:l10n-language(target as element()) -> string (cache="yes"):
nearest-lang := $target/ancestor-or-self::*[@xml:lang][1]/@xml:lang
mc-language as string := if (exists($gentext-language))
then $gentext-language
else if (exists($nearest-lang) and $nearest-lang = '')
then $default-language
else ($nearest-lang, $default-language)[1]
language := lower-case($mc-language)
adjusted-language := if (contains($language, '-'))
then substring-before($language, '-')
|| '_' || substring-after($language, '-')
else $language
choose:
when doc-available(
resolve-uri($adjusted-language||'.xml', $v:localization-base-uri)):
<-- $adjusted-language
when doc-available(
resolve-uri(
substring-before($adjusted-language, '_')||'.xml', $v:localization-base-uri)):
<-- substring-before($adjusted-language,'_')
else:
xsl:message
"No localization exists for ""
<-- $adjusted-language
"" or ""
<-- substring-before($adjusted-language,'_')
"". Using default ""
<-- $default-language
""."
<-- $default-language
function f:gentext-letters(node as element()) -> element(l:letters)?:
<-- f:gentext-letters-for-language($node)
function f:gentext-letters-for-language(node as element()) -> element(l:letters)?:
lang := f:l10n-language($node)
l10n := fp:localization($lang)
letters := $l10n/l:letters
if empty($letters):
xsl:message(select="'No letters for', $lang")
if count($letters) gt 1:
xsl:message(select="'Multiple letters for localization:', $lang")
<-- $letters[1]
function fp:properties(context as element(), properties as array(map(*))) -> map(*):
props as map(*)* :=
foreach 1 to array:size($properties):
map := array:get($properties, .)
nodes as node()* :=
xsl:evaluate(context-item="$context", xpath="$map?xpath")
if exists($nodes):
<-- $map
choose:
when empty($props):
xsl:message(
select="'No properties for ' || local-name($context)",
use-when="'properties' = $v:debug")
<-- map { }
else:
<-- $props[1]
function f:date-format(context as element()) -> string:
format := f:pi($context, 'date-format')
choose:
when $context/*:
<-- 'apply-templates'
when string($context) castable as xs:dateTime:
choose:
when $format:
<-- $format
else:
<-- $date-dateTime-format
when string($context) castable as xs:date:
choose:
when $format:
<-- $format
else:
<-- $date-date-format
else:
<-- 'apply-templates'
function fp:replace-element -> array(*):
param lines as array(*)
param elemno as integer
param new-elem as item()*
<-- fp:replace-element($lines, $elemno, 1, $new-elem, [])
function fp:replace-element -> array(*):
param array as array(*)
param elemno as integer
param count as integer
param new-elem as item()*
param newarray as array(*)
choose:
when $count gt array:size($array):
<-- $newarray
when $count = $elemno:
<-- fp:replace-element($array, $elemno, $count+1, $new-elem,
array:append($newarray, $new-elem))
else:
<-- fp:replace-element($array, $elemno, $count+1, $new-elem,
array:append($newarray, $array($count)))
function f:target(id as string, context as node()) -> element()* (cache="yes"):
<-- key('id', $id, root($context))
function f:href(context as node(), node as element()) -> string (cache="yes"):
<-- '#' || f:generate-id($node)
vp:gidmap := map {
'acknowledgements': 'ack',
'annotation': 'an',
'appendix': 'ap',
'article': 'art',
'bibliodiv': 'bd',
'bibliography': 'bi',
'book': 'bo',
'chapter': 'ch',
'colophon': 'co',
'dedication': 'ded',
'equation': 'eq',
'example': 'ex',
'figure': 'fig',
'glossary': 'g',
'glossdiv': 'gd',
'glossentry': 'ge',
'glossterm': 'gt',
'itemizedlist': 'il',
'listitem': 'li',
'orderedlist': 'ol',
'part': 'part',
'preface': 'p',
'procedure': 'proc',
'refentry': 're',
'reference': 'ref',
'refsect1': 'rs1_',
'refsect2': 'rs2_',
'refsect3': 'rs3_',
'sect1': 's1_',
'sect2': 's2_',
'sect3': 's3_',
'sect4': 's4_',
'sect5': 's5_',
'section': 's',
'table': 'tab',
'variablelist': 'vl',
'varlistentry': 'vle'
}
function f:generate-id(node as element()) -> string (cache="yes"):
<-- f:generate-id($node, true())
function f:generate-id(node as element(), use-xml-id as boolean) -> string (cache="yes"):
choose:
when $use-xml-id and $node/@xml:id:
<-- $node/@xml:id/string()
when empty($node/parent::*):
<-- $generated-id-root
else:
aid := f:generate-id($node/parent::*, $use-xml-id)
type := (map:get($vp:gidmap, local-name($node)),
local-name($node))[1]
prec := $node/preceding-sibling::*[node-name(.)=node-name($node)]
<-- $aid || $generated-id-sep || $type || string(count($prec)+1)
function f:id(node as element()) -> string (cache="yes"):
<-- if ($node/@xml:id)
then $node/@xml:id/string()
else f:generate-id($node)
function f:unique-id(node as element()) -> string (cache="yes"):
<-- f:generate-id($node, false())
function fp:css-properties(context as element()?) -> attribute()?:
generic-attributes as map(*) :=
xsl:map
foreach $context/@css:*:
xsl:map-entry(key="local-name(.)", select="string(.)")
media-attributes as map(*) :=
xsl:map
foreach $context/@*:
if namespace-uri(.) = 'https://xsltng.docbook.org/ns/css#' || $output-media:
xsl:map-entry(key="local-name(.)", select="string(.)")
attributes as map(*) := map:merge(($generic-attributes, $media-attributes),
map { 'duplicates': 'use-last' })
if map:size($attributes) != 0:
xsl:iterate(select="map:keys($attributes)")
param css := ''
xsl:on-completion
@style
name := .
value := map:get($attributes, .)
xsl:next-iteration
css := $css || $name || ':' || $value || ';'
function f:spaces(length as item()*) -> string?:
choose:
when empty($length):
when count($length) gt 1:
<-- f:spaces(string-join($length ! string(.), ''))
when $length castable as xs:integer:
length := xs:integer($length)
choose:
when $length lt 0:
when $length lt 10:
<-- substring(' ', 1, $length)
else:
<-- ' ' || f:spaces($length - 10)
else:
<-- f:spaces(string-length(string($length)))
function fp:lookup-string -> node()*:
param context as element()
param lookup as element()
param table-name as string
value := $lookup/*[node-name(.)=node-name($context)]
if count($value) gt 1:
xsl:message(expand-text="yes") = Duplicate {$table-name} for {node-name($context)}
<-- if (empty($value))
then $lookup/db:_default/node()
else $value[1]/node()
function fp:separator(node as element(), key as string) -> node()*:
<-- fp:localization-template($node, 'separator')
function f:label-separator(node as element()) -> node()*:
<-- fp:separator($node, 'label-separator')
function fp:parse-key-value-pairs(strings as string*) -> map(xs:string,xs:string):
<-- fp:parse-key-value-pairs($strings, map { })
function fp:parse-key-value-pairs -> map(xs:string,xs:string):
param strings as string*
param map as map(xs:string,xs:string)
car := $strings[1]
cdr := subsequence($strings, 2)
key := if (contains($car, ':'))
then substring-before($car, ':')
else '_default'
value := if (contains($car, ':'))
then substring-after($car, ':')
else $car
choose:
when empty($car):
<-- $map
when map:contains($map, $key):
xsl:message(select="'Warning: ignoring duplicate key:', $key")
<-- fp:parse-key-value-pairs($cdr, $map)
else:
<-- fp:parse-key-value-pairs($cdr,
map:put($map, $key, $value))
function f:refsection(node as element()) -> boolean:
<-- $node/self::db:refsection
or $node/self::db:refsect1
or $node/self::db:refsect2
or $node/self::db:refsect3
function f:section(node as element()) -> boolean (visibility="public"):
<-- $node/self::db:section
or $node/self::db:sect1
or $node/self::db:sect2
or $node/self::db:sect3
or $node/self::db:sect4
or $node/self::db:sect5
or f:refsection($node)
function f:section-depth(node as element()?) -> integer (visibility="public"):
choose:
when empty($node):
<- 0
when $node/self::db:section:
<- count($node/ancestor::db:section) + 1
when $node/self::db:sect1 or $node/self::db:sect2
or $node/self::db:sect3 or $node/self::db:sect4
or $node/self::db:sect5:
<- xs:integer(substring(local-name($node), 5))
when $node/self::db:refsection:
<- count($node/ancestor::db:refsection)+1
when $node/self::db:refsect1 or $node/self::db:refsect2
or $node/self::db:refsect3:
<- xs:integer(substring(local-name($node), 8))
else:
<-- f:section-depth($node/parent::*)
function f:step-number(node as element(db:step)) -> integer+:
xsl:iterate(select="reverse($node/ancestor-or-self::*)")
param number := ()
choose:
when self::db:procedure:
<-- $number
xsl:break
when self::db:step:
xsl:next-iteration
number := (count(preceding-sibling::db:step)+1, $number)
else:
xsl:next-iteration
number := $number
function f:step-numeration(node as element(db:step)) -> string:
depth := count(f:step-number($node))
depth := $depth
mod string-length($procedure-step-numeration)
depth := if ($depth eq 0)
then string-length($procedure-step-numeration)
else $depth
<-- substring($procedure-step-numeration, $depth, 1)
function f:orderedlist-item-number(node as element(db:listitem)) -> integer+:
xsl:iterate(select="reverse($node/ancestor-or-self::*)")
param number := ()
xsl:on-completion(select="$number")
choose:
when self::db:listitem[parent::db:orderedlist]:
xsl:next-iteration
number := (count(preceding-sibling::db:listitem)
+ f:orderedlist-startingnumber(parent::db:orderedlist),
$number)
else:
xsl:next-iteration
number := $number
function f:orderedlist-item-numeration(node as element(db:listitem)) -> string:
numeration := $node/parent::db:orderedlist/@numeration
choose:
when exists($numeration):
choose:
when $numeration = 'upperalpha':
<-- 'A'
when $numeration = 'loweralpha':
<-- 'a'
when $numeration = 'upperroman':
<-- 'I'
when $numeration = 'lowerroman':
<-- 'i'
else:
<-- '1'
else:
depth := count(f:orderedlist-item-number($node))
depth := $depth
mod string-length($orderedlist-item-numeration)
depth := if ($depth eq 0)
then string-length($orderedlist-item-numeration)
else $depth
<-- substring($orderedlist-item-numeration, $depth, 1)
function f:tokenize-on-char(string as string, char as string) -> string*:
ch := substring($char||' ', 1, 1)
tchar := if ($ch = ('.', '?', '*', '{', '}', '\', '\[', '\]'))
then '\' || $ch
else $ch
<-- tokenize($string, $tchar)
function f:uri-scheme(uri as string) -> string?:
if matches($uri, '^[-a-zA-Z0-9]+:'):
<-- replace($uri, '^([-a-zA-Z0-9]+):.*$', '$1')
function f:relative-path(base as string, path as string) -> string:
choose:
when exists(f:uri-scheme($path)) and f:uri-scheme($path) ne 'file':
<-- $path
else:
bpath := replace($base, '^file:/+', '')
ppath := replace($path, '^file:/+', '')
base-parts := tokenize($bpath, '/')[position() lt last()]
path-parts := tokenize($ppath, '/')
common-prefix as string* :=
xsl:iterate(select="$base-parts")
param pos := 1
param common := ()
xsl:on-completion(select="$common")
if $base-parts[$pos] = $path-parts[$pos]:
xsl:next-iteration
pos := $pos + 1
common := ($common, $base-parts[$pos])
base-tail := $base-parts[position() gt count($common-prefix)]
path-tail := $path-parts[position() gt count($common-prefix)]
final-parts as string* :=
foreach 1 to count($base-tail):
<-- '..'
<-- $path-tail
<-- string-join($final-parts, '/')
function f:orientation-class(node as element()) -> string?:
nearest := ($node/ancestor-or-self::*[contains-token(@role,'landscape')
or contains-token(@role,'portrait')]
| $node/ancestor-or-self::db:table[@orient]
| $node/ancestor-or-self::db:informaltable[@orient])[last()]
choose:
when $output-media != 'print':
when $nearest/@orient and $nearest/@orient = 'land':
<-- 'landscape'
when $nearest/@orient:
<-- 'portrait'
when contains-token($nearest/@role, 'landscape'):
<-- 'landscape'
else:
<-- 'portrait'
function f:conditional-orientation-class(node as element()) -> string?:
parent := $node/parent::element() ! f:orientation-class(.)
orient := f:orientation-class($node)
choose:
when exists($parent) and $parent eq $orient:
else:
<-- $orient
function f:global-syntax-highlighter(context as node()) -> string (cache="yes"):
choose:
when f:pi($context/root()/*, 'syntax-highlighter'):
<-- f:pi($context/root()/*, 'syntax-highlighter')
when f:pi($context/root()/*/db:info, 'syntax-highlighter'):
<-- f:pi($context/root()/*/db:info, 'syntax-highlighter')
else:
<-- $verbatim-syntax-highlighter
function fp:add-resource-base-uri(path as string) -> string:
choose:
when starts-with($path, '/') or starts-with($path, 'file:'):
<-- $path
when ends-with($resource-base-uri, '/'):
<-- $resource-base-uri || $path
else:
<-- $resource-base-uri || '/' || $path