. "$BST_LIB"
require xsltproc libxslt/present
# libxslt — the GNOME XSLT 1.0 processor. xsltproc is the CLI driver over
# libxslt + libexslt; libxml2 (already on this image) supplies the parser and
# XPath engine underneath, so this suite is the transformation layer on top of
# what 62-libxml2.test already covers.
#
# Everything is generated into $TMPDIR: the suite needs no data/ payload, and a
# short path matters because several xsltproc diagnostics quote the filename.
t=${TMPDIR:-/tmp}/bst-libxslt.$$
mkdir -p "$t" || { bst_notok libxslt/tmpdir "cannot create $t"; exit 0; }
# --- payloads ----------------------------------------------------------------
cat > "$t/doc.xml" <<'EOF'
Godel Escher BachHofstadter29.95
ContactSagan7.50
BlindsightWatts12.00
spaced text
EOF
# identity-ish: value-of, for-each, attribute access, text output
cat > "$t/basic.xsl" <<'EOF'
=|
EOF
# sorting, numbering, position()/last()
cat > "$t/sort.xsl" <<'EOF'
:(/)
EOF
# named templates, params, variables, recursion, choose/if
cat > "$t/tmpl.xsl" <<'EOF'
1
fact10=
cheap=
EOF
# keys, modes, apply-templates with select
cat > "$t/key.xsl" <<'EOF'
1985:
titles:
,
EOF
# XPath 1.0 function library
cat > "$t/xpath.xsl" <<'EOF'
X-concat
X-substring
X-substring-before
X-substring-after
X-translate
X-normalize
X-strlen
X-contains
X-starts
X-sum
X-floor
X-ceiling
X-round
X-count
X-name
X-ancestor
X-following
X-pred
X-format
X-boolean
X-lang
X-lang-root
EOF
# output methods / serialization
cat > "$t/xmlout.xsl" <<'EOF'
<raw&
EOF
cat > "$t/htmlout.xsl" <<'EOF'
T
x
EOF
cat > "$t/noxmldecl.xsl" <<'EOF'
a < b
EOF
# stylesheet parameters
cat > "$t/param.xsl" <<'EOF'
P:/
EOF
# import/include precedence
cat > "$t/inc-base.xsl" <<'EOF'
BASE-
SHARED
EOF
cat > "$t/importer.xsl" <<'EOF'
OVER-[]
EOF
# namespaces in the result tree
cat > "$t/ns.xsl" <<'EOF'
1
EOF
# EXSLT
cat > "$t/exslt.xsl" <<'EOF'
12
E-nodeset
E-tokenize
E-tokenize2
E-replace
E-padding
E-max
E-min
E-abs
E-distinct
E-func
EOF
# deliberately broken stylesheet (unknown top-level XSL element in the XSL ns)
cat > "$t/bad.xsl" <<'EOF'
EOF
# not well-formed XML
printf '\n' > "$t/malformed.xml"
# HTML input for --html
cat > "$t/page.html" <<'EOF'
Pageone
two
EOF
cat > "$t/html-in.xsl" <<'EOF'
H::
EOF
# infinite recursion, to prove --maxdepth is enforced rather than crashing
cat > "$t/deep.xsl" <<'EOF'
EOF
# xsl:message terminate
cat > "$t/terminate.xsl" <<'EOF'
STOP-HERE
EOF
# --- 1. presence and identification ------------------------------------------
check libxslt/version xsltproc --version
check_out libxslt/version-libxslt "libxslt 10143" xsltproc --version
check_out libxslt/version-libexslt "libexslt" xsltproc --version
check_out libxslt/version-libxml "compiled against libxml" xsltproc --version
# --- 2. core XSLT 1.0 engine --------------------------------------------------
check_eq libxslt/basic-forEach \
'b1=Godel Escher Bach|b2=Contact|b3=Blindsight|' \
"$(xsltproc "$t/basic.xsl" "$t/doc.xml" 2>/dev/null)"
# xsl:sort reorders the iteration (position()/last() follow it) while xsl:number
# keeps counting the node's place in the SOURCE tree — so the leading numbers
# come out 2,3,1, which is the assertion worth making: it distinguishes the two.
sorted=$(xsltproc "$t/sort.xsl" "$t/doc.xml" 2>/dev/null)
check_eq libxslt/sort-numeric \
'2:Contact(1/3) 3:Blindsight(2/3) 1:Godel Escher Bach(3/3)' \
"$(printf '%s' "$sorted" | tr -s ' ' | sed 's/ $//')"
tm=$(xsltproc "$t/tmpl.xsl" "$t/doc.xml" 2>/dev/null)
check_eq libxslt/named-template-recursion 3628800 \
"$(printf '%s\n' "$tm" | sed -n 's/.*fact10=\([0-9]*\).*/\1/p')"
check_eq libxslt/if-and-choose b2b3 \
"$(printf '%s\n' "$tm" | sed -n 's/.*cheap=\(.*\)$/\1/p')"
kk=$(xsltproc "$t/key.xsl" "$t/doc.xml" 2>/dev/null)
check_eq libxslt/key-lookup 2 "$(printf '%s\n' "$kk" | sed -n 's/^1985:\([0-9]*\).*/\1/p')"
check_eq libxslt/apply-templates-mode 'God,Con,Bli,' \
"$(printf '%s\n' "$kk" | sed -n 's/.*titles:\(.*\)$/\1/p')"
imp=$(xsltproc "$t/importer.xsl" "$t/doc.xml" 2>/dev/null)
check_out libxslt/import-shared "SHARED" printf '%s' "$imp"
check_out libxslt/import-override "OVER-Godel Escher Bach" printf '%s' "$imp"
check_out libxslt/apply-imports "[BASE-Godel Escher Bach]" printf '%s' "$imp"
# --- 3. the XPath 1.0 function library, one value per case --------------------
xp=$(xsltproc "$t/xpath.xsl" "$t/doc.xml" 2>/dev/null)
xpv() { printf '%s\n' "$xp" | sed -n "s/^$1 //p"; }
check_eq libxslt/xpath-concat abc "$(xpv X-concat)"
check_eq libxslt/xpath-substring 234 "$(xpv X-substring)"
check_eq libxslt/xpath-substring-before a "$(xpv X-substring-before)"
check_eq libxslt/xpath-substring-after b "$(xpv X-substring-after)"
check_eq libxslt/xpath-translate BAr "$(xpv X-translate)"
check_eq libxslt/xpath-normalize-space 'spaced text' "$(xpv X-normalize)"
check_eq libxslt/xpath-string-length 5 "$(xpv X-strlen)"
check_eq libxslt/xpath-contains true "$(xpv X-contains)"
check_eq libxslt/xpath-starts-with true "$(xpv X-starts)"
check_eq libxslt/xpath-sum 49.45 "$(xpv X-sum)"
check_eq libxslt/xpath-floor 3 "$(xpv X-floor)"
check_eq libxslt/xpath-ceiling 4 "$(xpv X-ceiling)"
check_eq libxslt/xpath-round 3 "$(xpv X-round)"
check_eq libxslt/xpath-count 3 "$(xpv X-count)"
check_eq libxslt/xpath-local-name book "$(xpv X-name)"
check_eq libxslt/xpath-ancestor-axis catalog "$(xpv X-ancestor)"
check_eq libxslt/xpath-following-sibling 2 "$(xpv X-following)"
check_eq libxslt/xpath-predicate-index Blindsight "$(xpv X-pred)"
check_eq libxslt/xpath-format-number '1,234,567.89' "$(xpv X-format)"
check_eq libxslt/xpath-boolean-empty false "$(xpv X-boolean)"
check_eq libxslt/xpath-lang true "$(xpv X-lang)"
# lang() reads the context node's inherited xml:lang; at match="/" the context is
# the root node, which inherits nothing — false is correct, not a miss.
check_eq libxslt/xpath-lang-root false "$(xpv X-lang-root)"
# --- 4. output methods and serialization --------------------------------------
xo=$(xsltproc "$t/xmlout.xsl" "$t/doc.xml" 2>/dev/null)
check_out libxslt/out-xml-decl '' printf '%s' "$xo"
check_out libxslt/out-copy-of '' printf '%s' "$xo"
check_out libxslt/out-escaping '<raw&' printf '%s' "$xo"
check_out libxslt/out-indent ' /dev/null)
check_out libxslt/out-html-meta '' printf '%s' "$ho"
no=$(xsltproc "$t/noxmldecl.xsl" "$t/doc.xml" 2>/dev/null)
check_eq libxslt/out-omit-decl 0 "$(printf '%s\n' "$no" | grep -c '' printf '%s' "$no"
nso=$(xsltproc "$t/ns.xsl" "$t/doc.xml" 2>/dev/null)
check_out libxslt/ns-literal 'xmlns:e="urn:example"' printf '%s' "$nso"
check_out libxslt/ns-xsl-element 'urn:other' printf '%s' "$nso"
# --- 5. parameters and CLI surface --------------------------------------------
check_eq libxslt/param-default 'P:nobody/0' \
"$(xsltproc "$t/param.xsl" "$t/doc.xml" 2>/dev/null)"
check_eq libxslt/stringparam 'P:world/0' \
"$(xsltproc --stringparam who world "$t/param.xsl" "$t/doc.xml" 2>/dev/null)"
check_eq libxslt/param-xpath 'P:nobody/42' \
"$(xsltproc --param n 21 "$t/param.xsl" "$t/doc.xml" 2>/dev/null)"
check_eq libxslt/param-xpath-string 'P:lit/0' \
"$(xsltproc --param who "'lit'" "$t/param.xsl" "$t/doc.xml" 2>/dev/null)"
xsltproc --output "$t/out.txt" "$t/basic.xsl" "$t/doc.xml" 2>/dev/null
check_out libxslt/cli-output 'b2=Contact' cat "$t/out.txt"
check_eq libxslt/cli-stdin-doc 'b1=Godel Escher Bach|b2=Contact|b3=Blindsight|' \
"$(cat "$t/doc.xml" | xsltproc "$t/basic.xsl" - 2>/dev/null)"
check_eq libxslt/cli-html-parser 'H:2:Page' \
"$(xsltproc --html "$t/html-in.xsl" "$t/page.html" 2>/dev/null)"
check_out libxslt/cli-nonet 'b1=' xsltproc --nonet "$t/basic.xsl" "$t/doc.xml"
check_out libxslt/cli-novalid 'b1=' xsltproc --novalid "$t/basic.xsl" "$t/doc.xml"
check_out libxslt/cli-timing 'b1=' xsltproc --timing "$t/basic.xsl" "$t/doc.xml"
check_out libxslt/cli-repeat 'b1=' xsltproc --repeat "$t/basic.xsl" "$t/doc.xml"
# two documents on one command line are transformed independently (the text
# output carries no trailing newline, so count occurrences, not lines)
check_eq libxslt/cli-multi-doc 2 \
"$(xsltproc "$t/basic.xsl" "$t/doc.xml" "$t/doc.xml" 2>/dev/null | grep -o 'b3=Blindsight' | wc -l | tr -d ' ')"
# --- 6. EXSLT (libexslt) ------------------------------------------------------
ex=$(xsltproc "$t/exslt.xsl" "$t/doc.xml" 2>/dev/null)
exv() { printf '%s\n' "$ex" | sed -n "s/^$1 //p"; }
check_eq libxslt/exslt-node-set 2 "$(exv E-nodeset)"
check_eq libxslt/exslt-tokenize 3 "$(exv E-tokenize)"
check_eq libxslt/exslt-tokenize-index b "$(exv E-tokenize2)"
check_eq libxslt/exslt-replace 'a+b+c' "$(exv E-replace)"
check_eq libxslt/exslt-padding '[....]' "$(exv E-padding)"
check_eq libxslt/exslt-math-max 29.95 "$(exv E-max)"
check_eq libxslt/exslt-math-min 7.5 "$(exv E-min)"
check_eq libxslt/exslt-math-abs 7 "$(exv E-abs)"
check_eq libxslt/exslt-set-distinct 2 "$(exv E-distinct)"
check_eq libxslt/exslt-func 42 "$(exv E-func)"
# date:date-time is not asserted for a value (it is the wall clock) but it must
# produce a parseable ISO-8601 stamp, which exercises libexslt's date module and
# the platform's time(2)/localtime(3) path.
cat > "$t/date.xsl" <<'EOF'
D
Y
W
EOF
dt=$(xsltproc "$t/date.xsl" "$t/doc.xml" 2>/dev/null)
check_eq libxslt/exslt-date-year 2026 "$(printf '%s\n' "$dt" | sed -n 's/^Y //p')"
if printf '%s\n' "$dt" | sed -n 's/^D //p' | grep -q '^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]'; then
bst_ok libxslt/exslt-date-time
else
bst_notok libxslt/exslt-date-time "not ISO-8601: $(_bst_1line "$dt")"
fi
# --- 7. errors, exit codes and limits -----------------------------------------
check_rc libxslt/err-bad-xpath 5 xsltproc "$t/bad.xsl" "$t/doc.xml"
check_rc libxslt/err-malformed-doc 6 xsltproc "$t/basic.xsl" "$t/malformed.xml"
check_rc libxslt/err-missing-doc 6 xsltproc "$t/basic.xsl" "$t/nosuch.xml"
check_rc libxslt/err-missing-xsl 4 xsltproc "$t/nosuch.xsl" "$t/doc.xml"
check_rc libxslt/err-terminate 10 xsltproc "$t/terminate.xsl" "$t/doc.xml"
# the message text must reach stderr — asserted separately from the rc, since
# check_out also requires rc=0 and a terminate deliberately exits 10
tmsg=$(xsltproc "$t/terminate.xsl" "$t/doc.xml" 2>&1)
case "$tmsg" in *STOP-HERE*) bst_ok libxslt/err-terminate-msg ;;
*) bst_notok libxslt/err-terminate-msg "$(_bst_1line "$tmsg")" ;; esac
# runaway recursion must be caught by the depth limiter, not by dying: xsltproc
# reports the failure and exits non-zero, and it must still be < 128 (i.e. not a
# signal death) — on this arm an unbounded recursion is the thing most likely to
# take the whole runtime down rather than the program.
out=$(xsltproc --maxdepth 200 "$t/deep.xsl" "$t/doc.xml" 2>&1); rc=$?
if [ "$rc" -ne 0 ] && [ "$rc" -lt 128 ]; then bst_ok libxslt/maxdepth-enforced
else bst_notok libxslt/maxdepth-enforced "rc=$rc $(_bst_1line "$out")"; fi
check_out libxslt/maxdepth-message 'recursion' printf '%s' "$out"
# --- 8. libexslt really is linked in, not merely referenced -------------------
#
# This arm links everything statically, so there is no libexslt.so to look for —
# the question "did libexslt make it into the binary" is answered by asking the
# binary what it registered at start-up.
check_out libxslt/dumpextensions-runs 'Registered XSLT Extensions' xsltproc --dumpextensions
check_out libxslt/dumpextensions-exslt '{http://exslt.org/common}node-set' xsltproc --dumpextensions
check_out libxslt/dumpextensions-math '{http://exslt.org/math}sqrt' xsltproc --dumpextensions
check_out libxslt/dumpextensions-str '{http://exslt.org/strings}split' xsltproc --dumpextensions
rm -rf "$t"