<?xml version='1.0'?>

<!--********************************************************************
Copyright (C) 2020-2026  Robert A. Beezer

This file is part of PreTeXt.

PreTeXt is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 or version 3 of the
License (at your option).

PreTeXt 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with PreTeXt.  If not, see <http://www.gnu.org/licenses/>.
*********************************************************************-->

<!DOCTYPE xsl:stylesheet [
    <!ENTITY % entities SYSTEM "entities.ent">
    %entities;
]>

<xsl:stylesheet
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"
    xmlns:xml="http://www.w3.org/XML/1998/namespace"
    xmlns:xhtml="http://www.w3.org/1999/xhtml"
    xmlns:pi="http://pretextbook.org/2020/pretext/internal"
    xmlns:pf="https://prefigure.org"
    xmlns:exsl="http://exslt.org/common"
    xmlns:date="http://exslt.org/dates-and-times"
    xmlns:str="http://exslt.org/strings"
    extension-element-prefixes="exsl date str"
    exclude-result-prefixes="pi"
>

<!-- This is the once-mythical pre-processor, though we prefer     -->
<!-- to describe it as the "assembly" of "enhanced" source.  By    -->
<!-- "assembly" we mean pre-processing of source, by "assembling"  -->
<!-- various pieces of material or content, authored or computed,  -->
<!-- into an enhanced source tree. This template operates by       -->
<!-- successive passes through the entire source tree making       -->
<!-- adjustments into a new "enhanced" or modified source tree     -->
<!-- with each pass.                                               -->
<!--                                                               -->
<!-- * $original will point to source file/tree/XML at the overall -->
<!--   "pretext" element.                                          -->
<!-- * The "version" templates are applied to decide if certain    -->
<!--   elements are excluded from the source tree.  This creates   -->
<!--   the new $version source tree by *removing* source.  It also -->
<!--   resolves "custom" elements.  If these two features have     -->
<!--   been used properly by an author, then the result should be  -->
<!--   valid PreTeXt (when perhaps the authored source was not).   -->
<!-- * The modal "assembly" templates are applied to the source    -->
<!--   root element, creating a new version of the source, which   -->
<!--   has been "enhanced".  This pass assembles computed          -->
<!--   content (most visibly the bibliography), creating the       -->
<!--   $assembly source tree by *adding* new source elements.      -->
<!-- * The "repair" templates will automatically repair deprecated -->
<!--   constructions so that actual conversions can remove         -->
<!--   orphaned code.  Despite the name, we also implement         -->
<!--   conveniences that are universal across all conversions, so  -->
<!--   that conversions can assume a more canonical version of the -->
<!--   source, or remove the need for additional templates to      -->
<!--   realize certain constructions.  This creates the            -->
<!--   $repair source tree by *changing* source.                   -->
<!-- * $root will point to the root of the final enhanced          -->
<!--   source file/tree/XML.                                       -->
<!-- * Derived variables, $docinfo and $document-root, will        -->
<!--   be created here for use in subsequent stylesheets.          -->
<!--                                                               -->
<!-- Notes:                                                        -->
<!--                                                               -->
<!-- 1.  $original is needed for context switches back into the    -->
<!--     original authored source, such as for determining the     -->
<!--     location of the source in the file system.                -->
<!-- 2.  Any coordination of automatically assigned identifiers    -->
<!--     requires identical source, so even a simple extraction    -->
<!--     stylesheet might require preparing identical source       -->
<!--     via this method.                                          -->
<!-- 3.  Overrides, customization of the assembly will typically   -->
<!--     happen here, but can be converter-specific in some ways.  -->
<!--                                                               -->
<!-- The "publisher-variables.xsl" and "pretext-assembly.xsl"      -->
<!-- stylesheets are symbiotic, and should be imported             -->
<!-- simultaneously.  Assembly will change the source in various   -->
<!-- ways, while some defaults for publisher variables will depend -->
<!-- on source.  The default variables should depend on gross      -->
<!-- structure and adjustments should be to smaller portions of    -->
<!-- the source, but we don't take any chances.  So, note in       -->
<!-- "assembly" that an intermediate tree is defined as a          -->
<!-- variable, which is then used in defining some variables,      -->
<!-- based on assembled source.  Conversely, certain variables,    -->
<!-- such as locations of customizations or private solutions,     -->
<!-- are needed early in assembly, while other variables, such     -->
<!-- as options for numbering, are needed for later enhancements   -->
<!-- to the source.  If new code results in undefined, or          -->
<!-- recursively defined, variables, this discussion may be        -->
<!-- relevant.  (This is repeated verbatim in the other            -->
<!-- stylesheet).                                                  -->
<!--  -->
<!-- Note too, that we want this stylesheet to be independent, and -->
<!-- that can be tested with the  pretext-enhanced-source.xsl      -->
<!-- stylesheet.  There is one danger: any (modal) template        -->
<!-- applied here, needs to be defined here.  "Normal" conversions -->
<!-- will import things like "pretext-common.xsl" and templates    -->
<!-- defined there will be available.  But when not defined here,  -->
<!-- the default is to just apply default templates to the         -->
<!-- content, which may generally just produce a lot of text.      -->
<!-- Which is no good, say as an attribute value.                  -->

<!-- This stylesheet runs as a chain of full-tree passes.  Each      -->
<!-- pass is a modal traversal that copies the previous tree and     -->
<!-- alters only the elements it cares about, yielding a new         -->
<!-- tree.  The new tree is built as a result tree fragment and      -->
<!-- immediately turned into a real node-set with                    -->
<!-- exsl:node-set(), so the next pass can walk it.  The chain is    -->
<!-- wired in "The Assembly Pipeline" section as a sequence of       -->
<!-- variables ($version, $assembly, ...); that sequence, not the    -->
<!-- order of templates in this file, is the authoritative pass      -->
<!-- order.                                                          -->
<!--                                                                 -->
<!-- Most passes are the identity for almost every node.  The        -->
<!-- low-priority identity templates that make each pass a           -->
<!-- faithful copy by default are collected in "Source Assembly      -->
<!-- Infrastructure"; the section for a pass then overrides them     -->
<!-- (one section per pass, below, in execution order) for just      -->
<!-- the handful of elements that pass transforms.                   -->
<!--                                                                 -->
<!-- The passes, in the order they run (the identifier in            -->
<!-- parentheses is the node-set each one produces):                 -->
<!--                                                                 -->
<!--    1. private-solutions   ($private-solutions)                  -->
<!--         Splice in an external file of instructor solutions.     -->
<!--         Identity, and skipped, when no such file is named.      -->
<!--    2. version             ($version)                            -->
<!--         Resolve "version" and "custom" elements, *removing*     -->
<!--         excluded content.  Result should be valid PreTeXt.      -->
<!--    3. id-attribute        ($original-labeled)  @pi:original-id  -->
<!--         First of three identification stamps (see below).       -->
<!--    4. assembly            ($assembly)                           -->
<!--         *Add* computed content: the assembled bibliography,     -->
<!--         copied WeBWorK problems, matching/card-sort pieces.     -->
<!--    5. exercise            ($exercise)                           -->
<!--         Tag each exercise with its kind (inline, divisional,    -->
<!--         worksheet, ...) for later decisions.                    -->
<!--    6. id-attribute        ($assembly-label)    @pi:assembly-id  -->
<!--         Second stamp: the early id passes coordinate on.        -->
<!--    7. dynamic-substitution ($dynamic)                           -->
<!--         Splice computed answers into fill-in-the-blank and      -->
<!--         kindred dynamic exercises.  Skipped when none exist.    -->
<!--    8. representations     ($representations)                    -->
<!--         Render interactive exercises as static or dynamic       -->
<!--         equivalents, per $exercise-style.                       -->
<!--    9. repair              ($repair)                             -->
<!--         *Change* source: fix deprecated constructions and       -->
<!--         apply conveniences, for a canonical tree.               -->
<!--   10. enrichment          ($enrichment)                         -->
<!--         *Add* generated material, e.g. a GeoGebra preview       -->
<!--         or visual text for a bare url.                          -->
<!--   11. labels              ($labels)                             -->
<!--         Promote an authored @xml:id to @label and record        -->
<!--         localization support.  See "Labels".                    -->
<!--   12. id-attribute        ($identification)    @pi:unique-id    -->
<!--         Third stamp: the final id conversions consume.          -->
<!--   13. augment             ($augment)                            -->
<!--         Annotate divisions with @pi:level (and ordered lists    -->
<!--         with @pi:ordered-list-level), as numbering needs.       -->
<!--   14. serial-stamp        ($serial-stamp)                       -->
<!--         Stamp @pi:serial on every numbered item.  See           -->
<!--         "Numbering".                                            -->
<!--                                                                 -->
<!-- After the chain, $root, $docinfo, $document-root and            -->
<!-- $bibinfo are derived from the final tree for the conversion     -->
<!-- stylesheets that import this one.                               -->
<!--                                                                 -->
<!-- Three identifiers, one mechanism.  The id-attribute pass        -->
<!-- runs three times (passes 3, 6, 12), each stamping one           -->
<!-- attribute by a deterministic depth-first walk: @pi:original-id  -->
<!-- (authored structure), @pi:assembly-id (early, so passes can     -->
<!-- coordinate before filenames exist) and @pi:unique-id (the final -->
<!-- identifier).  The three agree element-for-element only          -->
<!-- because no intervening pass reorders siblings; that             -->
<!-- invariant is stated in full at "Structural Contract for         -->
<!-- Identification Passes" and checked, when assembly.debug is      -->
<!-- set, by the id-coherence-check.                                 -->
<!--                                                                 -->
<!-- Two-pass extraction and substitution.  A few constructs         -->
<!-- (fill-in-the-blank answers, WeBWorK, MOM, ...) need an          -->
<!-- external round trip: this stylesheet first emits a tree that    -->
<!-- drives the trip, then on a later run reads the results back.    -->
<!-- The $b-extracting-* switches that select that mode are          -->
<!-- described at "Controlling Two-Pass Extraction and               -->
<!-- Substitution".                                                  -->

<!-- Isolate conversion of Runestone/interactive to PreTeXt/static -->
<xsl:import href="./pretext-runestone-static.xsl"/>

<!-- We explicitly do not import "pretext-common.xsl" as we want    -->
<!-- this important pre-processing stylesheet to have no hidden     -->
<!-- dependencies.  In almost every rational use, the "-common"     -->
<!-- stylesheet is imported by a conversion, so it is easy to       -->
<!-- miss these dependencies.  An example in 2022-06 was the use    -->
<!-- of the "unique-id" template to coordinate construction and     -->
<!-- insertion of WeBWorK problems with an intervening trip to a    -->
<!-- WW server.  The "pretext-enhanced-source.xsl" stylesheet is    -->
<!-- one place where "-common" does not creep in.  Use of a modal   -->
<!-- template here, with a definition in -common, will do a         -->
<!-- massive "value-of" when not defined for the "-enhanced-source" -->
<!-- stylesheet, which might be detectable (in strange ways).       -->

<!-- The "representations" pass is used to make derived versions of      -->
<!-- authored exercises which can be rendered dynamically.  For example, -->
<!-- a multiple choice question.  These representations can be "static"  -->
<!-- and so meant for use in PDF or braille output, or "dynamic", which  -->
<!-- means anyplace Javascript (or similar) is available.  Right now     -->
<!-- that is just HTML (and not output built on HTML, such as EPUB).     -->
<!--                                                                     -->
<!-- Notes:                                                              -->
<!--   * We default here to "static".  HTML production will override     -->
<!--     to "dynamic" and then any importing stylesheet will need to     -->
<!--     override back to "static".                                      -->
<!--   * 'pg-problems' are WeBWorK problems for an archive               -->
<!--   * If testing, the pretext-enhanced-source.xsl  stylesheet will    -->
<!--     need a stringparam override to view and test dynamic versions.  -->
<xsl:variable name="exercise-style" select="'static'"/>

<!-- Short-Circuit -->
<!-- Sometimes we only want to stop at an intermediate tree.  For example, -->
<!-- we may convert only to a "version" (valid PreTeXt) via resolution of  -->
<!-- version support and customizations, or stop at the tree that has      -->
<!-- @pi:assembly-id attributes but has not yet loaded exercise components.-->
<!-- We control this with internal variables, not documented as author or  -->
<!-- publisher features.  When we stop this early, "exercise-style" is     -->
<!-- irrelevant.                                                           -->

<!-- default is empty, so we can detect non-use -->
<xsl:param name="assembly.version-only" select="''"/>
<xsl:param name="assembly.assembly-id-only" select="''"/>

<!-- Set to 'yes' to enable diagnostic checks, such as         -->
<!-- verifying coherence of @pi:assembly-id and @pi:unique-id. -->
<!-- Not documented as an author or publisher feature.         -->
<xsl:param name="assembly.debug" select="''"/>
<xsl:variable name="b-assembly-debug" select="$assembly.debug = 'yes'"/>

<!-- Set to 'yes' to convert the @xml:base attributes stamped by the -->
<!-- xinclude mechanism into @pi:source-uri attributes, so that a    -->
<!-- diagnostic (validation, say) can name the file where a problem  -->
<!-- lies.  Not documented as an author or publisher feature.        -->
<xsl:param name="assembly.file-attribution" select="''"/>
<xsl:variable name="b-file-attribution" select="$assembly.file-attribution = 'yes'"/>

<!-- convert to a boolean, with error-checking -->
<xsl:variable name="version-only">
    <xsl:choose>
        <xsl:when test="$assembly.version-only = ''">
            <xsl:text>no</xsl:text>
        </xsl:when>
        <xsl:when test="$assembly.version-only = 'yes'">
            <xsl:text>yes</xsl:text>
        </xsl:when>
        <xsl:when test="$assembly.version-only = 'no'">
            <xsl:text>no</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:message>PTX:BUG:  the internal parameter  assembly.version-only  received an unrecognized value of "<xsl:value-of select="$assembly.version-only"/>" (possible values are "yes" and "no")</xsl:message>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>
<xsl:variable name="b-version-only" select="$version-only = 'yes'"/>

<!-- convert to a boolean, with error-checking -->
<xsl:variable name="assembly-id-only">
    <xsl:choose>
        <xsl:when test="$assembly.assembly-id-only = ''">
            <xsl:text>no</xsl:text>
        </xsl:when>
        <xsl:when test="$assembly.assembly-id-only = 'yes'">
            <xsl:text>yes</xsl:text>
        </xsl:when>
        <xsl:when test="$assembly.assembly-id-only = 'no'">
            <xsl:text>no</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:message>PTX:BUG:  the internal parameter  assembly.assembly-id-only  received an unrecognized value of "<xsl:value-of select="$assembly.assembly-id-only"/>" (possible values are "yes" and "no")</xsl:message>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>
<xsl:variable name="b-assembly-id-only" select="$assembly-id-only = 'yes'"/>

<!-- ################################################ -->
<!-- Controlling Two-Pass Extraction and Substitution -->
<!-- ################################################ -->

<!-- Some objects are authored in their native source languages,     -->
<!-- and after one application of this stylesheet, can be extracted, -->
<!-- "compiled", and then automatically incorporated into output     -->
<!-- formats.  An example is a "latex-image", which is extracted,    -->
<!-- and compiled by LaTeX into an image, which can then simply be   -->
<!-- pointed to reliably by the mechanisms of the various output     -->
<!-- formats.  In other words, the result of this "compilation"      -->
<!-- stands on its own, and is not brought back into the source.     -->
<!--                                                                 -->
<!-- Other objects are extracted, then processed (sent to a server   -->
<!-- typically), and a "static" PreTeXt version, in valid PreTeXt    -->
<!-- syntax, is returned and captured in files by the Python         -->
<!-- extraction step.  Then in a general application of this         -->
<!-- stylesheet to produce static output formats, those files are    -->
<!-- read and the static versions are substituted into the eventual  -->
<!-- source, for processing by stylesheets producing less-capable    -->
<!-- output (less-capable than HTML).                                -->
<!--                                                                 -->
<!-- Such objects are:                                               -->
<!--                                                                 -->
<!--   WeBWorK (WW/PG), MyOpenMath (MOM), STACK,                     -->
<!--   Dynamic Fill-in-the-Blank (FITB), bibliography                -->
<!--   entries, and QR codes                                         -->
<!--                                                                 -->
<!-- But there is a "chicken-and-egg condition" if two such objects  -->
<!-- are present in a fresh, un-processed, source document.  The     -->
<!-- extraction of the first object will also try to "sub in" the    -->
<!-- static versions of the second object from its                   -->
<!-- extracted/generated files, which do not yet exist.  So we note  -->
<!-- which extraction is happening *by a variable that is            -->
<!-- over-ridden by the particular extraction stylesheet* and we     -->
<!-- use this information to avoid the substitution.  We assume this -->
<!-- does not disturb the identifiers in use to coordinate the       -->
<!-- extraction and substitution.                                    -->
<!--                                                                 -->
<!-- At most, one of the following is true, and only if running      -->
<!-- an extraction stylesheet.  In general use (producing useful     -->
<!-- final output formats), they are all false.  The "extraction"    -->
<!-- stylesheets for each type of object, will override exactly one  -->
<!-- variable by setting it to "true()"                              -->
<xsl:variable name="b-extracting-pg"   select="false()"/>
<xsl:variable name="b-extracting-mom"  select="false()"/>
<xsl:variable name="b-extracting-fitb" select="false()"/>
<xsl:variable name="b-extracting-biblio" select="false()"/>
<xsl:variable name="b-extracting-stack" select="false()"/>
<xsl:variable name="b-extracting-qrcode" select="false()"/>

<xsl:variable name="b-extracting" select="$b-extracting-pg or $b-extracting-mom or $b-extracting-fitb or $b-extracting-biblio or $b-extracting-stack or $b-extracting-qrcode"/>

<!-- ############################## -->
<!-- Source Assembly Infrastructure -->
<!-- ############################## -->

<!-- When building duplicates, we have occasion                     -->
<!-- to inspect the original in various places                      -->
<!-- We do not know if we have "fixed" the                          -->
<!-- deprecated overall element, so need to                         -->
<!-- try both.  For example, this variable is                       -->
<!-- employed by the warnings and deprecation                       -->
<!-- messages that result from analyzing an                         -->
<!-- author's source, since we may "repair"                         -->
<!-- some of them later, so we have to catch                        -->
<!-- them early.                                                    -->
<!-- The author's source, as parsed.  Kept for duties genuinely     -->
<!-- about the source document: resolving relative filenames via    -->
<!-- the second argument of the document() function, and            -->
<!-- warnings/views that inspect exactly what the author wrote      -->
<!-- ("generic-warnings", view-source).  Checks and lookups against -->
<!-- the document's *content* belong on the assembled tree, $root,  -->
<!-- where private solutions are merged and versions are resolved.  -->
<xsl:variable name="original" select="/mathbook|/pretext"/>

<!-- These modal templates duplicate the source exactly for each -->
<!-- pass: elements, attributes, text, whitespace, comments,     -->
<!-- everything. Various other templates will override these     -->
<!-- templates to create a new enhanced source tree.             -->

<xsl:template match="node()|@*" mode="private-solutions">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="private-solutions"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="version">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="version"/>
    </xsl:copy>
</xsl:template>

<!-- Later, this template only *adds* an attribute to an element -->
<!-- it is copying over to the result tree.  Here we copy text   -->
<!-- nodes and the other attributes and the parameters are not   -->
<!-- needed.  This is a general-purpose template, see comments   -->
<!-- at further definition for elements.                         -->
<xsl:template match="node()|@*" mode="id-attribute">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="id-attribute"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="assembly">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="assembly"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="exercise">
    <xsl:param name="division" select="''"/>

    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="exercise">
            <xsl:with-param name="division" select="$division"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="dynamic-substitution">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="dynamic-substitution"/>
    </xsl:copy>
</xsl:template>

<!-- The highest format version of the substitutions file this stylesheet  -->
<!-- knows how to read.  Version 1 is the form in which an "eval-subst"    -->
<!-- carries "latex" and "plain" children.  Files written before that      -->
<!-- distinction existed carry no @version and hold text directly; they    -->
<!-- are read as version 0 by the fallback in "dynamic-representation".    -->
<xsl:variable name="dynamic-substitutions-format" select="1"/>

<!-- The root of this pass, which runs once, and so is where a check on    -->
<!-- the substitutions file as a whole belongs rather than at each of the  -->
<!-- individual lookups.  Without this the file would be read by a         -->
<!-- stylesheet older than the script that wrote it, and the mismatch      -->
<!-- would surface as substitutions quietly coming out wrong instead of    -->
<!-- as a statement of what is actually the matter.                        -->
<xsl:template match="/" mode="dynamic-substitution">
    <xsl:if test="($exercise-style = 'static') and not($b-extracting) and not($dynamic-substitutions-file = '')">
        <xsl:variable name="recorded" select="document($dynamic-substitutions-file,$original)/*/@version"/>
        <!-- A missing @version is an older file, and is silent: the        -->
        <!-- representation template already falls back for those.  Only a  -->
        <!-- version from the future is worth saying anything about.        -->
        <xsl:if test="$recorded &gt; $dynamic-substitutions-format">
            <xsl:message>
                <xsl:text>PTX:WARNING: the dynamic substitutions file records format version </xsl:text>
                <xsl:value-of select="$recorded"/>
                <xsl:text>,&#xa;</xsl:text>
                <xsl:text>but this version of PreTeXt reads version </xsl:text>
                <xsl:value-of select="$dynamic-substitutions-format"/>
                <xsl:text> at the newest.  The file was written by a&#xa;</xsl:text>
                <xsl:text>newer PreTeXt, and dynamic exercises may come out wrong.  Either update&#xa;</xsl:text>
                <xsl:text>PreTeXt, or delete the file and let this version generate it again.</xsl:text>
            </xsl:message>
        </xsl:if>
    </xsl:if>
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="dynamic-substitution"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="representations">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="representations"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="enrichment">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="enrichment"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="labels">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="labels"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="node()|@*" mode="augment">
    <xsl:param name="parent-struct" select="''"/>
    <xsl:param name="level" select="0"/>

    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="augment">
            <xsl:with-param name="parent-struct" select="$parent-struct"/>
            <xsl:with-param name="level" select="$level"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Catch-all identity: thread the inherited node-sets through. -->
<xsl:template match="node()|@*" mode="serial-stamp">
    <!-- no defaults: every caller passes the scopes explicitly -->
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$eq-nodes"/>
            <xsl:with-param name="fn-nodes" select="$fn-nodes"/>
            <xsl:with-param name="blocks-nodes" select="$blocks-nodes"/>
            <xsl:with-param name="figure-nodes" select="$figure-nodes"/>
            <xsl:with-param name="project-nodes" select="$project-nodes"/>
            <xsl:with-param name="exercise-nodes" select="$exercise-nodes"/>
            <xsl:with-param name="openproblem-nodes" select="$openproblem-nodes"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- ##################### -->
<!-- The Assembly Pipeline -->
<!-- ##################### -->

<!-- These templates initiate and create several iterations of -->
<!-- the source tree via modal templates.  Think of each as a  -->
<!-- "pass" through the source. Generally this constructs the  -->
<!-- new tree as a (text) result tree fragment and then we     -->
<!-- convert it into real XML nodes. These "real" trees have a -->
<!-- root element, as a result of the node-set() manufacture.  -->

<!-- The per-pass overview at the top of this file names what      -->
<!-- each variable below contributes; this sequence of             -->
<!-- variables is the authoritative pass order.                    -->

<!-- Grab private solutions first.  The "exercise" (and more) -->
<!-- that they belong to might be part of a version (have a   -->
<!-- @component attribute) and we don't want to miss that.    -->
<!-- It can happen next.                                      -->
<!-- Without a file of private solutions this pass is the identity, -->
<!-- so we do not build (or hold) a copy of the entire source: the   -->
<!-- result tree fragment stays empty, and the filtered union below  -->
<!-- hands the authored source itself to the next pass.              -->
<xsl:variable name="private-solutions-rtf">
    <xsl:if test="$b-private-solutions">
        <xsl:apply-templates select="/" mode="private-solutions"/>
    </xsl:if>
</xsl:variable>
<xsl:variable name="private-solutions" select="exsl:node-set($private-solutions-rtf)[$b-private-solutions] | (/)[not($b-private-solutions)]"/>

<xsl:variable name="version-rtf">
    <xsl:apply-templates select="$private-solutions" mode="version"/>
</xsl:variable>
<xsl:variable name="version" select="exsl:node-set($version-rtf)"/>

<!-- The "version" tree should be valid PreTeXt.  Furthermore, there  -->
<!-- should not be anymore modifications in subsequent passes which   -->
<!-- change the gross structure of a document (i.e. nature of the     -->
<!-- divisions).  The determination of various publisher variables,   -->
<!-- mostly relative to numbering depth, have default values that     -->
<!-- depend on the structure.  So aspects of this tree are consulted  -->
<!-- frequently in  publisher-variables.xsl.  This includes           -->
<!-- $html-chunk-level which inspects top-level division structure    -->
<!-- (book/part, article/section) to set chunking defaults.           -->
<!-- Also, note that this tree is useful for certain tasks, like      -->
<!-- validation, or reporting values of publisher variables, without  -->
<!-- regard to the subsequent passes.                                 -->
<!-- The version tree is the earliest assembly product a publisher   -->
<!-- variable may consult (structure can differ by version).  The    -->
<!-- passes up to, and including, "version" must never consult a     -->
<!-- variable defined from this tree — that is a circular reference, -->
<!-- detected only at runtime.  The "crossing point" facts in        -->
<!-- publisher-variables.xsl enumerate the consumers.                -->
<xsl:variable name="version-root" select="$version/pretext"/>
<xsl:variable name="version-docinfo" select="$version-root/docinfo"/>
<xsl:variable name="version-document-root" select="$version-root/*[not(self::docinfo)]"/>

<!-- This pass adds 100% internal identification for elements before   -->
<!-- anything has been added or subtracted. The tree it builds is used -->
<!-- for constructing "View Source" knowls in HTML output as a form of -->
<!-- always-accurate documentation.  And this is its only purpose.     -->
<!-- N.B.: see the $original-labeled tree used in the HTML conversion, -->
<!-- optionally, under the sway of a string parameter.  This is in the -->
<!-- (imported) pretext-view-source.xsl stylesheet.                    -->
<!-- Hack: to short-circuit this stylesheet, in the case of desiring   -->
<!-- the "version" tree *only*, we create an empty RTF.  This becomes  -->
<!-- a (essentially) empty node-set.  The empty node-set is the input  -->
<!-- the next pass, which will create an empty RTF, which will create  -->
<!-- an empty node-set.  Rinse.  Repeat.  Even though all these        -->
<!-- passes/variables are created, this is about a 17x speed-up.       -->
<!-- A review suggests there is no fixed overhead in any of these      -->
<!-- subsequent passes.                                                -->
<xsl:variable name="original-labeled-rtf">
    <!-- written as a "choose" for clarity -->
    <xsl:choose>
        <!-- short-circuit to stop after "version" -->
        <xsl:when test="$b-version-only"/>
        <!-- build on "version" to add original id's -->
        <xsl:otherwise>
            <!-- Duplicate identifiers are an authoring error, so the checks -->
            <!-- run on the version tree: private solutions are merged by    -->
            <!-- now (authored content in companion files), while version    -->
            <!-- resolution has already discarded the deliberate duplicates  -->
            <!-- that multi-version source may carry.  Later phases          -->
            <!-- manufacture copies of authored content (e.g. static         -->
            <!-- representations of interactive exercises), and those must   -->
            <!-- not be blamed on the author.                                -->
            <xsl:call-template name="duplication-check-xmlid">
                <xsl:with-param name="nodes" select="$version//*[@xml:id]"/>
                <xsl:with-param name="purpose" select="'authored'"/>
            </xsl:call-template>
            <xsl:call-template name="duplication-check-label">
                <xsl:with-param name="nodes" select="$version//*[@label]"/>
                <xsl:with-param name="purpose" select="'authored'"/>
            </xsl:call-template>
            <!-- checks are done, now add the "original-id" identification -->
            <xsl:apply-templates select="$version" mode="id-attribute">
                <!-- $parent-id defaults to 'root' in template -->
                <xsl:with-param name="attr-name" select="'original-id'"/>
            </xsl:apply-templates>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>
<xsl:variable name="original-labeled" select="exsl:node-set($original-labeled-rtf)"/>

<xsl:variable name="assembly-rtf">
    <xsl:apply-templates select="$original-labeled" mode="assembly"/>
</xsl:variable>
<xsl:variable name="assembly" select="exsl:node-set($assembly-rtf)"/>

<!-- Exercises are "tagged" as to their nature (division, inline, -->
<!-- worksheet, reading, project-like) and interactive exercises  -->
<!-- get more precise categorization.  The latter is used to      -->
<!-- determine if Runestone Services are loaded.                  -->

<xsl:variable name="exercise-rtf">
    <!-- initialize with default, 'inline' -->
    <xsl:apply-templates select="$assembly" mode="exercise">
        <xsl:with-param name="division" select="'inline'"/>
    </xsl:apply-templates>
</xsl:variable>
<xsl:variable name="exercise" select="exsl:node-set($exercise-rtf)"/>

<xsl:variable name="assembly-label-rtf">
    <xsl:apply-templates select="$exercise" mode="id-attribute">
        <!-- $parent-id defaults to 'root' in template -->
        <xsl:with-param name="attr-name" select="'assembly-id'"/>
    </xsl:apply-templates>
</xsl:variable>
<xsl:variable name="assembly-label" select="exsl:node-set($assembly-label-rtf)"/>

<!-- Make static substitutions for dynamic exercises.  This runs AFTER      -->
<!-- the @pi:assembly-id stamp because the substitution round trip keys on  -->
<!-- @pi:assembly-id: extract-dynamic.xsl writes it as the exercise_id, and -->
<!-- the lookup below reads it.  @pi:assembly-id is the early identifier    -->
<!-- both ends can compute consistently (the authored @label would only     -->
<!-- match for labeled exercises, missing unlabeled ones and tasks).        -->
<!-- The pass only acts on the elements enumerated in this presence         -->
<!-- test (see the "dynamic-substitution" templates); without any of        -->
<!-- them it is the identity, so we skip the full-tree copy.  NB: a         -->
<!-- new template in the mode must be reflected in this test.               -->
<xsl:variable name="b-has-dynamic-markup" select="boolean($assembly-label//setup | $assembly-label//numcmp | $assembly-label//strcmp | $assembly-label//jscmp | $assembly-label//mathcmp | $assembly-label//logic | $assembly-label//fillin[@ansobj] | $assembly-label//eval[@obj])"/>
<!-- "assembly-id-only" emits $assembly-label, so every pass below is   -->
<!-- discarded in that mode.  $post-label is empty then, and each later -->
<!-- pass takes its input from $post-label, so each one copies nothing  -->
<!-- and opens no file.  The test is written once, here, instead of     -->
<!-- being repeated in every pass: libxslt computes a global variable   -->
<!-- even when nothing uses the result, so a pass is not skipped just   -->
<!-- by leaving its variable unread.                                    -->
<xsl:variable name="post-label" select="$assembly-label[not($b-assembly-id-only)]"/>
<xsl:variable name="dynamic-rtf">
    <xsl:if test="$b-has-dynamic-markup">
        <xsl:apply-templates select="$post-label" mode="dynamic-substitution"/>
    </xsl:if>
</xsl:variable>
<xsl:variable name="dynamic" select="exsl:node-set($dynamic-rtf)[$b-has-dynamic-markup] | $post-label[not($b-has-dynamic-markup)]"/>

<xsl:variable name="representations-rtf">
    <xsl:apply-templates select="$dynamic" mode="representations"/>
</xsl:variable>
<xsl:variable name="representations" select="exsl:node-set($representations-rtf)"/>

<!-- Dependency: "repair" will fix some exercise representations, -->
<!-- especially coming from an "old" WeBWorK server, so the       -->
<!-- "repair" pass must come after the "representations" pass.    -->
<xsl:variable name="repair-rtf">
    <xsl:apply-templates select="$representations" mode="repair"/>
</xsl:variable>
<xsl:variable name="repair" select="exsl:node-set($repair-rtf)"/>

<!-- "enrichment" will *add* to the source automatically,  -->
<!-- such as footnotes with URLs that might not be visible -->
<!-- in static formats.                                    -->
<xsl:variable name="enrichment-rtf">
    <xsl:apply-templates select="$repair" mode="enrichment"/>
</xsl:variable>
<xsl:variable name="enrichment" select="exsl:node-set($enrichment-rtf)"/>

<!-- 2024-02-08: the construction of @label from @xml:id was split  -->
<!-- out of the "identification" pass, so is located here.  Perhaps -->
<!-- it can/should move earlier, maybe not.                         -->
<xsl:variable name="labels-rtf">
    <xsl:apply-templates select="$enrichment" mode="labels"/>
</xsl:variable>
<xsl:variable name="labels" select="exsl:node-set($labels-rtf)"/>

<xsl:variable name="identification-rtf">
    <xsl:apply-templates select="$labels" mode="id-attribute">
        <!-- $parent-id defaults to 'root' in template -->
        <xsl:with-param name="attr-name" select="'unique-id'"/>
    </xsl:apply-templates>
</xsl:variable>
<xsl:variable name="identification" select="exsl:node-set($identification-rtf)"/>

<xsl:variable name="augment-rtf">
    <xsl:apply-templates select="$identification" mode="id-coherence-check"/>
    <xsl:apply-templates select="$identification" mode="augment"/>
</xsl:variable>
<xsl:variable name="augment" select="exsl:node-set($augment-rtf)"/>

<xsl:variable name="serial-stamp-rtf">
    <!-- begin the pass with no counting scope yet -->
    <xsl:apply-templates select="$augment" mode="serial-stamp">
        <xsl:with-param name="eq-nodes" select="/.."/>
        <xsl:with-param name="fn-nodes" select="/.."/>
        <xsl:with-param name="blocks-nodes" select="/.."/>
        <xsl:with-param name="figure-nodes" select="/.."/>
        <xsl:with-param name="project-nodes" select="/.."/>
        <xsl:with-param name="exercise-nodes" select="/.."/>
        <xsl:with-param name="openproblem-nodes" select="/.."/>
    </xsl:apply-templates>
</xsl:variable>
<xsl:variable name="serial-stamp" select="exsl:node-set($serial-stamp-rtf)"/>

<!--                        IMPORTANT                           -->
<!--                                                            -->
<!-- Definitions that follow may be overridden after additional -->
<!-- per-conversion passes that takeoff from the final tree,    -->
<!-- here $augment.                                             -->
<!--                                                            -->
<!--    IF $augment CHANGES, SEARCH FOR AFFECTED CONVERSIONS    -->
<!--                                                            -->
<!-- 2023-03-20: braille conversion incorporares Nemeth braille -->

<!-- The main "pretext" element only has two possible children      -->
<!-- One is "docinfo", the other is "book", "article", etc.         -->
<!-- This is of interest by itself, or the root of content searches -->
<!-- And docinfo is the other child, these help prevent searching   -->
<!-- the wrong half.                                                -->
<!-- NB: source repair below converts a /mathbook to a /pretext     -->
<xsl:variable name="root" select="$serial-stamp/pretext"/>
<xsl:variable name="docinfo" select="$root/docinfo"/>
<xsl:variable name="document-root" select="$root/*[not(self::docinfo)]"/>
<xsl:variable name="bibinfo" select="$document-root/frontmatter/bibinfo"/>

<!-- ################# -->
<!-- Private Solutions -->
<!-- ################# -->

<!-- "solutions" here refers generically to "hint", "answer",  -->
<!-- and "solution" elements of an "exercise".  An author may  -->
<!-- wish to provide limited distribution of some solutions to -->
<!-- exercises, which we deem "private" here.  If a            -->
<!-- "private-solutions-file" is provided, it will be mined    -->
<!-- for these private solutions.                              -->

<!-- Note: there may be (nested) "pi:privatesolutionsdivision"  -->
<!-- elements in this file.  They are largely meaningless, but  -->
<!-- are necessary if an author wants to modularize their       -->
<!-- collection across multiple files.  Then each file can be a -->
<!-- single overall element.  (We expect/require no additional  -->
<!-- structure in this file.)  The consequence is the "//" in   -->
<!-- each expression below.                                     -->
<!-- NB: relative to *original* source file/tree                -->
<xsl:variable name="privatesolns" select="document($private-solutions-file, $original)"/>
<xsl:variable name="n-hint"     select="$privatesolns/pi:privatesolutions//hint"/>
<xsl:variable name="n-answer"   select="$privatesolns/pi:privatesolutions//answer"/>
<xsl:variable name="n-solution" select="$privatesolns/pi:privatesolutions//solution"/>

<!-- Note that when there are any private solutions then we make a copy that     -->
<!--   - does not preserve interstitial text nodes (whitespace indentation)      -->
<!--   - preserves things like "feedback", "choices", but may reorder them       -->
<!--     to occur before solutions                                               -->
<!--   - this happens for *every* "exercise", even if it does not have           -->
<!--     private solutions                                                       -->
<!--                                                                             -->
<!-- So there could be                                                           -->
<!--   - more care about placing new "hint", "answer", "solution"                -->
<!--     in the right order, perhaps by exploding this out into templates        -->
<!--   - condition on                                                            -->
<!--     $n-hint[@ref=$the-id]|$n-answer[@ref=$the-id]|$n-solution[@ref=$the-id] -->
<!--     to only manipulate an exercise that needs it                            -->
<xsl:template match="exercise|task" mode="private-solutions">
    <xsl:choose>
        <!-- $b-private-solutions is a publisher variable determined   -->
        <!-- by the specification of a file of private solutions there -->
        <xsl:when test="$b-private-solutions">
            <xsl:variable name="the-id" select="@xml:id"/>
            <xsl:copy>
                <!-- attributes, then all elements that are not solutions                               -->
                <!--   unstructured exercise:  "p" etc, then solutions OK even if schema violation?     -->
                <!--   structured exercise: copy statement, then interleave solutions                   -->
                <!--   non-terminal Task: introduction, task, conclusion                                -->
                <!--   terminal unstructured task: "p" etc, then solutions OK even if schema violation? -->
                <!--   terminal structured task: copy statement, then interleave solutions              -->
                <!-- TODO: defend against non-terminal task, unstructured cases      -->
                <!-- (identify proper structure + non-empty union of three additions -->
                <!-- Fix unstructured cases by inserting "statement",                -->
                <!-- warn about non-terminal task case and drop additions (error)    -->
                <xsl:apply-templates select="*[not(self::hint or self::answer or self::solution)]|@*" mode="private-solutions"/>
                <!-- hints, answers, solutions; first regular, second private -->
                <xsl:apply-templates select="hint" mode="private-solutions"/>
                <xsl:apply-templates select="$n-hint[@ref=$the-id]" mode="private-solutions"/>
                <xsl:apply-templates select="answer" mode="private-solutions"/>
                <xsl:apply-templates select="$n-answer[@ref=$the-id]" mode="private-solutions"/>
                <xsl:apply-templates select="solution" mode="private-solutions"/>
                <xsl:apply-templates select="$n-solution[@ref=$the-id]" mode="private-solutions"/>
            </xsl:copy>
        </xsl:when>
        <!-- otherwise, just a straight xerox -->
        <xsl:otherwise>
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="private-solutions"/>
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- ######## -->
<!-- Versions -->
<!-- ######## -->

<!-- The version feature (@component markings) allows for   -->
<!-- invalid PreTeXt source.  For example, an author might  -->
<!-- have two different "docinfo" elements for some reason. -->
<!-- So we allow a sort of pre-PreTeXt, which does not      -->
<!-- satisfy the schema.  Support for the "custom" element  -->
<!-- is similar in spirit.  So very early on, we unravel    -->
<!-- (resolve) these features, and if used properly the     -->
<!-- result will be valid PreTeXt, according to the schema. -->

<!-- Only elements "marked" with @component need to be      -->
<!-- examined, the catch-all xerox template above suffices. -->
<xsl:template match="*[@component]" mode="version">
    <!-- prepare for test below -->
    <xsl:variable name="single-component-fenced" select="concat('|', normalize-space(@component), '|')"/>
    <xsl:choose>
        <!-- version scheme not elected, so use element no matter what -->
        <!-- note that @include="" yields "||" in test here            -->
        <xsl:when test="$components-fenced = ''">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="version"/>
            </xsl:copy>
        </xsl:when>
        <!-- version scheme elected, element participating, so use element -->
        <!-- if it is a component in publisher's selection of components   -->
        <xsl:when test="contains($components-fenced, $single-component-fenced)">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="version"/>
            </xsl:copy>
        </xsl:when>
        <!-- version scheme elected, element participating, but its component -->
        <!-- has not been selected in publisher file, so it gets dropped here -->
        <xsl:otherwise/>
    </xsl:choose>
</xsl:template>

<!-- We use various ad-hoc, non-author elements in lots of places.     -->
<!-- Best to prefix them all with a namespace and keep them separate   -->
<!-- from author-land.  Thus, the "pretext internal" namespace with    -->
<!-- prefix/alias "pi". But we don't want authors to use some of       -->
<!-- these elements, like the presentational "pi:bold"!                -->
<!--                                                                   -->
<!-- So we kill these elements (and attributes) early.  So this is     -->
<!-- really unrelated to versions, but tere should be little danger in -->
<!-- doing in here and now.  The "private-solutions" pass is a place   -->
<!-- where files use this prefix, so maybe best not to do it there.    -->

<xsl:template match="pi:*" mode="version"/>
<xsl:template match="@pi:*" mode="version"/>

<!-- Exception: when file attribution has been requested (below), a -->
<!-- @pi:source-uri stamped on the source (by the pretext/pretext   -->
<!-- script's include mechanism, say) rides through the version     -->
<!-- pass, so a diagnostic can name the file where a problem lies.  -->
<xsl:template match="@pi:source-uri" mode="version">
    <xsl:if test="$b-file-attribution">
        <xsl:copy/>
    </xsl:if>
</xsl:template>

<!-- The xinclude mechanism stamps an @xml:base attribute onto the  -->
<!-- root element of every included file.  We drop it during the    -->
<!-- version pass, so the assembled source carries none, and the    -->
<!-- schema need not permit it.  But when file attribution has been -->
<!-- requested (validation, say, so a problem can be located in the -->
<!-- file where it lies) we mint a @pi:source-uri instead,          -->
<!-- consistent with the other "pi:" provenance attributes.         -->
<xsl:template match="@xml:base" mode="version">
    <xsl:if test="$b-file-attribution">
        <xsl:attribute name="pi:source-uri">
            <xsl:value-of select="."/>
        </xsl:attribute>
    </xsl:if>
</xsl:template>

<!-- The "custom" element, with a @name in an auxiliary file,     -->
<!-- and a @ref in a source file, allows for custom substitutions -->

<!-- If the publisher variable  $customizations-file  is bad, -->
<!-- then  document()  will raise an error.  The empty string -->
<!-- (default) will not raise an error, so if not specified,  -->
<!-- no problem.  But an empty string, and an attempted       -->
<!-- access in the template *will* raise the error below.     -->
<xsl:variable name="customizations" select="document($customizations-file, $original)"/>

<!-- Set the key, nodes to be located are named -->
<!-- "custom" within the file just accessed     -->
<!-- For each one, @name is the search term     -->
<!-- that will locate it: the key, the index    -->
<xsl:key name="name-key" match="custom" use="@name"/>

<xsl:template match="custom[@ref]" mode="version">
    <!-- We need to get the @ref attribute now, due to a context shift -->
    <!-- And the "custom" context also, for use in a location report   -->
    <xsl:variable name="the-ref" select="string(@ref)"/>
    <xsl:variable name="the-custom" select="."/>
    <!-- Now the context shift to query the customizations -->
    <xsl:for-each select="$customizations">
        <xsl:variable name="the-lookup" select="key('name-key', $the-ref)"/>
        <!-- This is an AWOL node, not empty content (which is allowed) -->
        <xsl:if test="not($the-lookup)">
            <xsl:text>[MISSING CUSTOM CONTENT HERE]</xsl:text>
            <xsl:message>PTX:ERROR:     lookup for a "custom" element with @name set to "<xsl:value-of select="$the-ref"/>" has failed, while consulting the customization file "<xsl:value-of select="$customizations-file"/>".  Output will contain "[MISSING CUSTOM CONTENT HERE]" instead</xsl:message>
            <xsl:apply-templates select="$the-custom" mode="location-report"/>
        </xsl:if>
        <!-- Copying the contents of "custom" via the "version" templates  -->
        <!-- will keep the "pi" namespace from appearing in places, as it  -->
        <!-- will with an "xsl:copy-of" on the same node set.  But it      -->
        <!-- allows nested "custom" elements.                              -->
        <!--                                                               -->
        <!-- Do we want authors to potentially create cyclic references?   -->
        <!-- A simple 2-cycle test failed quickly and obviously, so it     -->
        <!-- will be caught quite easily, it seems.                        -->
        <xsl:apply-templates select="$the-lookup/node()" mode="version"/>
    </xsl:for-each>
</xsl:template>

<!-- ############## -->
<!-- Identification -->
<!-- ############## -->

<!-- This section is the id-attribute pass.  One mechanism (the    -->
<!-- "id-attribute" templates below) is applied three times by     -->
<!-- the chain (passes 3, 6 and 12), stamping @pi:original-id,     -->
<!-- @pi:assembly-id and @pi:unique-id in turn.  The "labels" pass -->
<!-- runs between the second and third of these (see "Labels"),    -->
<!-- and the @label values it creates are what the @pi:unique-id   -->
<!-- stamp reads here.  The contract that follows is what lets     -->
<!-- the three stampings agree element-for-element.                -->

<!-- ################################################### -->
<!-- Structural Contract for Identification Passes       -->
<!-- ################################################### -->

<!-- The "id-attribute" template (mode="id-attribute")       -->
<!-- produces identification strings by a depth-first        -->
<!-- traversal, encoding each element's position among its   -->
<!-- siblings.  An element's ID depends only on its          -->
<!-- ancestors and their sibling positions, so the template  -->
<!-- is fast, unique, and local.                             -->
<!--                                                         -->
<!-- This template is applied three times, producing three   -->
<!-- attributes on every element:                            -->
<!--                                                         -->
<!--   @pi:original-id                                       -->
<!--       after "version", before any additions             -->
<!--   @pi:assembly-id                                       -->
<!--       after "exercise", before "representations"        -->
<!--   @pi:unique-id                                         -->
<!--       after "labels", the final structural form         -->
<!--                                                         -->
<!-- The architecture relies on one critical invariant:      -->
<!--                                                         -->
<!--   BETWEEN ANY TWO ID-STAMPING PASSES, NO INTERVENING    -->
<!--   PASS MAY CHANGE THE NUMBER OR ORDER OF SIBLING        -->
<!--   ELEMENTS AT ANY LEVEL OF THE TREE, EXCEPT WITHIN A    -->
<!--   SUBTREE THAT IS BEING WHOLLY REPLACED (SAME PARENT,   -->
<!--   SAME SIBLING POSITION).                               -->
<!--                                                         -->
<!-- This invariant holds because:                           -->
<!--                                                         -->
<!--   (a) Replacements (e.g. an interactive replaced by a   -->
<!--       static sidebyside) occupy the same sibling        -->
<!--       position as the original element.                 -->
<!--                                                         -->
<!--   (b) Replacements are never nested: a replaced         -->
<!--       subtree does not itself contain another element   -->
<!--       that will be replaced at a different sibling      -->
<!--       position.                                         -->
<!--                                                         -->
<!--   (c) Each ID-stamping pass is placed at a moment when  -->
<!--       the tree is structurally quiet - no pending       -->
<!--       changes will shift sibling positions before the   -->
<!--       next one.                                         -->
<!--                                                         -->
<!-- Consequence: the id-attribute template produces the     -->
<!-- same ID for every non-replaced element across passes,   -->
<!-- and a replaced element's ID (consumed during isolation) -->
<!-- remains valid through to the substitution phase.        -->
<!--                                                         -->
<!-- Any future pass that inserts or removes sibling         -->
<!-- elements (rather than replacing in-place) MUST be       -->
<!-- placed so that it does not fall between two ID-stamping -->
<!-- passes, or else the identification will silently drift. -->

<!-- This general-purpose template constructs unique strings as part   -->
<!-- of a natural depth-first exploration of the tree.  Strings are    -->
<!-- reset when provided by authors on elements (ideally via @label).  -->
<!-- Otherwise the tree structure is reflected by location at each     -->
<!-- level of the subtree rooted at the last authored string.  A bit   -->
<!-- unsightly, and only partially effective to unwind numbers back    -->
<!-- to an element.  But super-fast to construct and reliably unique   -->
<!-- (though an author could provide two strings that make a conflict, -->
<!-- we believe).                                                      -->
<xsl:template match="*" mode="id-attribute">
    <xsl:param name="parent-id"  select="'root'"/>
    <xsl:param name="attr-name"  select="''"/>

    <xsl:copy>
        <!-- duplicate all attributes -->
        <xsl:apply-templates select="@*" mode="id-attribute"/>
        <!-- * Strategy is much like @pi:original-id but maybe needs as much care         -->
        <!-- * Element counts are used to reflect document tree structure                 -->
        <!-- * Non-numeric separator needed to preserve uniqueness (e.g.1-12 != 11-2).    -->
        <!-- * Separators are therefore hyphens                                           -->
        <!-- * Colons as separators would create confusion with namespaces                -->
        <!-- * Salt added to authored values could decrease risk of collision             -->
        <xsl:variable name="new-id">
            <xsl:choose>
                <!-- A @label might be authored.  Or not authored, and   -->
                <!-- then an authored @xml:id was promoted into a @label -->
                <xsl:when test="@label">
                    <xsl:value-of select="@label"/>
                </xsl:when>
                <!-- This mimics the upgrade of an authored xml:id to a label -->
                <!-- NB: this might not ever happen in some passes, when an   -->
                <!-- @xml:id value has been upgraded to a @label value in a   -->
                <!-- prior pass, because if there was an @xml:id, then it     -->
                <!-- was upgraded to being a @label and if this "choose" gets -->
                <!-- here, then the next test is false.                       -->
                <xsl:when test="@xml:id">
                    <xsl:value-of select="@xml:id"/>
                </xsl:when>
                <!-- Author has not supplied any sort of identification, no -->
                <!-- @label and no @xml:id.  So we automatically devise one -->
                <xsl:otherwise>
                    <xsl:value-of select="$parent-id"/>
                    <xsl:text>-</xsl:text>
                    <!-- Start counting from 1, easier to debug -->
                    <xsl:number value="count(preceding-sibling::*) + 1"/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:variable>
        <xsl:attribute name="pi:{$attr-name}">
            <xsl:value-of select="$new-id"/>
        </xsl:attribute>
        <!-- recurse -->
        <xsl:apply-templates select="node()" mode="id-attribute">
            <xsl:with-param name="parent-id" select="$new-id"/>
            <xsl:with-param name="attr-name" select="$attr-name"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- There is no real purpose to put identification onto an     -->
<!-- (X)HTML element floating around as part of an interactive. -->
<xsl:template match="pf:*|xhtml:*" mode="id-attribute">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" mode="id-attribute"/>
    </xsl:copy>
</xsl:template>

<!-- Diagnostic: verify @pi:assembly-id equals @pi:unique-id for -->
<!-- every element that carries both.  A mismatch indicates      -->
<!-- a structural change between the two ID-stamping passes      -->
<!-- that violates the identification contract documented        -->
<!-- near the top of this stylesheet.  Gated by the              -->
<!-- assembly.debug parameter; does nothing when off.            -->

<xsl:template match="node()|@*" mode="id-coherence-check">
    <xsl:if test="$b-assembly-debug">
        <xsl:for-each select=".//*[@pi:assembly-id and @pi:unique-id and not(@pi:assembly-id = @pi:unique-id)]">
            <xsl:message>
                <xsl:text>PTX:DEBUG:  @pi:assembly-id / @pi:unique-id mismatch on &lt;</xsl:text>
                <xsl:value-of select="local-name()"/>
                <xsl:text>&gt;: assembly-id="</xsl:text>
                <xsl:value-of select="@pi:assembly-id"/>
                <xsl:text>" unique-id="</xsl:text>
                <xsl:value-of select="@pi:unique-id"/>
                <xsl:text>"</xsl:text>
            </xsl:message>
        </xsl:for-each>
    </xsl:if>
</xsl:template>

<!-- ######################## -->
<!-- Bibliography Manufacture -->
<!-- ######################## -->

<!-- Initial experiment, overall "references" flagged with -->
<!-- a @source filename as the place to go get a list of   -->
<!-- candidate "biblio" (in desired order)                 -->
<!-- NB: this needs a rethink when revisited.  A file of   -->
<!-- bibliography items can be specified, perhaps in       -->
<!-- in docinfo (runs with the source?), formed as         -->
<!-- $biblios in -common, and then mined for matches not   -->
<!-- explicitly present?                                   -->
<xsl:template match="backmatter/references[@source]" mode="assembly">
    <!-- Grab the list.  The filename is relative to the authored -->
    <!-- document, so the lookup anchors on $original: the context -->
    <!-- node lives in a constructed tree, which has no base URI   -->
    <xsl:variable name="biblios" select="document(@source, $original)"/>
    <!-- Copy the "references" element (could be literal, but maybe not in "text" output mode) -->
    <xsl:copy>
        <!-- @source attribute not needed in enhanced source -->
        <xsl:apply-templates select="@*[not(local-name() = 'source')]" mode="assembly"/>
        <!-- likely more elements to duplicate, consult schema -->
        <xsl:apply-templates select="title" mode="assembly"/>
        <!-- Look at each "biblio" in the external file -->
        <xsl:for-each select="$biblios/pretext-biblios/biblio">
            <xsl:variable name="the-id" select="@xml:id"/>
            <xsl:message>PTX:DEBUG: @xml:id of &lt;biblio&gt; in bibliography file: <xsl:value-of select="$the-id"/></xsl:message>
            <!-- Building duplicate, so look in this pass's input  -->
            <!-- tree (the version in play) for an "xref" pointing -->
            <!-- to the current context "biblio"                   -->
            <xsl:if test="$original-labeled//xref[@ref = $the-id]">
                <xsl:message>PTX:DEBUG:  Located this &lt;biblio&gt; cited in original source</xsl:message>
                <xsl:apply-templates select="." mode="assembly"/>
            </xsl:if>
        </xsl:for-each>
    </xsl:copy>
</xsl:template>

<!-- ########################################################## -->
<!-- ########################################################## -->
<!-- 2025-08-13: these error checks are not really right.       -->
<!-- Testing for existence of the file of references seems      -->
<!-- to be the big problem?  If it does not exist, the          -->
<!-- processor just fails altogether?  Further, these variables -->
<!-- might be renamed as positive events, to ease the logic.    -->
<!-- ########################################################## -->
<!-- ########################################################## -->

<!-- To use CSL styles, we need to determine if a few things are in place -->
<!-- (a)  are we even using CSL style files at all                        -->
<!-- (b)  if we are extracting or not                                     -->
<!-- (c)  if the desired generated references and citations exist         -->
<!-- (d)  if the generated file exists, built with the same style file?   -->
<!-- Without everything in place, we just copy references/"biblio".        -->
<!-- Here comes the gauntlet -->

<!-- $b-using-csl-styles: a consequence of opting in via publisher file         -->
<!-- $b-extracting-biblio: set here and overridden in the extraction stylesheet -->

<!-- two error conditions -->
<!-- 2025-08-13: this variable is always false -->
<xsl:variable name="missing-csl-file">
    <xsl:choose>
        <!-- can't be missing if we don't need it, and we   -->
        <!-- don't induce panic by looking for it, when it  -->
        <!-- isn't called for, and getting ominous warnings -->
        <xsl:when test="not($b-using-csl-styles)">
            <xsl:text>no</xsl:text>
        </xsl:when>
        <xsl:otherwise>
            <xsl:text>no</xsl:text>
            <!-- this is only a test, variable is local and not retained -->
            <!-- <xsl:variable name="the-references" -->
                <!-- select="document($csl-file, $original)/pi:csl-references"/> -->
            <!-- since we build the file, condiition on the size of -->
            <!-- this node-set:  one (good) or none (bad, missing)  -->
            <!-- <xsl:choose> -->
                <!-- file looks good -->
                <!-- <xsl:when test="count($the-references) = 1"> -->
                    <!-- <xsl:text>no</xsl:text> -->
                <!-- </xsl:when> -->
                <!-- nothing came of document() -->
                <!-- <xsl:otherwise> -->
                    <!-- <xsl:text>yes</xsl:text> -->
                    <!-- and we take the opportunity to say so, just once, and early on -->
                    <!-- <xsl:message>PTX:ERROR:     your publisher file indicates the use of a Citation Stylesheet Language (CSL) specification for references, but we have not located your file of generated references and citations at "<xsl:value-of select="$csl-file"/>".  We will fall back to default processing in order to proceed.</xsl:message> -->
                <!-- </xsl:otherwise> -->
            <!-- </xsl:choose> -->
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>
<xsl:variable name="b-missing-csl-file" select="$missing-csl-file = 'yes'"/>

<!-- This is the case of having a style file specified in    -->
<!-- the publisher file AND having a generated file of       -->
<!-- references and citations AND the two versions of the    -->
<!-- style file used do not match.  The first two conditions -->
<!-- should always be consulted.                             -->
<xsl:variable name="style-file-mismatch">
    <xsl:choose>
        <!-- we do not warning at the end of this template, -->
        <!-- just because the file itself does not exist    -->
        <xsl:when test="not($b-using-csl-styles) or $missing-csl-file">
            <xsl:text>no</xsl:text>
        </xsl:when>
        <!-- now we are using CSL styles and we do have a file to interrogate -->
        <xsl:otherwise>
            <!-- this is only a test, variable is local and not retained -->
            <xsl:variable name="the-references"
                select="document($csl-file, $original)/pi:csl-references"/>
            <!-- attribute in generated file saying which style file was used -->
            <xsl:variable name="csl-style-file-for-generated">
                <xsl:value-of select="$the-references/@csl-style-file"/>
            </xsl:variable>
            <xsl:choose>
                <xsl:when test="$csl-style-file = $csl-style-file-for-generated">
                    <xsl:text>no</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:text>yes</xsl:text>
                    <!-- and we take the opportunity to say so, just once, and early on -->
                    <xsl:message>PTX:WARNING:     your publisher file indicates the use of one Citation Stylesheet Language (CSL) specification for references ("<xsl:value-of select="$csl-style-file"/>"), but your file of generated references and citations at "<xsl:value-of select="$csl-file"/>" was built using a different CSL style file ("<xsl:value-of select="$csl-style-file-for-generated"/>").  We will fall back to default processing in order to proceed.</xsl:message>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
</xsl:variable>
<xsl:variable name="b-style-file-mismatch" select="$style-file-mismatch = 'yes'"/>

<!-- ########################################################## -->
<!-- ########################################################## -->
<!-- 2025-08-13: end of incomplete error logic.                 -->
<!-- ########################################################## -->
<!-- ########################################################## -->

<xsl:template match="backmatter/references[not(@source)]" mode="assembly">
    <xsl:choose>
        <!-- duplicate for biblio extraction process or if using -->
        <!-- default (simplistic) PreTeXt bibliography support   -->
        <xsl:when test="$b-extracting-biblio or not($b-using-csl-styles)">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="assembly"/>
            </xsl:copy>
        </xsl:when>
        <!-- not extracting, so doing a conversion, and also     -->
        <!-- must be using CSL styles, so a collection of        -->
        <!-- processed references and citations should exist     -->
        <!-- but maybe not (this "when" is separate for clarity) -->
        <xsl:when test="$b-missing-csl-file or $b-style-file-mismatch">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="assembly"/>
            </xsl:copy>
        </xsl:when>
        <!-- not extracting, using CSL, have a generated file,  -->
        <!-- matching styles in publihser file and in generated -->
        <!-- file.  We can just do it.                          -->
        <xsl:otherwise>
            <!-- $csl-file is defined in the publisher-variables stylesheet, -->
            <xsl:variable name="the-references"
                select="document($csl-file, $original)/pi:csl-references"/>
            <!-- Now, a "choose" within the above "otherwise".  If the file -->
            <!-- is missing or mis-matched, we just duplicate.  Otherwise,  -->
            <!-- we finally make replacment entries in the "references".    -->
            <xsl:copy>
                <!-- duplicate attributes on "references" -->
                <xsl:apply-templates select="@*" mode="assembly"/>
                <!-- duplicate/massage each bibliographic entry   -->
                <!-- realize as a standard "raw" type reference,  -->
                <!-- though make take this tyepe private some day -->
                <xsl:for-each select="$the-references/pi:csl-biblio">
                    <biblio type="raw">
                        <!-- An @xml:id is likely present, but conceivably -->
                        <!-- absent if a "biblio" is never referenced.     -->
                        <!-- Preserve it through the process.              -->
                        <xsl:copy-of select="@xml:id"/>
                        <!-- preserve numeric identification -->
                        <xsl:attribute name="numeric">
                            <xsl:value-of select="@numeric"/>
                        </xsl:attribute>
                        <xsl:apply-templates select="node()" mode="assembly"/>
                    </biblio>
                </xsl:for-each>
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="xref" mode="assembly">
    <!-- determine if this "xref" points to a "biblio",  -->
    <!-- and really just a backmatter/references/biblio, -->
    <!-- plus using CSL styles for citations             -->
    <xsl:variable name="is-biblio-target">
        <xsl:choose>
            <!-- not doing styles, target is not of interest -->
            <!-- to us anyway and we want to bail out as     -->
            <!-- quickly and as easily as possible           -->
            <xsl:when test="not($b-using-csl-styles)">
                <xsl:text>no</xsl:text>
            </xsl:when>
            <!-- first/last page-range device -->
            <xsl:when test="@first">
                <xsl:text>no</xsl:text>
            </xsl:when>
            <xsl:when test="@provisional">
                <xsl:text>no</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:variable name="tokenized-refs" select="str:tokenize(@ref, ' ,')"/>
                <xsl:variable name="first-ref">
                    <xsl:value-of select="$tokenized-refs[1]"/>
                </xsl:variable>
                <!-- The context "xref" sits in this pass's input tree, so -->
                <!-- id() searches exactly the version in play             -->
                <xsl:choose>
                    <xsl:when test="id($first-ref)/self::biblio/parent::references/parent::backmatter">
                        <xsl:text>yes</xsl:text>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:text>no</xsl:text>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="b-is-biblio-target" select="$is-biblio-target = 'yes'"/>
    <!--  -->
    <xsl:choose>
        <!-- duplicate for biblio extraction process or if the "xref"  -->
        <!-- is not a candidate for replacement by a CSL citation      -->
        <!-- Note: not using CSL styles immediately determines that    -->
        <!-- this "xrref" is not a "biblio target" and a copy is       -->
        <!-- made here, immediately as well                            -->
        <xsl:when test="not($b-is-biblio-target) or $b-extracting-biblio">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="assembly"/>
            </xsl:copy>
        </xsl:when>
        <!-- a candidate for replacement  -->
        <xsl:otherwise>
            <xsl:variable name="the-xref-id">
                <xsl:value-of select="@pi:original-id"/>
            </xsl:variable>
            <xsl:variable name="matched-citation" select="document('gen/references/csl-bibliography.xml', $original)/pi:csl-references/pi:csl-citation[@xml:id = $the-xref-id]"/>
            <xsl:copy-of select="$matched-citation"/>
            <!-- WARN ON UNMATCHED -->
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- We cosmetically change a "drag-n-drop" style matching problem from    -->
<!-- being signaled by "matches" and instead call it a "cardsort" problem, -->
<!-- which is a more accurate description of the interface from Runestoone -->
<!-- Services.  We can't wait for "repair" since we do manipulation of     -->
<!-- exercises in advance.  Why's that?  So an old WeBWorK server can send -->
<!-- back old PreTeXt and have it be repaired.  So we do this change in an -->
<!-- earlier pass.                                                         -->
<xsl:template match="exercise/matches|project/matches|activity/matches|exploration/matches|investigation/matches|task/matches" mode="assembly">
    <!-- literal element gets namespace declarations -->
    <xsl:element name="cardsort">
        <xsl:apply-templates select="node()|@*" mode="assembly"/>
    </xsl:element>
</xsl:template>

<!-- Ordering for static version of a cardsort exercise no longer goes   -->
<!-- on the "match", due to the possibility of multiple "premise" inside -->
<!-- the "match".  If the problem is an old-style 1-1 corrspondence, we  -->
<!-- will move it onto *all* contained "premise", which will be fine if  -->
<!-- there is one or less within the "match".  We allow for a recent     -->
<!-- transition from "matches" to "cardsort".                            -->
<xsl:template match="matches/match/@order|cardsort/match/@order" mode="assembly"/>

<xsl:template match="matches/match/premise|cardsort/match/premise" mode="assembly">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="assembly"/>
        <xsl:if test="parent::match/@order">
            <xsl:attribute name="order">
                <xsl:value-of select="parent::match/@order"/>
            </xsl:attribute>
        </xsl:if>
        <xsl:apply-templates select="node()" mode="assembly"/>
    </xsl:copy>
</xsl:template>

<!-- WeBWorK @copy resolution -->

<!-- A "webwork" with a @copy attribute is a lightweight reference  -->
<!-- to another authored "webwork".  We resolve the copy here in    -->
<!-- the assembly pass by pulling the target's content into a new   -->
<!-- "webwork" element.                                             -->
<xsl:template match="webwork[@copy]" mode="assembly">
    <!-- Find the target.  Maybe. -->
    <xsl:variable name="target" select="id(@copy)"/>
    <!-- Trap potential pitfalls and record part of an error -->
    <!-- message.  Use a non-empty error message as a signal -->
    <!-- to bail out gracefully on the copy.                 -->
    <xsl:variable name="error-message-for-copy">
        <xsl:choose>
            <xsl:when test="not($target)">
                <xsl:text>the @copy attribute points to nothing, check the spelling?</xsl:text>
            </xsl:when>
            <xsl:when test="not($target/self::webwork)">
                <xsl:text>the @copy attribute points to a "</xsl:text>
                <xsl:value-of select="local-name($target)"/>
                <xsl:text>" element, not another "webwork".</xsl:text>
            </xsl:when>
            <xsl:when test="$target/self::webwork[@source]">
                <xsl:text>the @copy attribute points a "webwork" with a @source attribute.  (Replace the @copy by the @source?)</xsl:text>
            </xsl:when>
            <xsl:when test="$target/self::webwork[@copy]">
                <xsl:text>the @copy attribute points to "webwork" with a @copy attribute. Sorry, we are not that sophisticated.</xsl:text>
            </xsl:when>
            <!-- Presumably OK, no error message -->
            <xsl:otherwise/>
        </xsl:choose>
    </xsl:variable>

    <xsl:choose>
        <!-- no error means to proceed with copy -->
        <xsl:when test="$error-message-for-copy = ''">
            <xsl:copy>
                <xsl:attribute name="pi:copied-from">
                    <xsl:value-of select="@copy"/>
                </xsl:attribute>
                <!-- Duplicate attributes, but remove the @copy attribute -->
                <!-- used as a signal here.  We don't want to copy this   -->
                <!-- again after we have been to the WeBWorK server.      -->
                <xsl:apply-templates select="@*[not(local-name(.) = 'copy')]" mode="assembly"/>
                <!-- The @seed makes the problem different, and there are also  -->
                <!-- unique identifiers, so grab any other attributes of the    -->
                <!-- original, but exclude these while formulating a copy/clone. -->
                <xsl:apply-templates select="$target/@*[(not(local-name(.) = 'id')) and
                                                        (not(local-name(.) = 'label')) and
                                                        (not(local-name(.) = 'seed'))]" mode="assembly"/>
                <!-- NB: authored WeBWorK content never has @xml:id or @label, -->
                <!-- so no scrubbing of unique IDs is needed here.             -->
                <xsl:apply-templates select="$target/node()" mode="assembly"/>
            </xsl:copy>
        </xsl:when>
        <!-- with an error in formulation, drop in something very -->
        <!-- similar in gross form, and alert at the console      -->
        <xsl:otherwise>
            <xsl:copy>
                <!-- Carry over as much metadata as possible -->
                <xsl:apply-templates select="@*[not(local-name(.) = 'copy')]" mode="assembly"/>
                <!-- Now a minimal, but correct PreTeXt, WeBWorK problem into the  -->
                <!-- extraction machinery, and out into all possible final outputs  -->
                <statement>
                    <p>
                        A WeBWorK problem right here was meant to be a copy of another problem,
                        but potentially with different randomization, but there was a failure.
                        The <c>@copy</c> attribute was set to <c><xsl:value-of select="@copy"/></c>.
                        Please report me, so the publisher can get more details by searching the
                        runtime output for <q><c>PTX:ERROR</c></q>.
                    </p>
                </statement>
            </xsl:copy>
            <!-- minimalist report into source, more at console -->
            <xsl:message>PTX:ERROR:   A WeBWorK problem has a @copy attribute with value "<xsl:value-of select="@copy"/>".</xsl:message>
            <xsl:message>             However, the problem did not render:</xsl:message>
            <xsl:message><xsl:text>             </xsl:text><xsl:value-of select="$error-message-for-copy"/></xsl:message>
            <xsl:message>             A placeholder problem will appear in your output instead.</xsl:message>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- ######### -->
<!-- Exercises -->
<!-- ######### -->

<!-- Exercises, and their kin, are complicated.  They come in five types   -->
<!-- (inline, divisional, reading, worksheet, project-like) based largely  -->
<!-- on location.  They can be static, interactive in HTML, interactive    -->
<!-- on a server.  Interactive versions come in many flavors, such as      -->
<!-- short answer, multiple choice, true/false, Parson, cardsort, fill-in, -->
<!-- and so on.  Their solutions (hint, answer, solution) apear, or do not -->
<!-- appear, where born or in specialized "solutions" divisions.  We       -->
<!-- scribble on each to record as much as we can right now.  It'll be     -->
<!-- useful below and forever.                                             -->

<!-- Record exercise ancestors/location-->
<!-- An "exercise" can be in one of four places.  We reset the parameter   -->
<!-- as we pass through.  Default is "inline" and we initialize with that  -->
<!-- value.  These three specialized divisions are always terminal, so we  -->
<!-- will never find an inline exercise contained within.  We allow        -->
<!-- publisher customization of exercises based on these locations, *and*  -->
<!-- for project-like.  These templates are just about divisions.          -->

<xsl:template match="reading-questions" mode="exercise">
    <xsl:param name="division"/>

    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="exercise">
            <xsl:with-param name="division" select="'reading'"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<xsl:template match="worksheet" mode="exercise">
    <xsl:param name="division"/>

    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="exercise">
            <xsl:with-param name="division" select="'worksheet'"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<xsl:template match="exercises" mode="exercise">
    <xsl:param name="division"/>

    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="exercise">
            <xsl:with-param name="division" select="'divisional'"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Annotate "exercise" and PROJECT-LIKE -->
<!-- Pre-processing here is entirely about supporting interactive       -->
<!-- exercises powered by Runestone Services.  We allow publisher       -->
<!-- options to control interactivity of "short answer" questions       -->
<!-- when hosted on a server, so that is why locations are being noted. -->
<!--   1.  "exercise-customization" refers to the situation where       -->
<!--       certain publication options can vary behavior or visibility  -->
<!--   2.  "exercise-interactive" refers to the type of                 -->
<!--        interactivity, "static" is the default                      -->
<!--                                                                    -->
<!-- TODO:                                                              -->
<!-- 1.  Expand to WW, example-like, and task                           -->
<!-- 2.  Insert "statement" when not authored                           -->
<!-- 3.  Use locations computed here, remove elsewhere                  -->
<!-- 4.  Recognize new, modern fill-in problems                         -->

<xsl:template match="exercise|&PROJECT-LIKE;|task" mode="exercise">
    <xsl:param name="division"/>

    <xsl:copy>
        <!-- Record one of five categories for customization, which    -->
        <!-- are not relevant for "example" (always inline), or "task" -->
        <!-- (always just a component of something larger).  WeBWorK   -->
        <!-- problems are interactive or static, inline or not, based  -->
        <!-- on publisher options.                                     -->
        <xsl:if test="not(self::task)">
            <xsl:attribute name="pi:exercise-customization">
                <xsl:choose>
                    <xsl:when test="&PROJECT-FILTER;">
                        <xsl:text>project</xsl:text>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$division"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
        </xsl:if>
        <!-- Determine and record types of interactivity -->
        <xsl:apply-templates select="." mode="exercise-interactive-attribute"/>
        <!-- catch remaining attributes -->
        <xsl:apply-templates select="@*" mode="exercise">
            <xsl:with-param name="division" select="$division"/>
        </xsl:apply-templates>
        <!-- Now the child elements -->
        <!-- NB: this would be a place to insert a "statement" for  -->
        <!-- "exercise", "example", "project" and "task", for which -->
        <!-- an author has not needed/elected to use one.           -->
        <xsl:apply-templates select="node()" mode="exercise">
            <xsl:with-param name="division" select="$division"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- These "interactivity types" are meant for Runestone-enabled  -->
<!-- interactive exercises and projects                           -->
<xsl:template match="*" mode="exercise-interactive-attribute">
    <xsl:attribute name="pi:exercise-interactive">
        <xsl:choose>
            <!-- This is defensive, so statement//var below does not -->
            <!-- match for WeBWorK.                                  -->
            <xsl:when test="self::task and ancestor::webwork">
                <xsl:text>webwork-task</xsl:text>
            </xsl:when>
            <!-- WeBWorK exercises always retain "webwork" child     -->
            <!-- through the pipeline.  Substitution from the        -->
            <!-- representations file happens in the representations -->
            <!-- pass, after exercise tagging.                       -->
            <xsl:when test="webwork">
                <xsl:text>webwork</xsl:text>
            </xsl:when>
            <xsl:when test="myopenmath">
                <xsl:text>myopenmath</xsl:text>
            </xsl:when>
            <xsl:when test="stack">
                <xsl:text>stack</xsl:text>
            </xsl:when>
            <!-- @runestone was once used to signify a Runestone exercise given    -->
            <!-- just by raw HTML.  The  @pi:exercise-interactive  value uses was, -->
            <!-- appropriately, "htmlhack".  We now replace it with an old-time,   -->
            <!-- static PreTeXt style (dumb) exercise.  Changing the value here    -->
            <!-- is a quick way to nullify activity in later passes.   See below   -->
            <!-- where the "repair" phase removes the  @runestone  attribute and   -->
            <!-- creates the (dumb) exercise, in order to understand more fully.   -->
            <!-- The commit with these changes might also help. (2025-11-04)       -->
            <xsl:when test="@runestone">
                <xsl:text>static</xsl:text>
            </xsl:when>
            <!-- true/false -->
            <xsl:when test="statement/@correct">
                <xsl:text>truefalse</xsl:text>
            </xsl:when>
            <!-- multiple choice -->
            <xsl:when test="statement and choices">
                <xsl:text>multiplechoice</xsl:text>
            </xsl:when>
            <!-- vertical is default/traditional -->
            <xsl:when test="statement and blocks and not(blocks/@layout = 'horizontal')">
                <xsl:text>parson</xsl:text>
            </xsl:when>
            <xsl:when test="statement and blocks and (blocks/@layout = 'horizontal')">
                <xsl:text>parson-horizontal</xsl:text>
            </xsl:when>
            <xsl:when test="statement and cardsort">
                <xsl:text>cardsort</xsl:text>
            </xsl:when>
            <xsl:when test="statement and matching">
                <xsl:text>matching</xsl:text>
            </xsl:when>
            <xsl:when test="statement and areas">
                <xsl:text>clickablearea</xsl:text>
            </xsl:when>
            <xsl:when test="select">
                <xsl:text>select</xsl:text>
            </xsl:when>
            <!-- noted WeBWorK earlier, so this is Runestone fillin -->
            <xsl:when test="statement//var">
                <xsl:text>fillin-basic</xsl:text>
            </xsl:when>
            <!-- new dynamic fillin goes here, perhaps:                     -->
            <!-- statement//fillin[(@*|node()) and not(@characters|@fill)]? -->
            <xsl:when test="statement//fillin and evaluation">
                <xsl:text>fillin</xsl:text>
            </xsl:when>
            <!-- only interactive programs make sense after a "statement" -->
            <xsl:when test="statement and program[(@interactive = 'codelens') or (@interactive = 'activecode')]">
                <xsl:text>coding</xsl:text>
            </xsl:when>
            <xsl:when test="dynamic and static">
                <xsl:text>dual</xsl:text>
            </xsl:when>
            <xsl:when test="statement and response">
                <xsl:text>shortanswer</xsl:text>
            </xsl:when>
            <!-- That's it, we are out of opportunities to be interactive -->

            <!-- A child that is a task indicates the exercise/project/task -->
            <!-- that is its parent is simply a container, rather than a    -->
            <!-- terminal task which would be structured with a "statement" -->
            <!-- in order to be interactive                                 -->
            <xsl:when test="task">
                <xsl:text>container</xsl:text>
            </xsl:when>
            <!-- Now we have what once would have been called a "traditional"     -->
            <!-- PreTeXt question, which is just "statement|hint|answer|solution" -->
            <!-- Or maybe just a bare statement that is not structured as such    -->
            <xsl:otherwise>
                <xsl:text>static</xsl:text>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:attribute>
</xsl:template>

<!-- Insert a default correct "test" for a FITB "exercise" or -->
<!-- PROJECT-LIKE when an "evaluate" has no explicit correct  -->
<!-- "test": the corresponding "fillin" has a static @answer, -->
<!-- so synthesize a "test" and let all downstream processing -->
<!-- follow a single code path.  Only @mode "string" and      -->
<!-- "number" are handled here; "math" uses separate logic.   -->
<xsl:template match="evaluation/evaluate[
    not(@all = 'yes') and
    not(test[@correct = 'yes']) and
    not(
        count(../../statement//fillin) > 1
        and
        ../evaluate[@all = 'yes']/test
    )
    ]" mode="exercise">
    <xsl:param name="division"/>

    <!-- Identify this evaluate's position among its siblings and its name, -->
    <!-- to locate the corresponding fillin by name first, position second. -->
    <xsl:variable name="eval-position" select="count(preceding-sibling::evaluate[not(@all='yes')]) + 1"/>
    <xsl:variable name="eval-name" select="@name"/>

    <!-- Navigate to the exercise/project/task parent of evaluation -->
    <xsl:variable name="exercise-parent" select="parent::evaluation/parent::*"/>

    <!-- Find the corresponding fillin (name-based match takes priority) -->
    <xsl:variable name="match-fillin">
        <xsl:choose>
            <xsl:when test="$eval-name != '' and
                            $exercise-parent/statement//fillin[@name = $eval-name]">
                <xsl:copy-of select="$exercise-parent/statement//fillin[@name = $eval-name]"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:copy-of select="($exercise-parent/statement//fillin)[$eval-position]"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="fillin" select="exsl:node-set($match-fillin)/fillin"/>

    <xsl:copy>
        <xsl:apply-templates select="@*" mode="exercise">
            <xsl:with-param name="division" select="$division"/>
        </xsl:apply-templates>
        <!-- Synthesize a correct test when the fillin has a static @answer -->
        <!-- and a recognized mode. Place it first so it takes precedence.  -->
        <xsl:if test="$fillin/@answer and not($fillin/@ansobj)">
            <xsl:choose>
                <xsl:when test="$fillin/@mode = 'number'">
                    <test correct="yes">
                        <numcmp use-answer="yes"/>
                    </test>
                </xsl:when>
                <xsl:when test="$fillin/@mode = 'string'">
                    <test correct="yes">
                        <strcmp use-answer="yes"/>
                    </test>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:message>PTX:WARNING: fillin in "<xsl:value-of
                        select="$exercise-parent/@pi:original-id"/>" has @answer but
                        @mode is missing or not recognized (expected 'number' or
                        'string'). No default correct test synthesized.</xsl:message>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:if>
        <!-- Copy existing children (non-correct tests, feedback, etc.) -->
        <xsl:apply-templates select="node()" mode="exercise">
            <xsl:with-param name="division" select="$division"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- ##################################################### -->
<!-- Dynamic Substitutions                                 -->
<!-- Cut out dynamic setup and evaluation for static mode. -->
<!-- NB: a new match in this mode must be reflected in the -->
<!-- $b-has-dynamic-markup presence test gating the pass.  -->
<!-- ##################################################### -->
<xsl:template match="setup[de-object|setupScript]" mode="dynamic-substitution">
    <xsl:if test="$exercise-style = 'dynamic'">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*" mode="dynamic-substitution"/>
        </xsl:copy>
    </xsl:if>
</xsl:template>

<xsl:template match="numcmp|strcmp|jscmp|mathcmp|logic[parent::test]" mode="dynamic-substitution">
    <xsl:if test="$exercise-style = 'dynamic'">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*" mode="dynamic-substitution"/>
        </xsl:copy>
    </xsl:if>
</xsl:template>

<xsl:template match="fillin[@ansobj]" mode="dynamic-substitution">
    <xsl:choose>
        <xsl:when test="($exercise-style = 'static') and not($b-extracting)">
            <!-- The substitutions file is keyed by @pi:assembly-id, which      -->
            <!-- extract-dynamic.xsl writes as the exercise_id.  This pass      -->
            <!-- runs after the @pi:assembly-id stamp, so the owner carries it. -->
            <xsl:variable name="owner" select="ancestor::statement/.."/>
            <xsl:variable name="parent-id">
                <xsl:apply-templates select="$owner" mode="assembly-id"/>
            </xsl:variable>
            <xsl:variable name="eval-subs" select="document($dynamic-substitutions-file,$original)"/>
            <xsl:variable name="object" select="@ansobj"/>
            <xsl:variable name="recorded" select="$eval-subs//dynamic-substitution[@id=$parent-id]/eval-subst[@obj=$object]"/>
            <!-- An answer is typeset as mathematics for the same modes that   -->
            <!-- "fillin-solution" in  pretext-runestone-static.xsl  wraps in  -->
            <!-- "m", so the two have to agree on which representation to use. -->
            <xsl:variable name="substitution">
                <xsl:apply-templates select="$recorded" mode="dynamic-representation">
                    <xsl:with-param name="b-latex" select="(@mode = 'math') or (@mode = 'number')"/>
                </xsl:apply-templates>
            </xsl:variable>
            <xsl:copy>
                <xsl:attribute name="answer">
                    <xsl:value-of select="$substitution"/>
                </xsl:attribute>
                <xsl:apply-templates select="@*|node()" mode="dynamic-substitution"/>
            </xsl:copy>
        </xsl:when>
        <xsl:otherwise>
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="dynamic-substitution"/>
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="eval[@obj]" mode="dynamic-substitution">
    <xsl:choose>
        <!-- static, for multiple conversions, but primarily LaTeX -->
        <xsl:when test="($exercise-style = 'static') and not($b-extracting)">
            <!-- The substitutions file is keyed by @pi:assembly-id, which      -->
            <!-- extract-dynamic.xsl writes as the exercise_id.  This pass      -->
            <!-- runs after the @pi:assembly-id stamp, so the owner carries it. -->
            <xsl:variable name="owner" select="(ancestor::statement|ancestor::solution|ancestor::evaluation)/.."/>
            <xsl:variable name="parent-id">
               <xsl:apply-templates select="$owner" mode="assembly-id"/>
            </xsl:variable>
            <xsl:variable name="eval-subs" select="document($dynamic-substitutions-file,$original)"/>
            <xsl:variable name="object" select="@obj"/>
            <xsl:variable name="recorded" select="$eval-subs//dynamic-substitution[@id=$parent-id]/eval-subst[@obj=$object]"/>
            <!-- The value lands wherever the "eval" stood, so the surrounding  -->
            <!-- markup decides which representation is wanted.  This is the    -->
            <!-- same test the HTML version makes when it wraps a reference in  -->
            <!-- "toTeX" in  pretext-runestone-fitb.xsl , and the two must      -->
            <!-- agree.  "md" is included because its single-line form holds    -->
            <!-- text directly, with no "mrow" for an ancestor step to find.    -->
            <xsl:variable name="substitution">
                <xsl:apply-templates select="$recorded" mode="dynamic-representation">
                    <xsl:with-param name="b-latex" select="boolean(ancestor::m|ancestor::md|ancestor::mrow)"/>
                </xsl:apply-templates>
            </xsl:variable>
            <xsl:value-of select="$substitution"/>
        </xsl:when>
        <!-- dynamic (aka HTML), needs static previews, server base64, etc, -->
        <!-- so just copy as-is with "webwork-reps" to signal and organize  -->
        <!-- to/for HTML conversion                                         -->
        <xsl:otherwise>
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="dynamic-substitution"/>
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- Pick a recorded substitution's representation.                       -->
<!--                                                                      -->
<!-- One generated object is routinely referenced more than once, and     -->
<!-- not always in the same way: as the answer of a "fillin" and again    -->
<!-- inside an "m" in the solution, say.  Those want different strings    -->
<!-- from the same object, and the substitutions file is keyed only by    -->
<!-- the object, so it cannot record which was meant.  Instead it records -->
<!-- both, and the caller, which does know its own context, chooses.      -->
<!--                                                                      -->
<!-- Substitution files generated before this distinction existed hold    -->
<!-- text and no children; that text is used for either request.          -->
<xsl:template match="eval-subst" mode="dynamic-representation">
    <xsl:param name="b-latex" select="false()"/>
    <!-- Each representation is independent: a request for one never looks -->
    <!-- at whether the other happens to be present.  A v1 substitution    -->
    <!-- carries both children together, or neither (the legacy case,      -->
    <!-- below), never just one, but the fallback is written per-child     -->
    <!-- regardless, since that is the contract, not an assumption about   -->
    <!-- which files exist.                                                -->
    <xsl:choose>
        <xsl:when test="$b-latex">
            <xsl:choose>
                <xsl:when test="latex">
                    <xsl:value-of select="latex"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:when>
        <xsl:otherwise>
            <xsl:choose>
                <xsl:when test="plain">
                    <xsl:value-of select="plain"/>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:value-of select="."/>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- An #eval child of test is implicit mathcmp. -->
<xsl:template match="test/eval[@obj]" mode="dynamic-substitution">
    <xsl:choose>
        <xsl:when test="$exercise-style = 'static'">
            <evaluation/>
        </xsl:when>
        <xsl:when test="$exercise-style = 'dynamic'">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="dynamic-substitution"/>
            </xsl:copy>
        </xsl:when>
    </xsl:choose>
</xsl:template>

<!-- ############### -->
<!-- Representations -->
<!-- ############### -->

<!-- Build multiple (two) representations of exercises that are produced  -->
<!-- in static (almost everything) and dynamic (HTML) versions.            -->
<!-- Generally these templates are parameterized by the $exercise-style   -->
<!-- variable/parameter.  We need the parameterization, since we do not   -->
<!-- want to make *multiple* copies of each exercise in the source, since -->
<!-- then duplicate items might confuse later templates e.g numbering).   -->
<!--                                                                      -->
<!-- A "static" version should be entirely in the style of a "regular"    -->
<!-- PreTeXt exercise, having a statement|hint|answer|solution structure. -->
<!-- Then it can be leveraged through all the infrastructure for things   -->
<!-- like solutions manuals and non-capable output formats.               -->
<!--                                                                      -->
<!-- A "dynamic" version is simply a duplicate of the author's source,    -->
<!-- which is handled by templates elsewhere, applied in the HTML         -->
<!-- conversion itself.                                                   -->

<!-- True/False        -->
<!-- Multiple Choice   -->
<!-- Parson problems   -->
<!-- Cardsort problems -->
<!-- Matching problems -->
<!-- Clickable Area    -->
<!-- ActiveCode        -->

<!-- The pattern grammar does not permit variable references, so the      -->
<!-- list of Runestone interactivity types is a literal, fenced string    -->
<!-- repeated for each element that can carry one.  Match exactly         -->
<!-- whenever  @pi:exercise-interactive  is one of the fenced values.     -->
<!-- NB: also consulted in Runestone manifest creation.                   -->

<xsl:template match="exercise[contains('|truefalse|multiplechoice|parson|parson-horizontal|cardsort|matching|clickablearea|fillin-basic|fillin|coding|shortanswer|', concat('|', @pi:exercise-interactive, '|'))]
                   | project[contains('|truefalse|multiplechoice|parson|parson-horizontal|cardsort|matching|clickablearea|fillin-basic|fillin|coding|shortanswer|', concat('|', @pi:exercise-interactive, '|'))]
                   | activity[contains('|truefalse|multiplechoice|parson|parson-horizontal|cardsort|matching|clickablearea|fillin-basic|fillin|coding|shortanswer|', concat('|', @pi:exercise-interactive, '|'))]
                   | exploration[contains('|truefalse|multiplechoice|parson|parson-horizontal|cardsort|matching|clickablearea|fillin-basic|fillin|coding|shortanswer|', concat('|', @pi:exercise-interactive, '|'))]
                   | investigation[contains('|truefalse|multiplechoice|parson|parson-horizontal|cardsort|matching|clickablearea|fillin-basic|fillin|coding|shortanswer|', concat('|', @pi:exercise-interactive, '|'))]
                   | task[contains('|truefalse|multiplechoice|parson|parson-horizontal|cardsort|matching|clickablearea|fillin-basic|fillin|coding|shortanswer|', concat('|', @pi:exercise-interactive, '|'))]" mode="representations">
    <!-- always preserve "exercise/project" container here, with attributes -->
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="representations"/>
        <xsl:choose>
            <!-- make a static version, in a PreTeXt   -->
            <!-- statement|hint|answer|solution style  -->
            <!-- for use naturally by most conversions -->
            <xsl:when test="$exercise-style = 'static'">
                <!-- overwrite classification as purely static now -->
                <xsl:attribute name="pi:exercise-interactive">
                    <xsl:text>static</xsl:text>
                </xsl:attribute>
                <xsl:apply-templates select="." mode="runestone-to-static"/>
            </xsl:when>
            <!-- duplicate for a dynamic version -->
            <xsl:when test="$exercise-style = 'dynamic'">
                <xsl:apply-templates select="node()" mode="representations"/>
            </xsl:when>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

<!-- A select question is only really capable on a Runestone server.  So when    -->
<!-- assembling for dynamic exercises, and not hosting on Runestone, go instead  -->
<!-- with the static version.  We have three scenarios, handled with variations. -->
<!-- Note that these matches have been carved out from the above massive match.  -->
<xsl:template match="exercise[(@pi:exercise-interactive = 'select')]
                   | project[(@pi:exercise-interactive = 'select')]
                   | activity[(@pi:exercise-interactive = 'select')]
                   | exploration[(@pi:exercise-interactive = 'select')]
                   | investigation[(@pi:exercise-interactive = 'select')]
                   | task[(@pi:exercise-interactive = 'select')]" mode="representations">
    <xsl:copy>
        <xsl:choose>
            <xsl:when test="($exercise-style = 'static')">
                <xsl:apply-templates select="@*" mode="representations"/>
                <!-- overwrite classification as purely static now -->
                <xsl:attribute name="pi:exercise-interactive">
                    <xsl:text>static</xsl:text>
                </xsl:attribute>
                <xsl:apply-templates select="." mode="runestone-to-static"/>
            </xsl:when>
            <!-- Use static version when making dynamic exercises *that are -->
            <!-- not hosted on Runestone*.  Classify as now being 'static'. -->
            <xsl:when test="($exercise-style = 'dynamic') and not($b-host-runestone)">
                <xsl:apply-templates select="@*" mode="representations"/>
                <!-- overwrite classification as purely static now -->
                <xsl:attribute name="pi:exercise-interactive">
                    <xsl:text>static</xsl:text>
                </xsl:attribute>
                <xsl:apply-templates select="." mode="runestone-to-static"/>
            </xsl:when>
            <!-- duplicate for a dynamic version -->
            <xsl:when test="$exercise-style = 'dynamic'">
                <xsl:apply-templates select="@*" mode="representations"/>
                <xsl:apply-templates select="node()" mode="representations"/>
            </xsl:when>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

<!-- dual copy (not copy-of) -->

<xsl:template match="exercise[(@pi:exercise-interactive = 'dual')]
                   | project[(@pi:exercise-interactive = 'dual')]
                   | activity[(@pi:exercise-interactive = 'dual')]
                   | exploration[(@pi:exercise-interactive = 'dual')]
                   | investigation[(@pi:exercise-interactive = 'dual')]
                   | task[(@pi:exercise-interactive = 'dual')]" mode="representations">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="representations"/>
        <xsl:choose>
            <xsl:when test="($exercise-style = 'static')">
                <!-- make a "static" classification, over-writing a "dual" -->
                <xsl:attribute name="pi:exercise-interactive">
                    <xsl:text>static</xsl:text>
                </xsl:attribute>
                <!-- copy overall title and idx, METADATA-FILTER feels too broad  -->
                <xsl:apply-templates select="title|idx" mode="representations"/>
                <xsl:apply-templates select="static/*" mode="representations"/>
            </xsl:when>
            <xsl:when test="($exercise-style = 'dynamic')">
                <!-- copy overall title and idx, METADATA-FILTER feels too broad  -->
                <xsl:apply-templates select="title|idx" mode="representations"/>
                <xsl:apply-templates select="dynamic/*" mode="representations"/>
            </xsl:when>
        </xsl:choose>
    </xsl:copy>
</xsl:template>


<!-- Static (non-interactive)                                 -->
<!-- @pi:exercise-interactive = 'static' needs no adjustments -->

<!-- Mine webwork-reps for relevant application -->

<!-- WeBWorK exercises retain a "webwork" child element through the    -->
<!-- pipeline.  Here in the representations pass, we look up the       -->
<!-- corresponding "webwork-reps" from the representations file using  -->
<!-- the parent exercise's @pi:assembly-id, and substitute it in place -->
<!-- of the "webwork" element.  We then split three ways, for PGML,    -->
<!-- static, and dynamic (HTML) employment, via modal templates.       -->
<!-- During extraction, the "webwork" child is left intact.            -->
<!-- NB: including "task" though this may not be supported.            -->
<xsl:template match="exercise[(@pi:exercise-interactive = 'webwork')]
                   | project[(@pi:exercise-interactive = 'webwork')]
                   | activity[(@pi:exercise-interactive = 'webwork')]
                   | exploration[(@pi:exercise-interactive = 'webwork')]
                   | investigation[(@pi:exercise-interactive = 'webwork')]" mode="representations">
    <xsl:choose>
        <!-- During extraction, pass through the exercise with its    -->
        <!-- "webwork" child intact for the extraction stylesheet     -->
        <!-- to process the authored content.                         -->
        <xsl:when test="$b-extracting">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="representations"/>
            </xsl:copy>
        </xsl:when>
        <xsl:otherwise>
            <!-- Load the per-exercise representation file for this    -->
            <!-- exercise, identified by its @pi:assembly-id.          -->
            <xsl:variable name="webwork-rep-uri"
                select="concat($webwork-representations-dir, @pi:assembly-id, '.xml')"/>
            <xsl:variable name="the-webwork-rep"
                select="document($webwork-rep-uri, $original)/webwork-reps"/>
            <xsl:choose>
                <!-- An empty string for $webwork-representations-dir, and       -->
                <!-- the "document()" still succeeds (returns the source file?). -->
                <!-- But this is hopeless. So just totally bail out repeatedly   -->
                <!-- and leave the containing "exercise" hollow.                 -->
                <xsl:when test="$webwork-representations-dir = ''">
                    <xsl:copy>
                        <xsl:apply-templates select="node()|@*" mode="representations"/>
                    </xsl:copy>
                    <xsl:message>PTX:ERROR:   no WeBWorK representations directory configured in the publication file; WeBWorK exercise with @pi:assembly-id "<xsl:value-of select="@pi:assembly-id"/>" will be empty.</xsl:message>
                </xsl:when>
                <!-- This should only fail if the file is missing or stale.  Repeatedly. -->
                <xsl:when test="not($the-webwork-rep)">
                    <xsl:copy>
                        <xsl:apply-templates select="node()|@*" mode="representations"/>
                    </xsl:copy>
                    <xsl:message>PTX:ERROR:   could not load WeBWorK representation file "<xsl:value-of select="$webwork-rep-uri"/>" for @pi:assembly-id "<xsl:value-of select="@pi:assembly-id"/>"; re-generate the WeBWorK representations.  A "webwork-representations.xml" in that directory indicates old-format files that need replacing.</xsl:message>
                </xsl:when>
                <xsl:otherwise>
                    <!-- The representation file may record a server failure   -->
                    <!-- from when the WeBWorK representations were generated. -->
                    <!-- In that case the file holds a placeholder "faux       -->
                    <!-- problem" (statement only) so assembly still proceeds, -->
                    <!-- but warn the author so the failure does not go        -->
                    <!-- unnoticed in later builds.                            -->
                    <xsl:if test="$the-webwork-rep/static/@failure">
                        <xsl:message>PTX:WARNING:   the WeBWorK problem with @pi:assembly-id "<xsl:value-of select="@pi:assembly-id"/>" has a stored "<xsl:value-of select="$the-webwork-rep/static/@failure"/>" failure recorded when the WeBWorK representations were last generated.  A placeholder is being rendered in its place.  Re-generate the WeBWorK representations (and watch for errors) to retry.</xsl:message>
                    </xsl:if>
                    <!-- Build a temporary exercise with "webwork-reps" from the  -->
                    <!-- representations file substituted in place of "webwork".   -->
                    <!-- This preserves the parent-child relationship that         -->
                    <!-- downstream templates rely on (e.g. "../introduction" in   -->
                    <!-- webwork-rep-to-static navigates from webwork-reps up to   -->
                    <!-- the exercise).                                            -->
                    <xsl:variable name="exercise-with-reps-rtf">
                        <xsl:copy>
                            <xsl:copy-of select="@*"/>
                            <xsl:for-each select="node()">
                                <xsl:choose>
                                    <xsl:when test="self::webwork">
                                        <xsl:copy-of select="$the-webwork-rep"/>
                                    </xsl:when>
                                    <xsl:otherwise>
                                        <xsl:copy-of select="."/>
                                    </xsl:otherwise>
                                </xsl:choose>
                            </xsl:for-each>
                        </xsl:copy>
                    </xsl:variable>
                    <xsl:variable name="exercise-with-reps" select="exsl:node-set($exercise-with-reps-rtf)/*"/>

                    <xsl:choose>
                        <!-- destined for creating problem sets, really just need PG code -->
                        <xsl:when test="$exercise-style = 'pg-problems'">
                            <xsl:copy>
                                <xsl:apply-templates select="@*" mode="webwork-rep-to-pg"/>
                                <xsl:apply-templates select="title" mode="webwork-rep-to-pg"/>
                                <xsl:apply-templates select="$exercise-with-reps/webwork-reps" mode="webwork-rep-to-pg"/>
                            </xsl:copy>
                        </xsl:when>
                        <!-- static, for multiple conversions, but primarily LaTeX -->
                        <xsl:when test="$exercise-style = 'static'">
                            <xsl:copy>
                                <xsl:apply-templates select="@*" mode="webwork-rep-to-static"/>
                                <xsl:attribute name="pi:exercise-interactive">
                                    <xsl:text>static</xsl:text>
                                </xsl:attribute>
                                <xsl:apply-templates select="$exercise-with-reps/node()" mode="webwork-rep-to-static"/>
                            </xsl:copy>
                        </xsl:when>
                        <!-- dynamic (aka HTML), needs static previews, server base64, etc -->
                        <xsl:otherwise>
                            <xsl:copy>
                                <xsl:apply-templates select="@*" mode="webwork-rep-to-html"/>
                                <xsl:apply-templates select="$exercise-with-reps/node()" mode="webwork-rep-to-html"/>
                            </xsl:copy>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- Edit a "webwork-reps" from the server into just PG material -->
<xsl:template match="node()|@*" mode="webwork-rep-to-pg">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="webwork-rep-to-pg"/>
    </xsl:copy>
</xsl:template>

<!-- Promote "pg" specific information to "webwork-reps" -->
<xsl:template match="webwork-reps" mode="webwork-rep-to-pg">
    <xsl:copy>
        <!-- copy existing attributes -->
        <xsl:apply-templates select="@*" mode="webwork-rep-to-pg"/>
        <!-- promote "pg" attributes (@source, @copied-from) -->
        <xsl:apply-templates select="pg/@*" mode="webwork-rep-to-pg"/>
        <!-- attributes done, recurse into child *elements*  -->
        <!-- no node() here, so drops interstial whitespace  -->
        <!-- that accumulates into the textual PG code, even -->
        <!-- if it does get sanitized in its use/application -->
        <xsl:apply-templates select="*" mode="webwork-rep-to-pg"/>
    </xsl:copy>
</xsl:template>

<!-- Attributes preserved, drop "pg" element, duplicate the guts -->
<!-- which should just be the actual PG version of the problem   -->
<xsl:template match="webwork-reps/pg" mode="webwork-rep-to-pg">
    <xsl:apply-templates select="node()" mode="webwork-rep-to-pg"/>
</xsl:template>

<!-- Drop "webwork-reps" children we don't need for problem sets -->
<xsl:template match="webwork-reps/static" mode="webwork-rep-to-pg"/>
<xsl:template match="webwork-reps/server-data" mode="webwork-rep-to-pg"/>


<!-- Static from webwork-reps as a generic exercise  -->
<!-- Edit a "webwork-reps" from the server into guts -->
<!-- of a static exercise or PROJECT-LIKE            -->

<!-- Kill author's lead-in/lead-out material on sight, we recreate -->
<!-- their children with "node()" in main reorganization template  -->
<xsl:template match="introduction[following-sibling::webwork-reps]" mode="webwork-rep-to-static"/>
<xsl:template match="conclusion[preceding-sibling::webwork-reps]" mode="webwork-rep-to-static"/>

<!-- Meld an author's "introduction" and "conclusion" into          -->
<!-- (a) the similarly-named items of a task-structured object      -->
<!-- (b) the statement of an object with a simpler structure        -->
<!-- We recurse into (many, principal) selected components of       -->
<!-- webwork-reps/static which effectively ignores  webwork-reps/pg -->
<!-- and  webwork-reps/server  so these pieces never make it into   -->
<!-- the assembled source.                                          -->
<!--                                                                -->
<!-- NB: we lose some attributes attached above to                  -->
<!-- "introduction" and "conclusion" (not "exercise"), BUT          -->
<!--   (i) we cannot point *into* a WW problem (no targets)         -->
<!--   (ii) we do not have numbered items (eg Figure)               -->
<!--   (iii) by removing them, we just disrupt any                  -->
<!--         sequences, and uniqueness is preserved                 -->
<!--   (iv) we could figure out which ones to copy where, if needed -->
<!-- NB: a possible refactor here:                                  -->
<!-- (i)  kill all the children of "webwork-reps", barring "static" -->
<!--      (currently they are just ignored)                         -->
<!-- (ii) rerwrite this template to have "static" as the context.   -->
<!--      This would mean adjust some paths to go one more step up  -->
<!--      to find things like "introduction".                       -->
<!-- Consequence: when leveraged for HTML previews this rearrangment-->
<!-- will be a big change.  Not clear if it is a desirable change.  -->
<xsl:template match="webwork-reps" mode="webwork-rep-to-static">
    <xsl:choose>
        <!-- a WW "staged" exercise, may have an top-level introduction and -->
        <!-- conclusion already, and does not have a top-level statement    -->
        <xsl:when test="static/task">
            <xsl:if test=".//introduction|static/introduction">
                <introduction>
                    <xsl:apply-templates select="../introduction/node()" mode="webwork-rep-to-static"/>
                    <xsl:apply-templates select="static/introduction/node()" mode="webwork-rep-to-static"/>
                </introduction>
            </xsl:if>
            <xsl:apply-templates select="static/task" mode="webwork-rep-to-static"/>
            <xsl:if test="../conclusion|static/conclusion">
                <conclusion>
                    <xsl:apply-templates select="../conclusion/node()" mode="webwork-rep-to-static"/>
                    <xsl:apply-templates select="static/conclusion/node()" mode="webwork-rep-to-static"/>
                </conclusion>
            </xsl:if>
        </xsl:when>
        <xsl:otherwise>
            <statement>
                <xsl:apply-templates select="../introduction/node()" mode="webwork-rep-to-static"/>
                <xsl:apply-templates select="static/statement/node()" mode="webwork-rep-to-static"/>
                <xsl:apply-templates select="../conclusion/node()" mode="webwork-rep-to-static"/>
            </statement>
            <xsl:apply-templates select="static/hint" mode="webwork-rep-to-static"/>
            <xsl:apply-templates select="static/answer" mode="webwork-rep-to-static"/>
            <xsl:apply-templates select="static/solution" mode="webwork-rep-to-static"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- Good time to clean-up what came back from a WW server.     -->
<!-- As part of the "webwork-rep-to-static" mode, we can be     -->
<!-- sure that only returns from the server are being adjusted. -->

<!-- From the code comment when this was done with Python: "p with -->
<!-- only a single fillin, not counting those inside an li without -->
<!-- preceding siblings"                                           -->
<!-- We likewise prune the "p" that only have a var with           -->
<!-- @form="essay. These come from WeBWorK essay questions         -->
<!-- starting with v2.19                                           -->
<xsl:template match="p" mode="webwork-rep-to-static">
    <!-- Substantially faster to have a simple match and then selectively -->
    <!-- filter matched elements. Start with the tests that are cheapest  -->
    <!-- and hope short-circuit evaluation avoids expensive ones.         -->
    <xsl:variable name="prune">
        <xsl:if test="(count(fillin)=1 or count(var[@form='essay'])=1) and
                      count(*)=1 and 
                      not(normalize-space(text())) and
                      (not(parent::li) or preceding-sibling::*)">
            <xsl:value-of select="true()"/>
        </xsl:if>
    </xsl:variable>
    <xsl:if test="$prune != 'true'">
        <xsl:copy>
            <xsl:apply-templates select="node()|@*" mode="webwork-rep-to-static"/>
        </xsl:copy>
    </xsl:if>
</xsl:template>


<!-- Some answer forms return a default/initial choice that is -->
<!-- simply a question-mark.  We scrub them here, with care.   -->
<xsl:template match="statement//var[@form = 'popup']/li[(p[. = '?']) or (normalize-space(.) = '?')]" mode="webwork-rep-to-static"/>
<xsl:template match="statement//ul[@form = 'popup']/li[(p[. = '?']) or (normalize-space(.) = '?')]" mode="webwork-rep-to-static"/>
<!-- This may only be needed as support for older servers' generated PreTeXt. -->
<xsl:template match="statement//var[@form = 'checkboxes']/li[(p[. = '?']) or (normalize-space(.) = '?')]" mode="webwork-rep-to-static"/>

<!-- @form comes back from the server as a result of authored      -->
<!-- "answer forms" and should be rendered as lists in static      -->
<!-- representations.                                              -->
<!-- NB: this does not preclude the match below (scrubbing default -->
<!-- items) from functioning.                                      -->
<xsl:template match="statement//ul[@form]|statement//var[@form]" mode="webwork-rep-to-static">
    <ul>
        <!-- duplicate attributes, but for @form -->
        <xsl:apply-templates select="@*[not(name() = 'form')]" mode="repair"/>
        <!-- internal attribute to indicate WW origins -->
        <xsl:attribute name="pi:ww-form">
            <xsl:value-of select="@form"/>
        </xsl:attribute>
        <!-- add a marker for an unordered list -->
        <xsl:attribute name="marker">
            <xsl:choose>
                <xsl:when test="@form = 'popup'">
                    <xsl:text>square</xsl:text>
                </xsl:when>
                <xsl:when test="@form = 'buttons'">
                    <xsl:text>circle</xsl:text>
                </xsl:when>
                <xsl:when test="@form = 'checkboxes'">
                    <xsl:text>square</xsl:text>
                </xsl:when>
            </xsl:choose>
        </xsl:attribute>
        <xsl:apply-templates select="node()" mode="webwork-rep-to-static"/>
    </ul>
</xsl:template>

<xsl:template match="statement//ol[@form]" mode="webwork-rep-to-static">
    <ol>
        <!-- duplicate attributes, but for @form -->
        <xsl:apply-templates select="@*[not(name() = 'form')]" mode="repair"/>
        <!-- internal attribute to indicate WW origins -->
        <xsl:attribute name="pi:ww-form">
            <xsl:value-of select="@form"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()" mode="webwork-rep-to-static"/>
    </ol>
</xsl:template>

<xsl:template match="statement//dl[@form]" mode="webwork-rep-to-static">
    <dl>
        <!-- duplicate attributes, but for @form -->
        <xsl:apply-templates select="@*[not(name() = 'form')]" mode="repair"/>
        <!-- internal attribute to indicate WW origins -->
        <xsl:attribute name="pi:ww-form">
            <xsl:value-of select="@form"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()" mode="webwork-rep-to-static"/>
    </dl>
</xsl:template>

<!-- Default xeroxing template -->
<xsl:template match="node()|@*" mode="webwork-rep-to-static">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="webwork-rep-to-static"/>
    </xsl:copy>
</xsl:template>

<!-- Edit a "webwork-reps" from the server into just HTML material -->

<!-- We have a static version that gets employed in the HTML conversion -->
<!-- as a "preview" before a reader hits an "Activate" button.  We are  -->
<!-- leveraging the clean-up of static versions here.                   -->
<!-- NB: for historical reasons, and so as to get a clean refactor, we  -->
<!-- apply this modal template to "static" which is a level lower down  -->
<!-- than its complete implementation, which starts at "webwork-reps".  -->
<!-- This means that there is no rearrangement of the overall           -->
<!-- "introduction" into the "statement".  But see the comments about a -->
<!-- potential refactor of the "webwork-rep-to-static" templates.       -->
<xsl:template match="static" mode="webwork-rep-to-html">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="webwork-rep-to-static"/>
    </xsl:copy>
</xsl:template>

<!-- Default xeroxing template -->
<xsl:template match="node()|@*" mode="webwork-rep-to-html">
    <xsl:copy>
        <xsl:apply-templates select="node()|@*" mode="webwork-rep-to-html"/>
    </xsl:copy>
</xsl:template>

<!-- MyOpenMath (MOM) to static -->

<!-- Static versions come from a MOM server, and have been stored  -->
<!-- as a "generated" component of a project.  We meld with a      -->
<!-- PreTeXt introduction and conclusion, into a "regular" PreTeXt -->
<!-- format, for any conversion to a static format to use.         -->
<!-- NB: very similar to STACK template below                      -->
<xsl:template match="exercise[(@pi:exercise-interactive = 'myopenmath')]
                   | project[(@pi:exercise-interactive = 'myopenmath')]
                   | activity[(@pi:exercise-interactive = 'myopenmath')]
                   | exploration[(@pi:exercise-interactive = 'myopenmath')]
                   | investigation[(@pi:exercise-interactive = 'myopenmath')]" mode="representations">
    <!-- duplicate the exercise/project -->
    <xsl:copy>
        <!-- and preserve attributes on the exercise/project -->
        <xsl:apply-templates select="@*" mode="representations"/>
        <!-- Now bifurcate on static/dynamic.  PG problem creation should not fall in here. -->
        <xsl:choose>
            <xsl:when test="($exercise-style = 'static') and not($b-extracting)">
                <!-- overwrite classification as purely static now -->
                <xsl:attribute name="pi:exercise-interactive">
                    <xsl:text>static</xsl:text>
                </xsl:attribute>
                <!-- locate the static representation in a file, generated independently -->
                <!-- NB: this filename is relative to the author's source                -->
                <xsl:variable name="filename">
                    <xsl:value-of select="$generated-directory-source"/>
                    <xsl:text>problems/mom-</xsl:text>
                    <xsl:value-of select="myopenmath/@problem"/>
                    <xsl:text>.xml</xsl:text>
                </xsl:variable>
                <!-- "myopenmath" child guaranteed by @pi:exercise-interactive value -->
                <xsl:variable name="mom-static-rep" select="document($filename, $original)/myopenmath"/>
                <!-- duplicate metadata first -->
                <xsl:apply-templates select="title|idx" mode="representations"/>
                <!-- Meld PreTeXt introduction, conclusion with MOM statement. We could -->
                <!-- duplicate MOM/statement attributes here, if there were any.        -->
                <statement>
                    <xsl:apply-templates select="introduction/node()" mode="representations"/>
                    <xsl:apply-templates select="$mom-static-rep/statement/node()" mode="representations"/>
                    <xsl:apply-templates select="conclusion/node()" mode="representations"/>
                </statement>
                <!-- these might not all be present, ever, but just to be safe -->
                <xsl:apply-templates select="$mom-static-rep/hint" mode="representations"/>
                <xsl:apply-templates select="$mom-static-rep/answer" mode="representations"/>
                <xsl:apply-templates select="$mom-static-rep/solution" mode="representations"/>
                <!-- NB: the "myopenmath" element has been ignored is now gone -->
            </xsl:when>
            <xsl:otherwise>
                <!-- Duplicate authored content for the non-static conversions   -->
                <!-- and let the conversions handle dynamic content.  Also, when -->
                <!-- extracting MOM we need the authored source unmolested.      -->
                <xsl:apply-templates select="node()" mode="representations"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

<!-- STACK questions to static -->

<!-- Static versions crom a STACK server, and have been stored     -->
<!-- as a "generated" component of a project.  We meld with a      -->
<!-- PreTeXt introduction and conclusion, into a "regular" PreTeXt -->
<!-- format, for any conversion to a static format to use.         -->
<!-- NB: very similar to MyOpenMath template above                 -->
<xsl:template match="exercise[(@pi:exercise-interactive = 'stack')]
                   | project[(@pi:exercise-interactive = 'stack')]
                   | activity[(@pi:exercise-interactive = 'stack')]
                   | exploration[(@pi:exercise-interactive = 'stack')]
                   | investigation[(@pi:exercise-interactive = 'stack')]" mode="representations">
    <!-- duplicate the exercise/project -->
    <xsl:copy>
        <!-- and preserve attributes on the exercise/project -->
        <xsl:apply-templates select="@*" mode="representations"/>
        <!-- Now bifurcate on static/dynamic.  PG problem creation should not fall in here. -->
        <xsl:choose>
            <xsl:when test="($exercise-style = 'static') and not($b-extracting)">
                <!-- overwrite classification as purely static now -->
                <xsl:attribute name="pi:exercise-interactive">
                    <xsl:text>static</xsl:text>
                </xsl:attribute>
                <!-- locate the static representation in a file, generated independently -->
                <!-- NB: this filename is relative to the author's source in "generated" -->
                <xsl:variable name="filename">
                    <xsl:value-of select="$generated-directory-source"/>
                    <xsl:text>stack/</xsl:text>
                    <xsl:apply-templates select="stack" mode="assembly-id"/>
                    <xsl:text>.ptx</xsl:text>
                </xsl:variable>
                <xsl:variable name="stack-static-rep" select="document($filename, $original)/stack-static"/>
                <!-- duplicate metadata first -->
                <xsl:apply-templates select="title|idx" mode="representations"/>
                <!-- Meld PreTeXt introduction, conclusion with STACK statement. We      -->
                <!-- could duplicate stack/statement attributes here, if there were any. -->
                <statement>
                    <xsl:apply-templates select="introduction/node()" mode="representations"/>
                    <xsl:apply-templates select="$stack-static-rep/statement/node()" mode="representations"/>
                    <xsl:apply-templates select="conclusion/node()" mode="representations"/>
                </statement>
                <!-- these might not all be present, ever, but just to be safe -->
                <xsl:apply-templates select="$stack-static-rep/hint" mode="representations"/>
                <xsl:apply-templates select="$stack-static-rep/answer" mode="representations"/>
                <xsl:apply-templates select="$stack-static-rep/solution" mode="representations"/>
                <!-- NB: the "stack" element has been ignored is now gone -->
            </xsl:when>
            <xsl:otherwise>
                <!-- Duplicate authored content for the non-static conversions   -->
                <!-- and let the conversions handle dynamic content.  Also, when -->
                <!-- extracting STACK we need the authored source unmolested.    -->
                <xsl:apply-templates select="node()" mode="representations"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:copy>
</xsl:template>

<!-- A "program" is "made" interactive by setting a value of the        -->
<!-- @interactive attribute.  For static (incapable) formats, code      -->
<!-- simply does not even consult the @interactive attribute.  By       -->
<!-- explicitly dropping it here, we insure that even if we are         -->
<!-- making HTML, but with static contents (e.g. EPUB), a "program"     -->
<!-- will not get routed into templates that make Runestone components. -->
<xsl:template match="program/@interactive" mode="representations">
    <xsl:choose>
        <!-- drop  @interactive  for static formats -->
        <xsl:when test="$exercise-style = 'static'"/>
        <xsl:otherwise>
            <xsl:copy/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="datafile|query" mode="representations">
    <xsl:choose>
        <!-- make a static version, in a PreTeXt style -->
        <!-- for use naturally by most conversions     -->
        <xsl:when test="$exercise-style = 'static'">
            <xsl:apply-templates select="." mode="runestone-to-static"/>
        </xsl:when>
        <!-- duplicate for a dynamic version -->
        <xsl:when test="$exercise-style = 'dynamic'">
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="representations"/>
            </xsl:copy>
        </xsl:when>
    </xsl:choose>
</xsl:template>

<!-- Static versions of Audio, Video, Interactives -->

<!-- Form a PreTeXt side-by-side with an image, a QR code and links -->

<xsl:template match="audio|video|interactive[not(static)]" mode="representations">
    <xsl:choose>
        <xsl:when test="($exercise-style = 'static') and not($b-extracting)">
            <!-- Read pre-computed URLs from sidecar file -->
            <!-- But not if doing an extraction           -->
            <xsl:variable name="url-file"
                select="document(concat($generated-directory-source, 'qrcode/', @pi:assembly-id, '-url.xml'), $original)"/>
            <!-- panel widths are experimental -->
            <sidebyside margins="7.5% 7.5%" widths="47% 21%" valign="top" halign="center">
                <!-- copy over @xml:id, which may be in use by -->
                <!-- page-breaking mechanism for LaTeX output  -->
                <xsl:copy-of select="@xml:id"/>
                <!-- A @label could mask an authored @xml:id           -->
                <!-- Note: this may be manufactured by an earlier pass -->
                <xsl:copy-of select="@label"/>
                <xsl:choose>
                    <!-- @preview present, so author provides a static image  -->
                    <!--                                                      -->
                    <!-- "video" is exceptional, we allow for a generic image -->
                    <xsl:when test="self::video and (@preview = 'generic')">
                        <image>
                            <xsl:attribute name="pi:generated">
                                <xsl:text>play-button/play-button.png</xsl:text>
                            </xsl:attribute>
                        </image>
                    </xsl:when>
                    <!--  -->
                    <xsl:when test="@preview">
                        <image>
                            <xsl:attribute name="source">
                                <xsl:value-of select="@preview"/>
                            </xsl:attribute>
                        </image>
                    </xsl:when>
                    <!-- semi-automatic images vary by format     -->
                    <!--                                          -->
                    <!-- interactive: screenshots with playwright -->
                    <!-- video: we scrape YouTube, only           -->
                    <!--        YouTube playlist gets generic     -->
                    <!-- audio: immature                          -->
                    <xsl:when test="self::interactive">
                        <image>
                            <xsl:attribute name="pi:generated">
                                <xsl:text>preview/</xsl:text>
                                <xsl:apply-templates select="." mode="assembly-id"/>
                                <xsl:text>-preview.png</xsl:text>
                            </xsl:attribute>
                        </image>
                    </xsl:when>
                    <!--  -->
                    <xsl:when test="self::video and @youtube">
                        <image>
                            <xsl:attribute name="pi:generated">
                                <xsl:text>youtube/</xsl:text>
                                <xsl:apply-templates select="." mode="assembly-id"/>
                                <xsl:text>.jpg</xsl:text>
                            </xsl:attribute>
                        </image>
                    </xsl:when>
                    <!--  -->
                    <xsl:when test="self::video and @youtubeplaylist">
                        <image>
                            <xsl:attribute name="pi:generated">
                                <xsl:text>play-button/play-button.png</xsl:text>
                            </xsl:attribute>
                        </image>
                    </xsl:when>
                    <!--  -->
                    <xsl:when test="self::audio">
                        <p>No static image provided via <c>@preview</c> attribute</p>
                    </xsl:when>
                    <!--  -->
                    <xsl:otherwise>
                        <p>BUG: PREVIEW NOT HANDLED</p>
                    </xsl:otherwise>
                </xsl:choose>
                <stack>
                    <!-- 2023-02-07: wrapping in a URL failed    -->
                    <!-- for a LaTeX build of the sample article -->
                    <image>
                        <xsl:attribute name="pi:generated">
                            <xsl:text>qrcode/</xsl:text>
                            <xsl:apply-templates select="." mode="assembly-id"/>
                            <xsl:text>.png</xsl:text>
                        </xsl:attribute>
                    </image>
                    <!-- We kill the automatic footnotes, a debatable decision -->
                    <!--  -->
                    <xsl:variable name="standalone-url">
                        <xsl:value-of select="$url-file/pi:qrcode-urls/pi:standalone-url"/>
                    </xsl:variable>
                    <xsl:if test="not($standalone-url = '')">
                        <p pi:indent="no">
                            <url href="{$standalone-url}" visual="">
                                <pi:localize string-id="standalone"/>
                            </url>
                        </p>
                    </xsl:if>
                    <!--  -->
                    <xsl:variable name="context-url">
                        <xsl:value-of select="$url-file/pi:qrcode-urls/pi:context-url"/>
                    </xsl:variable>
                    <xsl:if test="not($context-url = '')">
                        <p pi:indent="no">
                            <url href="{$context-url}" visual="">
                                <pi:localize string-id="incontext"/>
                            </url>
                        </p>
                    </xsl:if>
                    <!--  -->
                    <xsl:variable name="embed-iframe-url">
                        <xsl:value-of select="$url-file/pi:qrcode-urls/pi:embed-iframe-url"/>
                    </xsl:variable>
                    <xsl:if test="not($embed-iframe-url = '')">
                        <p pi:indent="no">
                            <!-- Kill the automatic footnote -->
                            <url href="{$embed-iframe-url}" visual="">
                                <pi:localize string-id="embed"/>
                            </url>
                        </p>
                    </xsl:if>
                    <!--  -->
                </stack>
            </sidebyside>
        </xsl:when>
        <xsl:otherwise>
            <!-- duplicate authored content for the non-static conversions and extractions-->
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="representations"/>
            </xsl:copy>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- If a "static" is given, just copy it's children -->
<xsl:template match="interactive[static]" mode="representations">
    <xsl:choose>
        <!-- duplicate the contents of an alternative "static" element -->
        <xsl:when test="$exercise-style = 'static'">
            <xsl:apply-templates select="static" mode="representations"/>
        </xsl:when>
        <xsl:when test="($exercise-style = 'dynamic') or ($exercise-style = 'pg-problems')">
            <!-- duplicate authored content for the non-static conversions -->
            <xsl:copy>
                <xsl:apply-templates select="node()|@*" mode="representations"/>
            </xsl:copy>
        </xsl:when>
    </xsl:choose>
</xsl:template>

<!-- ############# -->
<!-- Source Repair -->
<!-- ############# -->

<!-- We unilaterally make various changes to an author's source -->
<!-- so a conversion (every conversion?) can assume more        -->
<!-- accurately that the source has certain characteristics.    -->

<!-- 2019-04-02  "mathbook" replaced by "pretext" -->
<xsl:template match="/mathbook" mode="repair">
    <pretext>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </pretext>
</xsl:template>

<!-- 2017-02-05  "hyphen" replaced by a bare hyphen.  Added 2026-06-25. -->
<xsl:template match="hyphen" mode="repair">
    <xsl:text>-</xsl:text>
</xsl:template>

<!-- 2017-07-18  "@tex_size" was renamed "@tex-size" (cosmetic), which in  -->
<!-- turn became ignored (2017-11-09) in favor of a percentage "@width".   -->
<!-- The legacy attribute now does nothing, so drop it.  Added 2026-06-25. -->
<xsl:template match="@tex_size" mode="repair"/>

<!-- 2017-07-25  "autoname" on "xref" deprecated in favor of "text".  -->
<!-- Silently repair the legacy attribute to its modern functional    -->
<!-- equivalent.  Every other attribute, and any content, is left to  -->
<!-- the identity template, and so is preserved.  Added 2026-06-25.   -->
<xsl:template match="xref/@autoname" mode="repair">
    <xsl:attribute name="text">
        <xsl:choose>
            <xsl:when test=". = 'yes'">
                <xsl:text>type-global</xsl:text>
            </xsl:when>
            <xsl:when test=". = 'no'">
                <xsl:text>global</xsl:text>
            </xsl:when>
            <xsl:when test=". = 'title'">
                <xsl:text>title</xsl:text>
            </xsl:when>
        </xsl:choose>
    </xsl:attribute>
</xsl:template>

<!-- 2017-12-07  "@latexsep" on "c"/"cd" is ignored; drop it.  Added 2026-06-25. -->
<xsl:template match="c/@latexsep|cd/@latexsep" mode="repair"/>

<!-- 2018-02-05  "booktitle" replaced by "pubtitle".  Added 2026-06-25. -->
<xsl:template match="booktitle" mode="repair">
    <pubtitle>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </pubtitle>
</xsl:template>

<!-- 2019-02-20  "rename/@lang" replaced by "@xml:lang".  Added 2026-06-25. -->
<xsl:template match="rename/@lang" mode="repair">
    <xsl:attribute name="xml:lang">
        <xsl:value-of select="."/>
    </xsl:attribute>
</xsl:template>

<!-- 2021-07-02 wrap notation/usage in "m" if not present -->
<xsl:template match="notation/usage[not(m)]" mode="repair">
    <!-- duplicate "usage" w/ attributes, insert "m" as repair -->
    <usage>
        <xsl:apply-templates select="@*" mode="repair"/>
        <m>
            <xsl:apply-templates select="node()|@*" mode="repair"/>
        </m>
    </usage>
</xsl:template>

<!-- 2021-10-04 "glossary" was finalized, so old-style preserved -->

<!-- glossary introductions become headnotes -->
<xsl:template match="glossary/introduction" mode="repair">
    <headnote>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </headnote>
</xsl:template>

<!-- "terms" only ever had "defined-term" as children    -->
<!-- and is now obsolete, so dropped as excess structure -->
<xsl:template match="glossary/terms" mode="repair">
    <xsl:apply-templates select="defined-term" mode="repair"/>
</xsl:template>

<!-- "defined-term" was structured, so we just select elements -->
<xsl:template match="glossary/terms/defined-term" mode="repair">
    <gi>
        <xsl:apply-templates select="*|@*" mode="repair"/>
    </gi>
</xsl:template>

<!-- no more "conclusion", so drop it here; deprecation will warn -->
<xsl:template match="glossary/conclusion" mode="repair"/>

<!-- 2022-04-22 replace Python Tutor with Runestone CodeLens -->
<xsl:template match="program/@interactive" mode="repair">
    <xsl:choose>
        <xsl:when test=". = 'pythontutor'">
            <xsl:attribute name="interactive">
                <xsl:text>codelens</xsl:text>
            </xsl:attribute>
        </xsl:when>
        <xsl:otherwise>
            <xsl:copy/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- 2022-04-25 @label deprecated, slated for renewal in starring  -->
<!-- role. Lists with markers (not description lists) -->
<xsl:template match="ol/@label|ul/@label" mode="repair">
    <xsl:attribute name="marker">
        <xsl:value-of select="."/>
    </xsl:attribute>
</xsl:template>

<!-- 2022-04-24 An exception, label on video tracks mimicing HTML -->
<xsl:template match="video/track/@label" mode="repair">
    <xsl:attribute name="listing">
        <xsl:value-of select="."/>
    </xsl:attribute>
</xsl:template>

<!-- 2022-06-09 WeBWorK "stage" deprecated in favor of "task"       -->
<!-- We could use  match="webwork/stage"  but then this would only  -->
<!-- happen to the author's source while being prepared for the     -->
<!-- "extract-pg.xsl" worksheet.  But we also want to catch "stage" -->
<!-- coming back from an old WeBWorK server, which may be various   -->
<!-- places after we algorithmically manipulate the "webwork-reps"  -->
<!-- structure.  Instead, we just wait until now.  If necessary,    -->
<!-- perhaps "exercise/stage" for the post-server pass.             -->
<xsl:template match="stage" mode="repair">
    <task>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </task>
</xsl:template>

<!-- 2022-07-10 webwork//latex-image[syntax='PGtikz'] deprecated    -->
<!-- to just a normal latex-image. The text content for the code    -->
<!-- must be wrapped in a tikzpicture environment.                  -->
<xsl:template match="latex-image[@syntax='PGtikz']" mode="repair">
    <xsl:copy>
        <!-- we drop the @syntax attribute -->
        <xsl:apply-templates select="node()|@*[not(local-name(.) = 'syntax')]" mode="repair"/>
    </xsl:copy>
</xsl:template>
<xsl:template match="latex-image[@syntax='PGtikz']/text()" mode="repair">
    <xsl:text>\begin{tikzpicture}&#xa;</xsl:text>
    <xsl:call-template name="sanitize-latex">
        <xsl:with-param name="text">
            <xsl:copy>
                <xsl:apply-templates select="."/>
            </xsl:copy>
        </xsl:with-param>
    </xsl:call-template>
    <xsl:text>&#xa;\end{tikzpicture}&#xa;</xsl:text>
</xsl:template>

<!-- Deprecated 2018-12-30 in favor of "ca"   -->
<!-- Copy attributes...because you never know -->
<xsl:template match="circa" mode="repair">
    <ca>
        <xsl:apply-templates select="@*" mode="repair"/>
    </ca>
</xsl:template>

<!-- Due to naivete, we had empty templates for "keyboard characters"    -->
<!-- we did not have the skills to handle.  A good example was <dollar/> -->
<!-- simply because it is a (very) special character in LaTeX.  These    -->
<!-- were deprecated on 2019-02-06.  Beginning in 2022-12-26, we are     -->
<!-- providing fixes here, and removing all the (dead) code meant for    -->
<!-- backward compatibility.                                             -->

<!-- XML characters -->

<xsl:template match="less" mode="repair">
    <xsl:text>&lt;</xsl:text>
</xsl:template>

<xsl:template match="greater" mode="repair">
    <xsl:text>&gt;</xsl:text>
</xsl:template>

<!-- Ten LaTeX characters -->
<!-- # $ % ^ & _ { } ~ \  -->

<xsl:template match="hash" mode="repair">
    <xsl:text>#</xsl:text>
</xsl:template>

<xsl:template match="ampersand" mode="repair">
    <xsl:text>&amp;</xsl:text>
</xsl:template>

<xsl:template match="dollar" mode="repair">
    <xsl:text>$</xsl:text>
</xsl:template>

<xsl:template match="percent" mode="repair">
    <xsl:text>%</xsl:text>
</xsl:template>

<xsl:template match="circumflex" mode="repair">
    <xsl:text>^</xsl:text>
</xsl:template>

<xsl:template match="underscore" mode="repair">
    <xsl:text>_</xsl:text>
</xsl:template>

<xsl:template match="lbrace" mode="repair">
    <xsl:text>{</xsl:text>
</xsl:template>

<xsl:template match="rbrace" mode="repair">
    <xsl:text>}</xsl:text>
</xsl:template>

<xsl:template match="tilde" mode="repair">
    <xsl:text>~</xsl:text>
</xsl:template>

<xsl:template match="backslash" mode="repair">
    <xsl:text>\</xsl:text>
</xsl:template>

<!-- Lesser keyboard characters -->
<!-- [, ], *, /, `,             -->

<xsl:template match="lbracket" mode="repair">
    <xsl:text>[</xsl:text>
</xsl:template>

<xsl:template match="rbracket" mode="repair">
    <xsl:text>]</xsl:text>
</xsl:template>

<xsl:template match="asterisk" mode="repair">
    <xsl:text>*</xsl:text>
</xsl:template>

<xsl:template match="slash" mode="repair">
    <xsl:text>/</xsl:text>
</xsl:template>

<xsl:template match="backtick" mode="repair">
    <xsl:text>`</xsl:text>
</xsl:template>

<!-- Grouping constructions  -->
<!-- "braces" and "brackets" -->

<xsl:template match="braces" mode="repair">
    <xsl:text>{</xsl:text>
    <!-- attributes will be lost -->
    <xsl:apply-templates select="node()" mode="repair"/>
    <xsl:text>}</xsl:text>
</xsl:template>

<xsl:template match="brackets" mode="repair">
    <xsl:text>[</xsl:text>
    <!-- attributes will be lost -->
    <xsl:apply-templates select="node()" mode="repair"/>
    <xsl:text>]</xsl:text>
</xsl:template>

<!-- 2023-01-27: deprecate "datafile" to make way for a better    -->
<!-- Runestone-powered version.  Cosmetic replacement: "dataurl". -->
<!-- 2023-01-30: refine deprecation repair just after a minor CLI -->
<!-- release. A "datafile" element may be OK as a "new" use,      -->
<!-- with the presence of @label indicating use/application with  -->
<!-- Runestone Javascript.  So only automatically upgrade "old"   -->
<!-- uses lacking @label.                                         -->
<xsl:template match="datafile[not(@label)]" mode="repair">
    <dataurl>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </dataurl>
</xsl:template>

<xsl:template match="colophon/website[address]" mode="repair">
    <website>
        <xsl:apply-templates select="@*" mode="repair"/>
        <url>
            <xsl:attribute name="href">
                <xsl:value-of select="address"/>
            </xsl:attribute>
            <xsl:apply-templates select="name/node()" mode="repair"/>
        </url>
    </website>
</xsl:template>

<!-- 2023-08-28: deprecate the "console" "prompt" element -->

<!-- Removing this entire line typically orphans a text node    -->
<!-- just prior with a newline and indentation, but this should -->
<!-- not harm subsequent processing since we do not assume      -->
<!-- source is carefully authored as one element per line.      -->
<xsl:template match="console/prompt" mode="repair"/>

<!-- If there was a "prompt" element just preceding an "input"      -->
<!-- element, then we reach up and grab it and make it an attribute -->
<!-- of the "input" - but not if somebody happened to already start -->
<!-- using a @prompt attribute.                                     -->
<!-- https://www.oxygenxml.com/archives/xsl-list/199910/msg00541.html -->
<xsl:template match="console/input" mode="repair">
    <xsl:copy>
        <xsl:if test="not(@prompt) and preceding-sibling::*[1][self::prompt]">
            <xsl:attribute name="prompt">
                <xsl:value-of select="preceding-sibling::*[1][self::prompt]"/>
            </xsl:attribute>
        </xsl:if>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </xsl:copy>
</xsl:template>

<!-- 2023-09-07: move "description" to "shortdescription" -->
<!-- 2026-06-26: support this move for "description" inside "interactive" -->
<xsl:template match="image/description[not(*[not(self::var)])]|interactive/description[not(*[not(self::var)])]" mode="repair">
    <xsl:element name="shortdescription" namespace="">
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </xsl:element>
</xsl:template>

<!-- 2023-10-17: docinfo/latex-preamble is history -->

<xsl:template match="docinfo/latex-preamble" mode="repair">
    <!-- any attributes (no such thing?) are simply  -->
    <!-- orphaned and we just process child elements -->
    <xsl:apply-templates select="node()" mode="repair"/>
</xsl:template>

<!-- 2023-10-17: and "extra" LaTeX packages are re-worked -->

<xsl:template match="docinfo/latex-preamble/package" mode="repair">
    <xsl:element name="math-package">
        <xsl:attribute name="latex-name">
            <xsl:value-of select="."/>
        </xsl:attribute>
        <xsl:attribute name="mathjax-name">
            <xsl:value-of select="."/>
        </xsl:attribute>
    </xsl:element>
</xsl:template>

<!-- 2026-07-30: authored-attribute defaults gather under "defaults" -->

<!-- A "programs", "parsons", or "cross-references" element, as   -->
<!-- a child of "docinfo", moves inside a "defaults" element.     -->
<!-- The first of the trio present builds the container for all,  -->
<!-- and the same match then suppresses the others.  The retired  -->
<!-- "cross-references" element is renamed "xrefs" on the way in. -->
<xsl:template match="docinfo/programs | docinfo/parsons | docinfo/cross-references" mode="repair">
    <xsl:if test="count(preceding-sibling::programs | preceding-sibling::parsons | preceding-sibling::cross-references) = 0">
        <xsl:element name="defaults">
            <xsl:for-each select="../programs | ../parsons | ../cross-references">
                <xsl:choose>
                    <xsl:when test="self::cross-references">
                        <xsl:element name="xrefs">
                            <xsl:copy-of select="@*"/>
                        </xsl:element>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:copy>
                            <xsl:copy-of select="@*"/>
                        </xsl:copy>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:for-each>
        </xsl:element>
    </xsl:if>
</xsl:template>

<!-- The old "image-width" element, whose content was the width,  -->
<!-- becomes a "width" attribute on an "images" element.          -->
<xsl:template match="docinfo/defaults/image-width" mode="repair">
    <xsl:element name="images">
        <xsl:attribute name="width">
            <xsl:value-of select="normalize-space(.)"/>
        </xsl:attribute>
    </xsl:element>
</xsl:template>

<!-- 2026-07-31: a footnote within a "tabular" cell becomes a "tn" -->
<!-- table note, lettered and placed at the bottom of the table    -->
<!-- (Notes to tables: CMoS 18th ed., 3.77-3.81; specific notes    -->
<!-- at 3.80).  Any identification attributes are orphaned: a      -->
<!-- "tn" is not a cross-reference target.                         -->
<xsl:template match="tabular//fn" mode="repair">
    <tn>
        <xsl:apply-templates select="node()" mode="repair"/>
    </tn>
</xsl:template>

<!-- A "tn" authored as a block sibling of a cell's paragraphs is -->
<!-- relocated to the tail of the paragraph it follows, where its -->
<!-- mark belongs typographically; conversions then treat every   -->
<!-- "tn" as inline.  A "tn" amid the mixed text of a cell is     -->
<!-- already inline, and is untouched.                            -->
<xsl:template match="cell[p]/tn" mode="repair"/>

<xsl:template match="cell/p" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" mode="repair"/>
        <xsl:apply-templates select="following-sibling::tn[count(preceding-sibling::p[1] | current()) = 1]" mode="tn-relocate"/>
    </xsl:copy>
</xsl:template>

<!-- A faithful copy, since the "repair" mode on the same   -->
<!-- element is its suppression at the original location.   -->
<xsl:template match="cell/tn" mode="tn-relocate">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" mode="repair"/>
    </xsl:copy>
</xsl:template>

<!-- 2026-07-30: an "event" is bibliographic content, so it -->
<!-- belongs in "bibinfo", already its home for slideshows. -->
<!-- Suppressed at its origin within "docinfo"...           -->
<xsl:template match="docinfo/event" mode="repair"/>

<!-- ...and lands in an authored "bibinfo" without its own. -->
<xsl:template match="frontmatter/bibinfo[not(event)]" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="repair"/>
        <xsl:apply-templates select="node()" mode="repair"/>
        <xsl:apply-templates select="/*/docinfo/event" mode="event-relocate"/>
    </xsl:copy>
</xsl:template>

<!-- A faithful copy, since the "repair" mode on the same   -->
<!-- element is its suppression at the original location.   -->
<xsl:template match="docinfo/event" mode="event-relocate">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="repair"/>
        <xsl:apply-templates select="node()" mode="repair"/>
    </xsl:copy>
</xsl:template>

<!-- 2024-10-29: program is reworked -->

<!-- Add code element around text in program when missing -->
<xsl:template match="program[not(input|code)]" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="repair"/>
        <code>
            <xsl:value-of select="text()"/>
        </code>
    </xsl:copy>
</xsl:template>

<xsl:template match="program[not(code)]/input" mode="repair">
    <xsl:element name="code">
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </xsl:element>
</xsl:template>

<!-- Index deprecations -->
<!-- The way an index was constructed changed in 2017-07-14.  At 2024-08-08  -->
<!-- we are using the "repair" phase to move the old style to the new.  The  -->
<!-- old style looked like a "index-part" division with a mandatory          -->
<!-- "index-list" child.  Elements sprinkled into the text were an           -->
<!-- unstructured "index" or an "index" with up to three headings: "main",   -->
<!-- followed by possibly two "sub".  Now the division is "index" (as it     -->
<!-- should be!) and the entries are "idx".  A structured "idx" can have     -->
<!-- one to three "h" elements as the headings.                              -->

<!-- Change the division element -->
<xsl:template match="index-part" mode="repair">
    <index>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </index>
</xsl:template>

<!-- Change tne entry element, but avoid a new division name -->
<xsl:template match="index[not(index-list)]" mode="repair">
    <idx>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </idx>
</xsl:template>

<!-- Change first old style heading -->
<xsl:template match="index[not(index-list)]/main" mode="repair">
    <h>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </h>
</xsl:template>

<!-- Change second and third old style headings -->
<xsl:template match="index[not(index-list)]/sub" mode="repair">
    <h>
        <xsl:apply-templates select="node()|@*" mode="repair"/>
    </h>
</xsl:template>

<!-- Frontmatter repairs -->
<!-- 2024-10-10: we will no longer require an author to decide  -->
<!-- which frontmatter elements belong on a titlepage or in the -->
<!-- front colophon.  All the elements from both titlepage and  -->
<!-- colophon should now go in bibinfo. We run repair whenever  -->
<!-- the author has an existing titlepage or colophon without   -->
<!-- the new titlepage-items or colophon-items children.        -->
<xsl:template match="frontmatter[titlepage[not(titlepage-items)] or colophon[not(colophon-items)]]" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="repair"/>
        <bibinfo>
            <!-- Include deprecated children of titlepage and colophon -->
            <xsl:apply-templates select="titlepage/author" mode="repair"/>
            <xsl:apply-templates select="titlepage/editor" mode="repair"/>
            <xsl:apply-templates select="titlepage/credit" mode="repair"/>
            <xsl:apply-templates select="titlepage/date" mode="repair"/>
            <!-- for slides, we allowed an "event" -->
            <xsl:apply-templates select="titlepage/event" mode="repair"/>
            <!-- 2026-07-30: an "event" may also arrive from "docinfo" -->
            <xsl:apply-templates select="/*/docinfo/event[not(current()/titlepage/event)]" mode="event-relocate"/>
            <xsl:apply-templates select="colophon/credit" mode="repair"/>
            <xsl:apply-templates select="colophon/edition" mode="repair"/>
            <xsl:apply-templates select="colophon/website" mode="repair"/>
            <xsl:apply-templates select="colophon/copyright" mode="repair"/>
        </bibinfo>
        <!-- We (pretty much) duplicate everything, except two templates -->
        <!-- below hollow-out old-style "titlepage" and "colophon" to    -->
        <!-- match the new style with generators.                        -->
        <xsl:apply-templates select="node()" mode="repair"/>
    </xsl:copy>
</xsl:template>

<!-- We repair a "titlepage" that is not in the new style using a text   -->
<!-- generator.  The "titlepage" is structural and the empty text        -->
<!-- generator will be implemented in conversions to do the right thing. -->
<xsl:template match="titlepage[not(titlepage-items)]" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="repair"/>
        <titlepage-items/>
    </xsl:copy>
</xsl:template>

<!-- We repair a front "colophon" that is not in the new style using a   -->
<!-- text generator.  The "colophon" is structural and the empty text    -->
<!-- generator will be implemented in conversions to do the right thing. -->
<!-- NB: "frontmatter" is necessary so we don't clobber a BACK colophon! -->
<xsl:template match="frontmatter/colophon[not(colophon-items)]" mode="repair">
    <xsl:copy>
        <xsl:choose>
            <!-- Keep authored xml:id or label -->
            <xsl:when test="@xml:id|@label">
                <xsl:apply-templates select="@xml:id|@label" mode="repair"/>
            </xsl:when>
            <!-- Otherwise, use the label "front-colophon" -->
            <xsl:otherwise>
                <xsl:attribute name="label">
                    <xsl:text>front-colophon</xsl:text>
                </xsl:attribute>
            </xsl:otherwise>
        </xsl:choose>
        <!-- Include the colophon-items generator -->
        <colophon-items/>
    </xsl:copy>
</xsl:template>

<!-- We allow an author/editor/contributor to have their affiliation information not -->
<!-- wrapped in affiliation tags, but in that case we put them in affiliation tags.  -->
<xsl:template match="frontmatter//author[not(affiliation)]|frontmatter//editor[not(affiliation)]|frontmatter//contributor[not(affiliation)]" mode="repair">
    <xsl:copy>
        <!-- Include "personname" first -->
        <xsl:apply-templates select="personname|@*" mode="repair"/>
        <!-- If there are bare position/department/institution/address, wrap them in affiliation -->
        <xsl:if test="position or department or institution or location">
            <affiliation>
                <xsl:apply-templates select="position|department|institution|location" mode="repair"/>
            </affiliation>
        </xsl:if>
        <!-- Include all additional elements as they are -->
        <xsl:apply-templates select="*[not(self::personname or self::position or self::department or self::institution or self::location)]" mode="repair"/>
    </xsl:copy>
</xsl:template>

<!-- 2025-03-08:  "commentary" is deprecated.  Authors should remove it, -->
<!-- but we have suggested that it could be used with version support.   -->
<!-- So, if extant here in the repair phase, then it must have had a     -->
<!-- @component value that a publication file suggested retaining.       -->
<!-- So, just like the previous (now gone) "component" pass, we just     -->
<!-- unwrap the element.                                                 -->
<xsl:template match="commentary" mode="repair">
    <!-- do not duplicate "commentary", do not replicate   -->
    <!-- @component, do replicate element and text children -->
    <xsl:apply-templates select="node()" mode="repair"/>
</xsl:template>

<!-- Change listing captions to titles if there is not already a title -->
<xsl:template match="listing/caption" mode="repair">
    <xsl:if test="not(parent::listing/title)">
        <title>
            <xsl:apply-templates select="node()|@*" mode="repair"/>
        </title>
    </xsl:if>
</xsl:template>

<!-- 2025-08-15: Ensure that interactives all have a @pi:interactive-platform -->
<!-- that identifies their basic type                                         -->
<xsl:template match="interactive" mode="repair">
    <xsl:copy>
        <xsl:attribute name="pi:interactive-platform">
            <xsl:choose>
                <xsl:when test="@platform">
                    <xsl:value-of select="@platform"/>
                </xsl:when>
                <xsl:when test="@desmos">desmos</xsl:when>
                <xsl:when test="@geogebra">geogebra</xsl:when>
                <xsl:when test="@calcplot3d">calcplot3d</xsl:when>
                <xsl:when test="@circuitjs">circuitjs</xsl:when>
                <xsl:when test="@iframe">iframe</xsl:when>
                <xsl:otherwise>unknown</xsl:otherwise>
            </xsl:choose>
        </xsl:attribute>
        <xsl:apply-templates select="@*" mode="repair"/>
        <xsl:apply-templates select="node()" mode="repair"/>
    </xsl:copy>
</xsl:template>

<!-- 2025-11-04: a @runestone attribute was used to point into a file      -->
<!-- of raw HTML versions of Runestone exercises.  This was a              -->
<!-- transitional device to allow conversions of Runestone books into      -->
<!-- PreTeXt books as work progressed on better integration of Runestone.  -->
<!-- We make a dead simple replacment exercise, so numbering is preserved, -->
<!-- etc, and authors can adapt at their leisure.  We remove the offending -->
<!-- @runestone attribute via a very specialized template.                 -->

<!-- applies to exercise, PROJECT-LIKE, task -->
<xsl:template match="exercise[@runestone]|project[@runestone]|activity[@runestone]|exploration[@runestone]|investigation[@runestone]|task[@runestone]" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="repair"/>
        <p>There was once a (temporary) Runestone exercise here, which would only render in <init>HTML</init> output, and never in static output forms.  That (temporary) device is no longer supported as of 2025-11-04, since the exercise should now be authored in supported <pretext/> syntax.  You might alert the author to this situation.</p>
    </xsl:copy>
</xsl:template>

<!-- remove the @runestone, just in case -->
<xsl:template match="@runestone" mode="repair"/>


<!-- Deprecate "me", "men", "mdn" in favor of "md" (w/ "mrow" or bare) -->

<!-- Strategy: only two forms may be authored:                       -->
<!--                                                                 -->
<!--   Regular "md" with (multiple) "mrow" children                  -->
<!--     - @xml:id goes on the "mrow"                                -->
<!--     - @number may go on individual "mrow"                       -->
<!--     - @number may go on overall "md" (to mimic "mdn")           -->
<!--                                                                 -->
<!--   Bare "md" with content (like old "me" and "men")              -->
<!--     - @xml:id goes on the "md"                                  -->
<!--     - md/@number allows for me/men dichotomy                    -->
<!--     - md/@number is not assumed                                 -->
<!--                                                                 -->
<!-- Conversion here makes every existing display math construction  -->
<!-- look like the "regular" version described above.                -->

<!-- Not always a "repair" function, but we take the opportunity to  -->
<!-- record if an "mrow" is numbered or not, via a  @pi:numbered     -->
<!-- attribute, which is in our private namespace. For deprecations, -->
<!-- we just do it.  For authored forms, we interpret an authored    -->
<!-- @number attribute on authored "mrow" (status quo), on a regular -->
<!-- or bare "md" (new), and a global specification in "docinfo"     -->
<!-- (new).  These new features compensate for the deprecation       -->
<!-- of the "n"-series elements.                                     -->
<!-- The @tag attribute is a "local tag" formed with symbols,        -->
<!-- and precludes a number.                                         -->

<!-- Replace "me" and "me" by an "md" with one "mrow"    -->
<!--   - @xml:id will live on the "md" (new)             -->
<!--   - forcible  @npi:umbered  attribute for each,     -->
<!--       to preserve old behavior                      -->
<!--   - @pi:authored-one-line as empty sentinel, to     -->
<!--       distinguish from an *authored* single "mrow"  -->
<xsl:template match="me|men" mode="repair">
    <xsl:element name="md">
        <xsl:apply-templates select="@*" mode="repair"/>
        <!-- note origin as single-line display math -->
        <xsl:attribute name="pi:authored-one-line"/>
        <!-- manufacture an "mrow" to hold content -->
        <xsl:element name="mrow">
            <xsl:attribute name="pi:numbered">
                <xsl:choose>
                    <xsl:when test="self::me">
                        <xsl:text>no</xsl:text>
                    </xsl:when>
                    <xsl:when test="self::men">
                        <xsl:text>yes</xsl:text>
                    </xsl:when>
                </xsl:choose>
            </xsl:attribute>
            <xsl:apply-templates select="node()" mode="repair"/>
        </xsl:element>
    </xsl:element>
</xsl:template>

<!-- "md" with "mrow" needs no adjustment (status quo),   -->
<!-- the default "repair" templates are correct.  But     -->
<!-- see "mrow" template below for @pi:numbered behavior. -->

<!-- Replace "mdn" with "mrow" , by an "md" with @number     -->
<!--   - see "mrow" template below for @pi:numbered behavior -->
<!--   - @xml:id  was only ever allowed on individual "mrow" -->
<xsl:template match="mdn[mrow]" mode="repair">
    <xsl:element name="md">
        <xsl:apply-templates select="@*" mode="repair"/>
        <!-- copy the mrows -->
        <xsl:apply-templates select="node()" mode="repair"/>
    </xsl:element>
</xsl:template>

<!-- A bare "mdn" was just a transitional device during all    -->
<!-- the deprecation of the majority of math display elements  -->
<!-- and the introduction of more robust numbering options.    -->
<!-- It *never* enjoyed full support and was only in the wild  -->
<!-- for a couple of weeks.  We warn about this removal (as a  -->
<!-- deprecation) and suggest the "md" replacement.            -->
<xsl:template match="mdn[not(mrow)]" mode="repair"/>

<!-- Replace bare "md" by "md" with one "mrow"          -->
<!--   - @xml:id will live on the "md" (new)            -->
<!--   - md/@number  respected first                    -->
<!--   - md/@tag  transferred to the manufactured       -->
<!--       "mrow", where downstream processing expects  -->
<!--       it; @tag and @number are mutually exclusive  -->
<!--   - @pi:authored-one-line as empty sentinel, to    -->
<!--       distinguish from an *authored* single "mrow" -->
<xsl:template match="md[not(mrow)]" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="@*[not(local-name(.) = 'tag')]" mode="repair"/>
        <!-- note origin as single-line display math -->
        <xsl:attribute name="pi:authored-one-line"/>
        <!-- manufacture an "mrow" to hold content -->
        <xsl:element name="mrow">
            <!-- a @tag on the bare "md" is transferred to the "mrow" -->
            <xsl:copy-of select="@tag"/>
            <xsl:attribute name="pi:numbered">
                <xsl:choose>
                    <!-- a local @tag precludes a number -->
                    <xsl:when test="@tag">
                        <xsl:text>no</xsl:text>
                    </xsl:when>
                    <!-- possibly authored with @number -->
                    <xsl:when test="@number = 'yes'">
                        <xsl:text>yes</xsl:text>
                    </xsl:when>
                    <xsl:when test="@number = 'no'">
                        <xsl:text>no</xsl:text>
                    </xsl:when>
                    <!-- Now the global default set in "docinfo".  It would be -->
                    <!-- nice to store this choice in a global variable, but   -->
                    <!-- the mechanics of that result in erroneous recursion.  -->
                    <!-- So we simply repeatedly consult the "docinfo" built   -->
                    <!-- in the previous tree.                                 -->
                    <xsl:when test="$representations/pretext/docinfo/numbering/@equations = 'yes'">
                        <xsl:text>yes</xsl:text>
                    </xsl:when>
                    <xsl:when test="$representations/pretext/docinfo/numbering/@equations = 'no'">
                        <xsl:text>no</xsl:text>
                    </xsl:when>
                    <!-- the default default is to not number equations -->
                    <xsl:otherwise>
                        <xsl:text>no</xsl:text>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:attribute>
            <xsl:apply-templates select="node()" mode="repair"/>
        </xsl:element>
    </xsl:copy>
</xsl:template>

<!-- Authored "mrow", inside "md" or "mdn", get a @pi: numbered -->
<!-- attribute, according to hierarchy of specifications.       -->
<!-- ("md" and "mdn" parents in "match" could be overkill.)     -->
<xsl:template match="md/mrow|mdn/mrow" mode="repair">
    <xsl:copy>
        <xsl:apply-templates select="@*" mode="repair"/>
        <xsl:attribute name="pi:numbered">
            <xsl:choose>
                <xsl:when test="@tag">
                    <xsl:text>no</xsl:text>
                </xsl:when>
                <xsl:when test="@number = 'yes'">
                    <xsl:text>yes</xsl:text>
                </xsl:when>
                <xsl:when test="@number = 'no'">
                    <xsl:text>no</xsl:text>
                </xsl:when>
                <!-- now look to a (possible) "mdn" element as the      -->
                <!-- container, the @number attribute is not supported, -->
                <!-- the "n" implies a number by default                -->
                <xsl:when test="parent::mdn">
                    <xsl:text>yes</xsl:text>
                </xsl:when>
                <!-- now look to the (certain) "md" element as the container -->
                <xsl:when test="parent::md[@number = 'yes']">
                    <xsl:text>yes</xsl:text>
                </xsl:when>
                <xsl:when test="parent::md[@number = 'no']">
                    <xsl:text>no</xsl:text>
                </xsl:when>
                <!-- Now the global default set in "docinfo".  It would be -->
                <!-- nice to store this choice in a global variable, but   -->
                <!-- the mechanics of that result in erroneous recursion.  -->
                <!-- So we simply repeatedly consult the "docinfo" built   -->
                <!-- in the previous tree.                                 -->
                <xsl:when test="$representations/pretext/docinfo/numbering/@equations = 'yes'">
                    <xsl:text>yes</xsl:text>
                </xsl:when>
                <xsl:when test="$representations/pretext/docinfo/numbering/@equations = 'no'">
                    <xsl:text>no</xsl:text>
                </xsl:when>
                <!-- the default default is to not number equations -->
                <xsl:otherwise>
                    <xsl:text>no</xsl:text>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:attribute>
        <xsl:apply-templates select="node()" mode="repair"/>
    </xsl:copy>
</xsl:template>


<!-- Display math with "intertext"                           -->
<!-- We explode display math with "inertext" into a series   -->
<!-- of "md" interspered with "pi:intertext" elements.       -->
<!-- Note that $nodes only selects "mrow" and "intertext".   -->
<!-- Note:                                                   -->
<!-- *  This really is a "repair" for "mdn" with intertext   -->
<!--    there is a transformation to many "md".              -->
<!-- *  When the "mrow" get hit by the "repair" template,    -->
<!--    the numbering is recorded, based on the usual        -->
<!--     hierarchy,including the "md" or "mdn" container.    -->
<!-- *  The text of the "intertext" also gets hit by         -->
<!--   "repair"s of any sentence-level changes will be made. -->

<xsl:template match="md[intertext]|mdn[intertext]" mode="repair">

    <!-- We are going to explode an "md" with "intertext" into  -->
    <!-- multiple "md".  But for LaTeX, only, we are going to   -->
    <!-- basically put them all back together again.  But as we -->
    <!-- come up to each exploded "md" in the LaTeX conversion  -->
    <!-- it will not be so easy to see if the *original* "md"   -->
    <!-- had numbers or local tags.  So we determine this       -->
    <!-- prior to the explosion and record it onto each "md".   -->
    <!--                                                        -->
    <!-- The logic of deprecations here means it is perhaps     -->
    <!-- best to construct all the "mrow" in a disposable       -->
    <!-- node-set and analyze it for numbers and tags, recorded -->
    <!-- as a single boolean we can replicate.                  -->

    <xsl:variable name="trial-mrow-rtf">
        <xsl:apply-templates select="mrow" mode="repair"/>
    </xsl:variable>
    <xsl:variable name="trial-mrow" select="exsl:node-set($trial-mrow-rtf)"/>
    <xsl:variable name="b-needs-tags" select="boolean($trial-mrow/mrow[@pi:numbered = 'yes' or @tag])"/>

    <xsl:apply-templates select="." mode="intertext-exploder">
        <xsl:with-param name="nodes" select="mrow|intertext"/>
        <xsl:with-param name="location" select="'first'"/>
        <xsl:with-param name="b-needs-tags" select="$b-needs-tags"/>
    </xsl:apply-templates>
</xsl:template>

<xsl:template match="md[intertext]|mdn[intertext]" mode="intertext-exploder">
    <xsl:param name="nodes"/>
    <xsl:param name="location"/>
    <xsl:param name="b-needs-tags"/>

    <!-- No nodes, no action, so recursion ends, -->
    <!-- AND there is no $lead-node to switch on. -->
    <xsl:if test="$nodes">
        <!-- will switch on if $lead node is "mrow" or "interext" -->
        <xsl:variable name="lead-node" select="$nodes[1]"/>
        <xsl:choose>
            <xsl:when test="$lead-node[self::mrow]">
                <!-- The first intertext after $lead-node (if any) -->
                <!-- marks the end of a non-empty run of "mrow"    -->
                <!-- (we know $lead-node is an "mrow")             -->
                <xsl:variable name="break" select="$lead-node/following-sibling::intertext[1]"/>
                <!-- A maximal run of contiguous "mrow" starting at $lead-node.    -->
                <!-- All "mrow" including $lead-node, and those coming afterwards. -->
                <!-- $break is empty for the last run, so "not($break)" will be    -->
                <!-- true and there is no filtering.  Otherwise we compare an      -->
                <!-- "mrow"'s following "intertext" to see if it is $break or not. -->
                <xsl:variable name="md-block" select="$lead-node |
                    $lead-node/following-sibling::mrow[not($break) or (count(following-sibling::intertext[1] | $break) = count(following-sibling::intertext[1]))]"/>
                <!-- put the maximal run into a fresh "md"     -->
                <!-- Location helps with reconstruction in     -->
                <!-- the LaTeX conversion, where we un-explode -->
                <md pi:location="{$location}">
                    <xsl:attribute name="pi:latex-intertext-needs-tags">
                        <xsl:choose>
                            <xsl:when test="$b-needs-tags">
                                <xsl:text>yes</xsl:text>
                            </xsl:when>
                            <xsl:otherwise>
                                <xsl:text>no</xsl:text>
                            </xsl:otherwise>
                        </xsl:choose>
                    </xsl:attribute>
                    <xsl:apply-templates select="$md-block" mode="repair"/>
                </md>
                <!-- "first" is never repeated, and  -->
                <!-- "last" only happens in one case -->
                <xsl:variable name="next-location">
                    <xsl:choose>
                        <xsl:when test="not($break/following-sibling::intertext)">
                            <xsl:text>last</xsl:text>
                        </xsl:when>
                        <xsl:otherwise>
                            <xsl:text>intermediate</xsl:text>
                        </xsl:otherwise>
                    </xsl:choose>
                </xsl:variable>
                <!-- We strip down the set of $nodes, starting at the     -->
                <!-- last "mrow" of the $md-block, so the remainder will  -->
                <!-- normally start with an "intertext", or the remainder -->
                <!-- might be empty (which will halt the recursion).      -->
                <xsl:apply-templates select="." mode="intertext-exploder">
                    <xsl:with-param name="nodes" select="$md-block[last()]/following-sibling::*"/>
                    <xsl:with-param name="location" select="$next-location"/>
                    <xsl:with-param name="b-needs-tags" select="$b-needs-tags"/>
                </xsl:apply-templates>
            </xsl:when>
            <xsl:when test="$lead-node[self::intertext]">
                <!-- We place the text of the intertext into a custom      -->
                <!-- internal element for handling by the common templates -->
                <pi:intertext>
                    <xsl:apply-templates select="$lead-node/node()" mode="repair"/>
                </pi:intertext>
                <!-- Strip down $nodes by simply removing the leading "intertext" -->
                <xsl:apply-templates select="." mode="intertext-exploder">
                    <xsl:with-param name="nodes" select="$lead-node/following-sibling::*"/>
                    <xsl:with-param name="location" select="$location"/>
                    <xsl:with-param name="b-needs-tags" select="$b-needs-tags"/>
                </xsl:apply-templates>
            </xsl:when>
            <!-- orioginal  $nodes  ensures we never get here -->
            <xsl:otherwise/>
        </xsl:choose>
    </xsl:if>
</xsl:template>


<!-- ############################## -->
<!-- Killed, in Chronological Order -->
<!-- ############################## -->

<!-- 2017-07-16  killed, from 2015-03-13 deprecation -->
<xsl:template match="paragraph" mode="repair"/>

<!-- 2019-02-20  deprecated and killed simultaneously -->
<xsl:template match="todo" mode="repair"/>

<!-- A "pagebreak" should have had limited -->
<!-- uptake, so no real care taken,        -->
<!-- Deprecated 2021-03-17                 -->
<xsl:template match="pagebreak" mode="repair"/>

<!-- @permid experiments retired 2024-07-24, -->
<!-- so eliminated in this phase             -->
<xsl:template match="@permid" mode="repair"/>

<!-- 2024-08-05: remove metadata elements from a sidebyside, -->
<!-- which have not been schema-compliant since circa 2017   -->
<xsl:template match="sidebyside/*[&METADATA-FILTER;]" mode="repair"/>

<!-- 2026-07-08  "title" deprecated on "introduction"/"conclusion" of a traditional division -->

<!-- The title is dropped, silently; conversions provide a localized -->
<!-- heading whenever a heading is called for.  Titles remain within -->
<!-- specialized divisions, which are never structured, and so are   -->
<!-- never summary pages (the situation that demands the heading)    -->
<xsl:template match="introduction[parent::article or parent::chapter or parent::appendix or parent::section or parent::subsection]/title|conclusion[parent::article or parent::chapter or parent::appendix or parent::section or parent::subsection]/title" mode="repair"/>

<!-- 2026-08-06  a "references" preface is a "headnote" -->

<!-- The "introduction" becomes a "headnote", which never carries a  -->
<!-- "title", so any title is discarded; the deprecation warning     -->
<!-- says so plainly                                                 -->
<xsl:template match="references/introduction" mode="repair">
    <headnote>
        <xsl:apply-templates select="node()[not(self::title)]|@*" mode="repair"/>
    </headnote>
</xsl:template>

<!-- A "references" has no "conclusion": the element is dropped -->
<!-- whole, and the deprecation warning says so plainly         -->
<xsl:template match="references/conclusion" mode="repair"/>

<!-- 2026-08-20  "ol" structuring an "exercise" is deprecated -->

<!-- An "ol" as a child of an "exercise", or of its "statement",  -->
<!-- once lettered its items like exercise parts.  The list is    -->
<!-- wrapped in a "p" here, like any other list in running prose. -->
<!-- Within the divisions that historically lettered these lists, -->
<!-- a stamp preserves the extra list level for existing sources; -->
<!-- new sources letter a list with marker="(a)", or structure    -->
<!-- real parts with "task".  The deprecation warning says so.    -->
<xsl:template match="exercise/ol|exercise/statement/ol" mode="repair">
    <p>
        <xsl:copy>
            <xsl:if test="ancestor::exercises or ancestor::reading-questions or ancestor::worksheet or ancestor::handout">
                <xsl:attribute name="pi:legacy-parts">
                    <xsl:text>yes</xsl:text>
                </xsl:attribute>
            </xsl:if>
            <xsl:apply-templates select="node()|@*" mode="repair"/>
        </xsl:copy>
    </p>
</xsl:template>

<!-- ########## -->
<!-- Enrichment -->
<!-- ########## -->

<!-- Certain markup can be translated into more primitive versions using      -->
<!-- existing markup, so we do a translation of certain forms into more       -->
<!-- potentially verbose forms that an author might tire of doing repeatedly. -->
<!-- See below for examples.  This is better than making a result-tree        -->
<!-- fragment and applying templates, since all context is lost that way.     -->

<!-- Visual URLs -->
<!-- A great way to present a URL is with some clickable text.  But that    -->
<!-- is useless in print.  And maybe a reader really would like to see the  -->
<!-- actual URL.  So "@visual" is a version of the URL that is pleasing to  -->
<!-- look at, maybe just a TLD, no protocol (e.g "https://"), no "www."     -->
<!-- if unnecessary, etc.  This "visual URL"  may be provided by an author  -->
<!-- through a @visual attribute.  When this attribute is not provided, we  -->
<!-- manufacture a reasonable version from the real, actual URL that must   -->
<!-- necessarily be given.  To prevent consideration of a visual version,   -->
<!-- an author can set @visual="" and no manufactured version will be made. -->
<xsl:template match="url[node() and not(@visual)]|dataurl[node() and not(@visual)]" mode="enrichment">
    <!-- We create a new "default-ish" visual URL for a  -->
    <!-- content-full "url" when none has been authored -->
    <!--  -->
    <!-- We get a candidate visual URI             -->
    <!--   @href: external link/reference/location -->
    <!--   dataurl[@source]:  internal link        -->
    <xsl:variable name="uri">
        <xsl:choose>
            <!-- "url" and "dataurl" both support external @href -->
            <xsl:when test="@href">
                <xsl:value-of select="@href"/>
            </xsl:when>
            <!-- a "dataurl" might be local, @source is         -->
            <!-- indication, so prefix with a base URL,         -->
            <!-- add "external" directory, via template useful  -->
            <!-- also for visual URL formulation in -assembly   -->
            <!-- N.B. we are using the base URL, since this is  -->
            <!-- the most likely need by employing conversions. -->
            <!-- It would eem duplicative in a conversion to    -->
            <!-- HTML, so could perhaps be killed in that case. -->
            <!-- But it is what we want for LaTeX, and perhaps  -->
            <!-- for EPUB, etc.                                 -->
            <xsl:when test="self::dataurl and @source">
                <xsl:apply-templates select="." mode="static-url"/>
            </xsl:when>
            <!-- empty will be non-functional -->
            <xsl:otherwise/>
        </xsl:choose>
    </xsl:variable>
    <!-- And clean-up automatically in the prevalent cases -->
    <xsl:variable name="truncated-href">
        <xsl:choose>
            <xsl:when test="substring(@href, 1, 8) = 'https://'">
                <xsl:value-of select="substring($uri, 9)"/>
            </xsl:when>
            <xsl:when test="substring(@href, 1, 7) = 'http://'">
                <xsl:value-of select="substring($uri, 8)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$uri"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <!--  -->
    <xsl:copy>
        <!-- copy all the attributes, which might include a @visual,      -->
        <!-- and that @visual could be empty (a signal it is not desired) -->
        <xsl:apply-templates select="@*" mode="enrichment"/>
        <!-- Provide the "missing" @visual (see match above),  -->
        <!-- so now *every* content-full "url" has an @visual, -->
        <!-- either authored or provided automatically here.   -->
        <!-- Conversions decide what to do with it.            -->
        <xsl:attribute name="visual">
            <xsl:value-of select="$truncated-href"/>
        </xsl:attribute>
        <!-- done with attributes, copy the content -->
        <xsl:apply-templates select="node()" mode="enrichment"/>
    </xsl:copy>
</xsl:template>

<!-- ######################################## -->
<!-- Enrichment of Geogebra interactives      -->
<!-- ######################################## -->

<!-- Expand reference to Geogebra by material-id to use -->
<!-- an implicit applet approach with a slate to better -->
<!-- facilitate all of the applet control parameters.   -->
<xsl:template match="interactive[@geogebra]" mode="enrichment">
    <xsl:param name="default-aspect" select="'1:1'" />
    <xsl:variable name="ggbMaterialWidth">
        <xsl:choose>
            <xsl:when test="@material-width">
                <xsl:value-of select="@material-width"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:text>800</xsl:text>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="aspect-ratio">
        <xsl:apply-templates select="." mode="get-aspect-ratio">
            <xsl:with-param name="default-aspect" select="$default-aspect" />
        </xsl:apply-templates>
    </xsl:variable>
    <xsl:variable name="ggbMaterialHeight">
        <xsl:choose>
            <xsl:when test="@material-height">
                <xsl:value-of select="@material-height"/>
            </xsl:when>
            <xsl:when test="$aspect-ratio=''"/>
            <xsl:otherwise>
                <xsl:value-of select="round($ggbMaterialWidth div $aspect-ratio)" />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="slate-copy-attr" select="'toolbar algebra-input reset-icon shift-drag-zoom zoom-buttons'"/>
    <xsl:variable name="interactive-drop-attr" select="concat($slate-copy-attr, ' ', 'geogebra material-width material-height')"/>
    <interactive>
        <xsl:attribute name="platform">
            <xsl:text>geogebra</xsl:text>
        </xsl:attribute>
        <xsl:if test="not(@aspect)">
            <xsl:attribute name="aspect">
                <xsl:value-of select="$aspect-ratio"/>
            </xsl:attribute>
        </xsl:if>
        <!-- Restore all of the original attributes not processed separately -->
        <xsl:copy-of select="@*[not(contains(concat(' ', $interactive-drop-attr, ' '), concat(' ', local-name(), ' ')))]"/>
        <!-- done with attributes, copy the content -->
        <xsl:apply-templates select="node()" mode="enrichment"/>
        <slate>
            <!-- A manufactured "slate" carries a genuine @xml:id (not a  -->
            <!-- pi: stamp): the interactive machinery locates slates by  -->
            <!-- id, and the derived name keeps it stable and             -->
            <!-- collision-free.                                          -->
            <xsl:attribute name="xml:id">
                <xsl:value-of select="@pi:assembly-id"/>
                <xsl:text>-ggb-slate</xsl:text>
            </xsl:attribute>
            <xsl:attribute name="surface">
                <xsl:text>geogebra</xsl:text>
            </xsl:attribute>
            <xsl:attribute name="material">
                <xsl:value-of select="@geogebra"/>
            </xsl:attribute>
            <xsl:attribute name="material-width">
                <xsl:value-of select="$ggbMaterialWidth"/>
            </xsl:attribute>
            <xsl:if test="$ggbMaterialHeight != ''">
                <xsl:attribute name="material-height">
                    <xsl:value-of select="$ggbMaterialHeight"/>
                </xsl:attribute>
            </xsl:if>
            <xsl:copy-of select="@*[contains(concat(' ', $slate-copy-attr, ' '), concat(' ', local-name(), ' '))]"/>
        </slate>
    </interactive>
</xsl:template>

<!-- ###### -->
<!-- Labels -->
<!-- ###### -->

<!-- The "labels" pass (pass 11).  It promotes an authored         -->
<!-- @xml:id to @label and records localization support.  It is    -->
<!-- deliberately separate from the id-attribute mechanism in      -->
<!-- "Identification": it runs after @pi:assembly-id is stamped and-->
<!-- before @pi:unique-id, and the @label values created here are  -->
<!-- read by that final @pi:unique-id stamp.                       -->

<!-- Reader-facing identification switched to prefer @label,      -->
<!-- rather than @xml:id (at 1779e6dbc84c6ecc).  So to preserve   -->
<!-- authored (crafted) identifier strings, we copy the old over  -->
<!-- into the new.  This preserves identifiers in output          -->
<!-- (filenames, fragment identifiers).  Subsequent passes        -->
<!-- should not introduce or remove elements.                     -->

<!-- 2023-03-30: This is old commentary about the use of the -->
<!-- "unique-id" identifier in the LaTeX conversion, which   -->
<!-- has now become more universal.  Once identifiers settle -->
<!-- down, we can clean up the parts of this worth keeping.  -->
<!--  -->
<!-- This produces unique strings that are internal to the  -->
<!-- LaTeX (intermediate) file.  Since neither author nor   -->
<!-- reader will ever see these, they can be as fast and as -->
<!-- wild as necessary.  But for mature works, likely with  -->
<!-- @permid on many relevant objects, or many @xml:id      -->
<!-- provided for URLs in HTML, these can be predictable    -->
<!-- across runs (and therefore help with tweaking the LaTeX-->
<!-- output under revision control) These are employed with -->
<!-- \label{}, \ref{}, \cite{}, \pageref{}, \eqref{}, etc.  -->
<!-- We can change this at will, with no adverse effects    -->
<!-- NB: colons are banned from PTX @xml:id, and will not   -->
<!-- appear in @permid, though we could use dashes instead  -->
<!-- without getting duplicates.  The prefixes guarantee    -->
<!-- that the three uniqueness schemes do not overlap.      -->

<!-- First, we upgrade an authored @xml:id to a @label,       -->
<!-- WHEN there is no authored @label present.  This is a     -->
<!-- sort of backward-compatibility maneuver.  An @xml:id     -->
<!-- now serves only as a sort of internal name for a target  -->
<!-- node (like a cross-reference, "xref"), while it formerly -->
<!-- served as a string to generate various bits of output,   -->
<!-- such as filenames in HTML output.                        -->

<!-- This same walk also records language support (see the    -->
<!-- "Languages" section for the $locales variable), since    -->
<!-- both jobs are simple attribute additions and do not      -->
<!-- deserve separate passes through the entire source.       -->

<xsl:template match="*" mode="labels">
    <xsl:copy>
        <!-- duplicate all attributes -->
        <xsl:apply-templates select="@*" mode="labels"/>
        <!-- Case: an authored @xml:id, not an authored @label -->
        <xsl:if test="@xml:id and not(@label)">
            <xsl:attribute name="label">
                <xsl:value-of select="@xml:id"/>
            </xsl:attribute>
        </xsl:if>
        <!-- Case: a @label provided in source by author                 -->
        <!-- It is helpful to distinguish between an authored @label and -->
        <!-- one that this template creates by copying over a @xml:id.   -->
        <!-- So we drop an (empty) attribute as a boolean indicator.     -->
        <!-- This form will simplify checks later at "run-time".         -->
        <xsl:if test="@label">
            <xsl:attribute name="pi:authored-label"/>
        </xsl:if>
        <!-- A supported @xml:lang is recorded in an internal attribute -->
        <!-- for use by localizations.  The root element is the         -->
        <!-- fail-safe node on a language query up the tree, so there   -->
        <!-- an absent, or unsupported, @xml:lang becomes the default,  -->
        <!-- en-US.  An unsupported @xml:lang below the root is left    -->
        <!-- alone, as it might be relevant for future features.        -->
        <xsl:choose>
            <xsl:when test="@xml:lang = $locales">
                <xsl:attribute name="pi:locale-lang">
                    <xsl:value-of select="@xml:lang"/>
                </xsl:attribute>
            </xsl:when>
            <xsl:when test="not(parent::*)">
                <xsl:attribute name="xml:lang">
                    <xsl:text>en-US</xsl:text>
                </xsl:attribute>
                <xsl:attribute name="pi:locale-lang">
                    <xsl:text>en-US</xsl:text>
                </xsl:attribute>
            </xsl:when>
            <xsl:otherwise/>
        </xsl:choose>
        <!-- recurse -->
        <xsl:apply-templates select="node()" mode="labels"/>
    </xsl:copy>
</xsl:template>

<!-- We look for duplicate identifiers both right after    -->
<!-- assembly and right after automatic generation.  The   -->
<!-- application of these templates is mixed-in to the     -->
<!-- creation of the trees.                                -->
<!-- NB: these were built as regular templates and the     -->
<!-- root of the relevant tree was passed in, this created -->
<!-- some error with the construction of the final tree:   -->
<!-- "Recursive definition of root"                        -->
<xsl:template name="duplication-check-xmlid">
    <!-- pass in all elements with @xml:id attributes -->
    <xsl:param name="nodes"/>
    <!-- 'authored' or 'generated', just influences messages -->
    <xsl:param name="purpose"/>

    <xsl:call-template name="duplication-check-attribute">
        <xsl:with-param name="nodes" select="$nodes"/>
        <xsl:with-param name="purpose" select="$purpose"/>
        <xsl:with-param name="target-attr" select="'xml:id'"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="duplication-check-label">
    <!-- pass in all elements with @label attributes -->
    <xsl:param name="nodes"/>
    <!-- 'authored' or 'generated', just influences messages -->
    <xsl:param name="purpose"/>

    <xsl:call-template name="duplication-check-attribute">
        <xsl:with-param name="nodes" select="$nodes"/>
        <xsl:with-param name="purpose" select="$purpose"/>
        <xsl:with-param name="target-attr" select="'label'"/>
    </xsl:call-template>
</xsl:template>

<xsl:template name="duplication-check-attribute">
    <xsl:param name="nodes"/>
    <!-- 'authored' or 'generated', just influences messages -->
    <xsl:param name="purpose"/>
    <xsl:param name="target-attr"/>

    <!-- construct a list of just the sorted labels -->
    <xsl:variable name="attr-values-sorted-rtf">
        <xsl:for-each select="$nodes/@*[name() = $target-attr]">
            <xsl:sort select="."/>
            <label>
                <xsl:value-of select="."/>
            </label>
        </xsl:for-each>
    </xsl:variable>
    <xsl:variable name="attr-values-sorted" select="exsl:node-set($attr-values-sorted-rtf)"/>

    <!-- traverse sorted list to find duplicates -->
    <xsl:for-each select="$attr-values-sorted/*">
        <!-- save off the string on current node -->
        <xsl:variable name="attr-value" select="."/>
        <!-- get previous two labels - will be '' if out of bounds -->
        <xsl:variable name="prev-value" select="string(preceding-sibling::*[1])"/>
        <xsl:variable name="prev-prev-value" select="string(preceding-sibling::*[2])"/>
        <!-- identify only first instance of a duplicate for each label -->
        <xsl:if test="($attr-value= $prev-value) and ($attr-value != $prev-prev-value)">
            <xsl:choose>
                <xsl:when test="$purpose = 'authored'">
                    <xsl:message>PTX:ERROR: the @<xsl:value-of select="$target-attr"/> value "<xsl:value-of select="$attr-value"/>" should be unique, but is authored multiple times.</xsl:message>
                </xsl:when>
            </xsl:choose>
            <xsl:message>           Results will be unpredictable, and likely incorrect.  Information on the locations follows:</xsl:message>
            <!-- use the original nodes to report location of instances -->
            <!-- select where they have an attr with the correct name and it has correct value -->
            <xsl:for-each select="$nodes[@*[name() = $target-attr] = $attr-value]">
                <xsl:apply-templates select="." mode="location-report" />
            </xsl:for-each>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

<!-- ######### -->
<!-- Languages -->
<!-- ######### -->

<!-- The variable $locales is a node-set of all the locales which have       -->
<!-- supported localization files.  A comparison of an @xml:lang (string)    -->
<!-- with $locales (node-set) will be true if the attribute value is a       -->
<!-- string value of one of the nodes in the node-set.  So it is easy to     -->
<!-- create a boolean value for localization support.                        -->
<!-- The recording of language support in @pi:locale-lang attributes is part -->
<!-- of the "labels" pass, since both jobs are simple attribute additions    -->
<!-- and do not deserve separate passes through the entire source.           -->
<xsl:variable name="locales" select="document('localizations/localizations.xml')/localizations/locale" />

<!-- ######### -->
<!-- Numbering -->
<!-- ######### -->

<!-- We use the "augment" pass to compute, and add, partially naïve           -->
<!-- information about numbers of objects, to be interpreted later by         -->
<!-- templates in the "-common" stylesheet.  By "naïve" we mean that          -->
<!-- these routines may depend on publisher variables (e.g. specification     -->
<!-- of roots of subtrees for serial numbers of blocks) but do not depend     -->
<!-- on subtlties of numbering (such as the structured/unstructured           -->
<!-- division dichotomy), which are addressed in the "-common" stylesheet.    -->
<!-- In this way, this information could be interpreted in new ways by        -->
<!-- additional conversions.                                                  -->
<!--                                                                          -->
<!-- The manufactured @pi:struct attribute is the (naïve) hierarchical number -->
<!-- of the *container* of an element, known as the "structure number"        -->
<!-- of an element.  The @pi:serial attribute is the computed serial number   -->
<!-- of the element, known as the "serial number".  Typically combining       -->
<!-- these two attributes forms teh number of an element.  As many            -->
<!-- practical subtleties about these numbers is delayed until their          -->
<!-- interpretation by templates in the "-common" stylesheet.                 -->

<!-- For every type of division, everywhere, the "division-serial-number"   -->
<!-- modal template will return a count of preceding peers at that level.   -->
<!-- The @pi:struct attribute is the structure number of the *parent*       -->
<!-- (container), which seems odd here, but fits the general scheme better. -->
<!-- The @pi:level attribute is helpful, and trvislly to compute here.      -->
<xsl:template match="part|chapter|appendix|section|subsection|subsubsection|exercises|solutions|reading-questions|references|glossary|worksheet|handout" mode="augment">
    <xsl:param name="parent-struct"/>
    <xsl:param name="level"/>

    <xsl:variable name="the-serial">
        <xsl:apply-templates select="." mode="division-serial-number"/>
    </xsl:variable>
    <xsl:variable name="new-struct">
        <xsl:choose>
            <!-- Parts as Roman numerals make for a lot of clutter.      -->
            <!-- We tend to only use them when necessary to diambiguate  -->
            <!-- a cross-reference in the case where these numbers are   -->
            <!-- structural.  So rightly or wrongly, and owing to        -->
            <!-- historical work, we squelch them as the lead item of a  -->
            <!-- structural number.  So here the Roman numeral will be   -->
            <!-- preserved as a serial number, but the construction of   -->
            <!-- the structural numbers will be delayed one level.       -->
            <!-- (It seems harder to strip these in -common.)            -->
            <xsl:when test="self::part"/>
            <!-- Decorative specialized divisions are transparent:    -->
            <!-- they do not extend the structure number chain, so    -->
            <!-- blocks inside them use the parent division's chain.  -->
            <!-- Specialized divisions are leaves of the division     -->
            <!-- tree, so this does not affect any descendant         -->
            <!-- division's @pi:struct.                               -->
            <xsl:when test="&SPECIALIZED-DIVISION-FILTER;">
                <xsl:variable name="is-numbered">
                    <xsl:apply-templates select="." mode="is-specialized-own-number"/>
                </xsl:variable>
                <xsl:choose>
                    <xsl:when test="$is-numbered = 'true'">
                        <xsl:value-of select="$parent-struct"/>
                        <xsl:if test="not($parent-struct='')">
                            <xsl:text>.</xsl:text>
                        </xsl:if>
                        <xsl:value-of select="$the-serial"/>
                    </xsl:when>
                    <xsl:otherwise>
                        <xsl:value-of select="$parent-struct"/>
                    </xsl:otherwise>
                </xsl:choose>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$parent-struct"/>
                <xsl:if test="not($parent-struct='')">
                    <xsl:text>.</xsl:text>
                </xsl:if>
                <xsl:value-of select="$the-serial"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="next-level" select="$level + 1"/>
    <xsl:copy>
        <xsl:attribute name="pi:struct">
            <xsl:value-of select="$parent-struct"/>
        </xsl:attribute>
        <xsl:attribute name="pi:serial">
            <xsl:value-of select="$the-serial"/>
        </xsl:attribute>
        <xsl:attribute name="pi:level">
            <xsl:value-of select="$next-level"/>
        </xsl:attribute>
        <!-- Full structure chain including this division's       -->
        <!-- contribution.  Decorative specialized divisions are  -->
        <!-- transparent (pass through parent's chain).  Used by  -->
        <!-- block elements to compute their structure numbers.   -->
        <xsl:attribute name="pi:block-struct">
            <xsl:value-of select="$new-struct"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()|@*" mode="augment">
            <xsl:with-param name="parent-struct" select="$new-struct"/>
            <xsl:with-param name="level" select="$next-level"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- The top-level division (book, article, ...) is the root of the   -->
<!-- division tree, at level 0, which the catch-all does not record.  -->
<!-- A level-0 numbering scheme counts continuously from the root.    -->
<xsl:template match="book|article|slideshow|letter|memo" mode="augment">
    <xsl:copy>
        <xsl:attribute name="pi:level">
            <xsl:text>0</xsl:text>
        </xsl:attribute>
        <xsl:apply-templates select="node()|@*" mode="augment">
            <xsl:with-param name="parent-struct" select="''"/>
            <xsl:with-param name="level" select="0"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- See the definitions of levels in -common.  For a book with parts   -->
<!-- ($parts != 'absent') we consider parts as peers of frontmatter and -->
<!-- backmatter.  So we need to increment the level in this case, only. -->
<!-- NB: this might consolidate with above, but seems better solo.      -->
<!-- NB: with some study and work, this situation might be improved?    -->
<xsl:template match="frontmatter|backmatter" mode="augment">
    <xsl:param name="parent-struct"/>
    <xsl:param name="level"/>

    <xsl:variable name="next-level">
        <xsl:choose>
            <xsl:when test="($parts = 'decorative') or ($parts = 'structural')">
                <xsl:value-of select="$level + 1"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="$level"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <!-- we only add a level (not necessary?) -->
    <!-- and just pass along structure number -->
    <xsl:copy>
        <xsl:attribute name="pi:level">
            <xsl:value-of select="$next-level"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()|@*" mode="augment">
            <xsl:with-param name="parent-struct" select="$parent-struct"/>
            <xsl:with-param name="level" select="$next-level"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Labels of ordered lists have formatting codes, which  -->
<!-- we detect here and pass on to other more specialized  -->
<!-- templates for implementation specifics                -->
<!-- In order: Arabic (0-based), Arabic (1-based)          -->
<!-- lower-case Latin, upper-case Latin,                   -->
<!-- lower-case Roman numeral, upper-case Roman numeral    -->
<!-- Absent a label attribute, defaults go 4 levels deep   -->
<!-- (max for Latex) as: Arabic, lower-case Latin,         -->
<!-- lower-case Roman numeral, upper-case Latin            -->
<xsl:template match="ol" mode="format-code">
    <xsl:param name="level"/>
    <xsl:choose>
        <xsl:when test="@marker">
            <xsl:choose>
                <xsl:when test="contains(@marker,'0')">0</xsl:when>
                <xsl:when test="contains(@marker,'1')">1</xsl:when>
                <xsl:when test="contains(@marker,'a')">a</xsl:when>
                <xsl:when test="contains(@marker,'A')">A</xsl:when>
                <xsl:when test="contains(@marker,'i')">i</xsl:when>
                <xsl:when test="contains(@marker,'I')">I</xsl:when>
                <!-- DEPRECATED 2015-12-12 -->
                <xsl:when test="@marker=''" />
                <xsl:otherwise>
                    <xsl:message>PTX:ERROR: ordered list label (<xsl:value-of select="@marker" />) not recognized</xsl:message>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:when>
        <xsl:otherwise>
            <xsl:choose>
                <xsl:when test="$level='0'">1</xsl:when>
                <xsl:when test="$level='1'">a</xsl:when>
                <xsl:when test="$level='2'">i</xsl:when>
                <xsl:when test="$level='3'">A</xsl:when>
                <xsl:otherwise>
                    <xsl:message>PTX:ERROR: ordered list is more than 4 levels deep (at level <xsl:value-of select="$level" />; a legacy exercise-parts list counts one level extra)</xsl:message>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="ol" mode="augment">
    <!-- The list level is intrinsic: a count of enclosing ordered   -->
    <!-- lists, raised by one within a list the repair phase stamped -->
    <!-- as a legacy exercise-parts list (the stamp lifts the whole  -->
    <!-- nest, so sublists letter as they always have).              -->
    <xsl:variable name="ordered-list-level" select="count(ancestor::ol) + number(boolean(ancestor-or-self::ol[@pi:legacy-parts]))"/>
    <xsl:variable name="format-code">
        <xsl:apply-templates select="." mode="format-code">
            <xsl:with-param name="level" select="$ordered-list-level"/>
        </xsl:apply-templates>
    </xsl:variable>
    <!-- deconstruct the left and right adornments of the label   -->
    <!-- or provide default adornments, consistent with LaTeX     -->
    <!-- then store them                                          -->
    <xsl:variable name="marker-prefix">
        <xsl:choose>
            <xsl:when test="@marker">
                <xsl:value-of select="substring-before(@marker, $format-code)" />
            </xsl:when>
            <xsl:when test="$format-code = 'a' and $ordered-list-level = '1'">
                <xsl:text>(</xsl:text>
            </xsl:when>
            <xsl:otherwise />
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="marker-suffix">
        <xsl:choose>
            <xsl:when test="@marker">
                <xsl:value-of select="substring-after(@marker, $format-code)" />
            </xsl:when>
            <xsl:when test="$format-code = 'a' and $ordered-list-level = '1'">
                <xsl:text>)</xsl:text>
            </xsl:when>
            <xsl:otherwise>
                <xsl:text>.</xsl:text>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:copy>
        <xsl:attribute name="pi:ordered-list-level">
            <xsl:value-of select="$ordered-list-level"/>
        </xsl:attribute>
        <xsl:attribute name="pi:format-code">
            <xsl:value-of select="$format-code"/>
        </xsl:attribute>
        <xsl:attribute name="pi:marker-prefix">
            <xsl:value-of select="$marker-prefix"/>
        </xsl:attribute>
        <xsl:attribute name="pi:marker-suffix">
            <xsl:value-of select="$marker-suffix"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()|@*" mode="augment"/>
    </xsl:copy>
</xsl:template>

<!-- The stamp has served its purpose once the level is recorded, -->
<!-- so it does not survive into the assembled tree.              -->
<xsl:template match="@pi:legacy-parts" mode="augment"/>

<!-- List levels and format codes are ancestor-axis computations    -->
<!-- that *could* be made at conversion time in -common; we simply  -->
<!-- choose to make them once, here in assembly, and stamp the      -->
<!-- results.  Unordered lists follow the pattern of ordered lists  -->
<!-- above: an authored @marker wins, else defaults cycle by depth. -->

<!-- Labels of unordered lists have format codes:   -->
<!-- disc, circle, square, or none.  Default order: -->
<!-- disc, circle, square, disc.                    -->
<xsl:template match="ul" mode="format-code">
    <xsl:param name="level"/>
    <xsl:choose>
        <xsl:when test="@marker">
            <xsl:choose>
                <xsl:when test="@marker='disc'">disc</xsl:when>
                <xsl:when test="@marker='circle'">circle</xsl:when>
                <xsl:when test="@marker='square'">square</xsl:when>
                <xsl:when test="@marker=''">none</xsl:when>
                <xsl:otherwise>
                    <xsl:message>PTX:ERROR: unordered list label (<xsl:value-of select="@marker" />) not recognized</xsl:message>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:when>
        <xsl:otherwise>
            <xsl:choose>
                <xsl:when test="$level='0'">disc</xsl:when>
                <xsl:when test="$level='1'">circle</xsl:when>
                <xsl:when test="$level='2'">square</xsl:when>
                <xsl:when test="$level='3'">disc</xsl:when>
                <xsl:otherwise>
                    <xsl:message>PTX:ERROR: unordered list is more than 4 levels deep (at level <xsl:value-of select="$level" />)</xsl:message>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="ul" mode="augment">
    <xsl:variable name="format-code">
        <xsl:apply-templates select="." mode="format-code">
            <xsl:with-param name="level" select="count(ancestor::ul)"/>
        </xsl:apply-templates>
    </xsl:variable>
    <xsl:copy>
        <xsl:attribute name="pi:format-code">
            <xsl:value-of select="$format-code"/>
        </xsl:attribute>
        <xsl:apply-templates select="node()|@*" mode="augment"/>
    </xsl:copy>
</xsl:template>

<!-- ##################### -->
<!-- Serial Stamp Pass     -->
<!-- ##################### -->

<!-- Stamps @pi:serial during assembly, in one depth-first walk.  Each-->
<!-- numbered family (equations and footnotes) is threaded as a       -->
<!-- node-set: its items in the current counting scope, and each item -->
<!-- is stamped with its position in that node-set.  How a scope is   -->
<!-- chosen, and how the node-set is recomputed at each step, is      -->
<!-- documented with the division templates below.                    -->

<!-- The "next" node-set                                                    -->

<!-- A numbered family is counted within a "scope": a division whose own    -->
<!-- items form one flat, serially numbered pool.  A division opens a       -->
<!-- scope for a family when it is terminal (has no traditional             -->
<!-- subdivision) or has reached the family's numbering level.  A           -->
<!-- structural division above that level recurses instead, and the         -->
<!-- content it holds directly (its introduction and conclusion) pools      -->
<!-- at the division's own level.                                           -->

<!-- Each template threads one node-set per family (the family's items      -->
<!-- in the current scope) and hands its children the "next" one, built     -->
<!-- from the inherited node-set:                                           -->

<!--     $b-open = not($nodes) and ($b-terminal or @pi:level >= LEVEL)      -->
<!--     $next   = $nodes | self::*[$b-open]//ITEMS                         -->

<!-- This is three cases.  Already in a scope ($nodes non-empty): $b-open   -->
<!-- is false, so $next is $nodes and the scope threads down unchanged.     -->
<!-- Opening a scope here: $next is this division's pool of items.  Still   -->
<!-- above the level: $next is empty, the walk recurses, and a deeper       -->
<!-- division opens the scope.                                              -->

<!-- The self::*[$b-open] guard makes the descendant scan for the pool      -->
<!-- happen only at the division that opens the scope; while a scope        -->
<!-- merely threads down, $nodes is non-empty and nothing is scanned.       -->

<!-- The introduction/conclusion template (below) has the same shape,       -->
<!-- except its pool is the division's own introduction and conclusion      -->
<!-- (siblings, not a descendant scan), taken only where the parent         -->
<!-- recursed.                                                              -->

<!-- ITEMS (the family's items) and LEVEL (its numbering switch) are the    -->
<!-- only per-family inputs; $b-terminal and the case structure are shared. -->
<!-- Seven families ride this structure: equations ("-eq") and footnotes    -->
<!-- ("-fn") are single counters; the block families share the "blocks"     -->
<!-- counter unless figure-likes, projects, inline exercises, or open       -->
<!-- problems are set "distinct", each then opening its own counter.        -->
<xsl:template match="book|article|part|chapter|appendix|frontmatter|backmatter|preface|section|subsection|subsubsection|exercises|worksheet|handout|reading-questions|references|glossary|solutions" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <!-- Terminal: this division's items pool into one flat scope  -->
    <!-- here.  For a traditional division the authority is the    -->
    <!-- two-model test "is-structured-division": an unstructured  -->
    <!-- division (content, plus at most one of each specialized   -->
    <!-- division) is terminal, its specialized divisions pooling  -->
    <!-- into its scope; a structured division (traditional        -->
    <!-- subdivisions, or only worksheets) recurses, and each      -->
    <!-- worksheet then opens a scope apiece.  The test does not   -->
    <!-- apply to "frontmatter", "backmatter", a "preface", or the -->
    <!-- specialized divisions themselves, which keep the plain    -->
    <!-- child-division inspection.                                -->
    <xsl:variable name="terminal">
        <xsl:choose>
            <xsl:when test="self::book or self::article or self::part or self::chapter or self::appendix or self::section or self::subsection or self::subsubsection">
                <xsl:variable name="is-structured">
                    <xsl:apply-templates select="." mode="is-structured-division"/>
                </xsl:variable>
                <xsl:value-of select="$is-structured = 'false'"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="not(part|chapter|appendix|section|subsection|subsubsection|preface|exercises|worksheet|handout|reading-questions|references|glossary|solutions)"/>
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:variable name="b-terminal" select="$terminal = 'true'"/>
    <xsl:variable name="b-open-eq"          select="not($eq-nodes)          and ($b-terminal or (@pi:level &gt;= $numbering-equations))"/>
    <xsl:variable name="b-open-fn"          select="not($fn-nodes)          and ($b-terminal or (@pi:level &gt;= $numbering-footnotes))"/>
    <xsl:variable name="b-open-blocks"      select="not($blocks-nodes)      and ($b-terminal or (@pi:level &gt;= $numbering-blocks))"/>
    <xsl:variable name="b-open-figure"      select="$b-number-figure-distinct      and not($figure-nodes)      and ($b-terminal or (@pi:level &gt;= $numbering-figures))"/>
    <xsl:variable name="b-open-project"     select="$b-number-project-distinct     and not($project-nodes)     and ($b-terminal or (@pi:level &gt;= $numbering-projects))"/>
    <xsl:variable name="b-open-exercise"    select="$b-number-exercise-distinct    and not($exercise-nodes)    and ($b-terminal or (@pi:level &gt;= $numbering-exercises))"/>
    <xsl:variable name="b-open-openproblem" select="$b-number-openproblem-distinct and not($openproblem-nodes) and ($b-terminal or (@pi:level &gt;= $numbering-openproblems))"/>
    <xsl:variable name="next-eq" select="$eq-nodes | self::*[$b-open-eq]//mrow[@pi:numbered = 'yes']"/>
    <xsl:variable name="next-fn" select="$fn-nodes | self::*[$b-open-fn]//fn"/>
    <!-- The shared "blocks" pool also gathers figure-likes, projects,  -->
    <!-- inline exercises, and open problems that are not run distinct. -->
    <xsl:variable name="next-blocks" select="$blocks-nodes
        | self::*[$b-open-blocks]//*[&FUNDAMENTAL-BLOCK-FILTER;]
        | self::*[$b-open-blocks and not($b-number-figure-distinct)]//*[&TOP-FIGURE-FILTER;]
        | self::*[$b-open-blocks and not($b-number-project-distinct)]//*[&PROJECT-FILTER;]
        | self::*[$b-open-blocks and not($b-number-exercise-distinct)]//exercise[&INLINE-EXERCISE-FILTER;]
        | self::*[$b-open-blocks and not($b-number-openproblem-distinct)]//*[&OPENPROBLEM-FILTER;]"/>
    <xsl:variable name="next-figure"      select="$figure-nodes      | self::*[$b-open-figure]//*[&TOP-FIGURE-FILTER;]"/>
    <xsl:variable name="next-project"     select="$project-nodes     | self::*[$b-open-project]//*[&PROJECT-FILTER;]"/>
    <xsl:variable name="next-exercise"    select="$exercise-nodes    | self::*[$b-open-exercise]//exercise[&INLINE-EXERCISE-FILTER;]"/>
    <xsl:variable name="next-openproblem" select="$openproblem-nodes | self::*[$b-open-openproblem]//*[&OPENPROBLEM-FILTER;]"/>
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$next-eq"/>
            <xsl:with-param name="fn-nodes" select="$next-fn"/>
            <xsl:with-param name="blocks-nodes" select="$next-blocks"/>
            <xsl:with-param name="figure-nodes" select="$next-figure"/>
            <xsl:with-param name="project-nodes" select="$next-project"/>
            <xsl:with-param name="exercise-nodes" select="$next-exercise"/>
            <xsl:with-param name="openproblem-nodes" select="$next-openproblem"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Introduction and conclusion of a traditional division.  The match    -->
<!-- is prefixed with the division names because PreTeXt also uses        -->
<!-- introduction and conclusion inside exercises, exercisegroups,        -->
<!-- objectives, and the like.  This is the pooling case above: where the -->
<!-- parent recursed, the division's own introduction and conclusion pool -->
<!-- into one scope, subdivisions excluded for free (siblings, not        -->
<!-- descendants).                                                        -->
<xsl:template match="article/introduction | chapter/introduction | section/introduction | subsection/introduction | appendix/introduction | article/conclusion | chapter/conclusion | section/conclusion | subsection/conclusion | appendix/conclusion" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:variable name="next-eq" select="$eq-nodes | (../introduction | ../conclusion)[not($eq-nodes)]//mrow[@pi:numbered = 'yes']"/>
    <xsl:variable name="next-fn" select="$fn-nodes | (../introduction | ../conclusion)[not($fn-nodes)]//fn"/>
    <xsl:variable name="next-blocks" select="$blocks-nodes
        | (../introduction | ../conclusion)[not($blocks-nodes)]//*[&FUNDAMENTAL-BLOCK-FILTER;]
        | (../introduction | ../conclusion)[not($blocks-nodes) and not($b-number-figure-distinct)]//*[&TOP-FIGURE-FILTER;]
        | (../introduction | ../conclusion)[not($blocks-nodes) and not($b-number-project-distinct)]//*[&PROJECT-FILTER;]
        | (../introduction | ../conclusion)[not($blocks-nodes) and not($b-number-exercise-distinct)]//exercise[&INLINE-EXERCISE-FILTER;]
        | (../introduction | ../conclusion)[not($blocks-nodes) and not($b-number-openproblem-distinct)]//*[&OPENPROBLEM-FILTER;]"/>
    <xsl:variable name="next-figure"      select="$figure-nodes      | (../introduction | ../conclusion)[not($figure-nodes)      and $b-number-figure-distinct]//*[&TOP-FIGURE-FILTER;]"/>
    <xsl:variable name="next-project"     select="$project-nodes     | (../introduction | ../conclusion)[not($project-nodes)     and $b-number-project-distinct]//*[&PROJECT-FILTER;]"/>
    <xsl:variable name="next-exercise"    select="$exercise-nodes    | (../introduction | ../conclusion)[not($exercise-nodes)    and $b-number-exercise-distinct]//exercise[&INLINE-EXERCISE-FILTER;]"/>
    <xsl:variable name="next-openproblem" select="$openproblem-nodes | (../introduction | ../conclusion)[not($openproblem-nodes) and $b-number-openproblem-distinct]//*[&OPENPROBLEM-FILTER;]"/>
    <xsl:copy>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$next-eq"/>
            <xsl:with-param name="fn-nodes" select="$next-fn"/>
            <xsl:with-param name="blocks-nodes" select="$next-blocks"/>
            <xsl:with-param name="figure-nodes" select="$next-figure"/>
            <xsl:with-param name="project-nodes" select="$next-project"/>
            <xsl:with-param name="exercise-nodes" select="$next-exercise"/>
            <xsl:with-param name="openproblem-nodes" select="$next-openproblem"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Numbered mrow.  Stamp @pi:serial with the position within the -->
<!-- inherited node-set.                                           -->
<xsl:template match="mrow[@pi:numbered = 'yes']" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:copy>
        <xsl:attribute name="pi:serial">
            <xsl:apply-templates select="." mode="position-in-node-set">
                <xsl:with-param name="nodes" select="$eq-nodes"/>
            </xsl:apply-templates>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$eq-nodes"/>
            <xsl:with-param name="fn-nodes" select="$fn-nodes"/>
            <xsl:with-param name="blocks-nodes" select="$blocks-nodes"/>
            <xsl:with-param name="figure-nodes" select="$figure-nodes"/>
            <xsl:with-param name="project-nodes" select="$project-nodes"/>
            <xsl:with-param name="exercise-nodes" select="$exercise-nodes"/>
            <xsl:with-param name="openproblem-nodes" select="$openproblem-nodes"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Every fn is numbered.  Stamp @pi:serial with the position -->
<!-- within the inherited footnote node-set.                   -->
<xsl:template match="fn" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:copy>
        <xsl:attribute name="pi:serial">
            <xsl:apply-templates select="." mode="position-in-node-set">
                <xsl:with-param name="nodes" select="$fn-nodes"/>
            </xsl:apply-templates>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$eq-nodes"/>
            <xsl:with-param name="fn-nodes" select="$fn-nodes"/>
            <xsl:with-param name="blocks-nodes" select="$blocks-nodes"/>
            <xsl:with-param name="figure-nodes" select="$figure-nodes"/>
            <xsl:with-param name="project-nodes" select="$project-nodes"/>
            <xsl:with-param name="exercise-nodes" select="$exercise-nodes"/>
            <xsl:with-param name="openproblem-nodes" select="$openproblem-nodes"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Block stamps: serial from the group's distinct counter, else the shared "blocks". -->
<!-- A subnumbered side-by-side panel is not a "top" figure, so it falls to the         -->
<!-- catch-all (no @pi:serial) and earns a letter downstream instead.                   -->

<!-- Fundamental blocks always share the "blocks" counter. -->
<xsl:template match="&DEFINITION-LIKE;|&THEOREM-LIKE;|&AXIOM-LIKE;|&REMARK-LIKE;|&COMPUTATION-LIKE;|&EXAMPLE-LIKE;" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:copy>
        <xsl:attribute name="pi:serial">
            <xsl:apply-templates select="." mode="position-in-node-set">
                <xsl:with-param name="nodes" select="$blocks-nodes"/>
            </xsl:apply-templates>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$eq-nodes"/>
            <xsl:with-param name="fn-nodes" select="$fn-nodes"/>
            <xsl:with-param name="blocks-nodes" select="$blocks-nodes"/>
            <xsl:with-param name="figure-nodes" select="$figure-nodes"/>
            <xsl:with-param name="project-nodes" select="$project-nodes"/>
            <xsl:with-param name="exercise-nodes" select="$exercise-nodes"/>
            <xsl:with-param name="openproblem-nodes" select="$openproblem-nodes"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Projects: distinct counter when split out, else the "blocks" counter. -->
<xsl:template match="&PROJECT-LIKE;" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:copy>
        <xsl:attribute name="pi:serial">
            <xsl:apply-templates select="." mode="position-in-node-set">
                <xsl:with-param name="nodes" select="$project-nodes[$b-number-project-distinct] | $blocks-nodes[not($b-number-project-distinct)]"/>
            </xsl:apply-templates>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$eq-nodes"/>
            <xsl:with-param name="fn-nodes" select="$fn-nodes"/>
            <xsl:with-param name="blocks-nodes" select="$blocks-nodes"/>
            <xsl:with-param name="figure-nodes" select="$figure-nodes"/>
            <xsl:with-param name="project-nodes" select="$project-nodes"/>
            <xsl:with-param name="exercise-nodes" select="$exercise-nodes"/>
            <xsl:with-param name="openproblem-nodes" select="$openproblem-nodes"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Top-level figure-likes: distinct counter when split out, else "blocks". -->
<xsl:template match="*[&TOP-FIGURE-FILTER;]" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:copy>
        <xsl:attribute name="pi:serial">
            <xsl:apply-templates select="." mode="position-in-node-set">
                <xsl:with-param name="nodes" select="$figure-nodes[$b-number-figure-distinct] | $blocks-nodes[not($b-number-figure-distinct)]"/>
            </xsl:apply-templates>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$eq-nodes"/>
            <xsl:with-param name="fn-nodes" select="$fn-nodes"/>
            <xsl:with-param name="blocks-nodes" select="$blocks-nodes"/>
            <xsl:with-param name="figure-nodes" select="$figure-nodes"/>
            <xsl:with-param name="project-nodes" select="$project-nodes"/>
            <xsl:with-param name="exercise-nodes" select="$exercise-nodes"/>
            <xsl:with-param name="openproblem-nodes" select="$openproblem-nodes"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Inline exercises: distinct counter when split out, else "blocks". -->
<xsl:template match="exercise[&INLINE-EXERCISE-FILTER;]" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:copy>
        <xsl:attribute name="pi:serial">
            <xsl:apply-templates select="." mode="position-in-node-set">
                <xsl:with-param name="nodes" select="$exercise-nodes[$b-number-exercise-distinct] | $blocks-nodes[not($b-number-exercise-distinct)]"/>
            </xsl:apply-templates>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$eq-nodes"/>
            <xsl:with-param name="fn-nodes" select="$fn-nodes"/>
            <xsl:with-param name="blocks-nodes" select="$blocks-nodes"/>
            <xsl:with-param name="figure-nodes" select="$figure-nodes"/>
            <xsl:with-param name="project-nodes" select="$project-nodes"/>
            <xsl:with-param name="exercise-nodes" select="$exercise-nodes"/>
            <xsl:with-param name="openproblem-nodes" select="$openproblem-nodes"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- Open problems: distinct counter when split out, else "blocks". -->
<xsl:template match="&OPENPROBLEM-LIKE;" mode="serial-stamp">
    <xsl:param name="eq-nodes"/>
    <xsl:param name="fn-nodes"/>
    <xsl:param name="blocks-nodes"/>
    <xsl:param name="figure-nodes"/>
    <xsl:param name="project-nodes"/>
    <xsl:param name="exercise-nodes"/>
    <xsl:param name="openproblem-nodes"/>
    <xsl:copy>
        <xsl:attribute name="pi:serial">
            <xsl:apply-templates select="." mode="position-in-node-set">
                <xsl:with-param name="nodes" select="$openproblem-nodes[$b-number-openproblem-distinct] | $blocks-nodes[not($b-number-openproblem-distinct)]"/>
            </xsl:apply-templates>
        </xsl:attribute>
        <xsl:apply-templates select="@*|node()" mode="serial-stamp">
            <xsl:with-param name="eq-nodes" select="$eq-nodes"/>
            <xsl:with-param name="fn-nodes" select="$fn-nodes"/>
            <xsl:with-param name="blocks-nodes" select="$blocks-nodes"/>
            <xsl:with-param name="figure-nodes" select="$figure-nodes"/>
            <xsl:with-param name="project-nodes" select="$project-nodes"/>
            <xsl:with-param name="exercise-nodes" select="$exercise-nodes"/>
            <xsl:with-param name="openproblem-nodes" select="$openproblem-nodes"/>
        </xsl:apply-templates>
    </xsl:copy>
</xsl:template>

<!-- ############## -->
<!-- Serial Numbers -->
<!-- ############## -->

<!-- These templates count the occurences of an element within a       -->
<!-- subtree.  Sometimes that subtree is rooted just above the element -->
<!-- (e.g. divisions) or sometimes it is many levels higher, such as   -->
<!-- when an "example" might be in a "subsubsection" but they are      -->
<!-- grouped ("count within" in LaTeX-speak) and counted across all    -->
<!-- "example" within a chapter (ignoring the divisions by section,    -->
<!-- subsection, and subsubsection).                                   -->
<!--                                                                   -->
<!-- All of the hard work of counting is done in these templates.      -->
<!-- Elsewhere, serial numbers are combined with hierarchical          -->
<!-- numbering of divisions to form "full" numbers.                    -->

<!-- Traditional Divisions -->
<!-- Mostly obvious, counting peers, including specialized         -->
<!-- divisions.  Roman numerals for parts, letters for appendices. -->
<xsl:template match="part" mode="division-serial-number">
    <xsl:number format="I" />
</xsl:template>
<xsl:template match="chapter" mode="division-serial-number">
    <!-- chapters, in parts or not -->
    <xsl:choose>
        <xsl:when test="($parts = 'absent') or ($parts = 'decorative')">
            <xsl:variable name="true-count">
                <xsl:number from="book" level="any" count="chapter" format="1" />
            </xsl:variable>
            <!-- $chapter-start defaults to 1 -->
            <xsl:value-of select="$true-count + $chapter-start - 1" />
        </xsl:when>
        <!-- author-specified chapter start number does  -->
        <!-- not really make sense for structural parts? -->
        <xsl:when test="$parts = 'structural'">
            <xsl:number from="part" count="chapter" format="1" />
        </xsl:when>
    </xsl:choose>
</xsl:template>
<!-- A "solutions" is a specialized division, but is numbered -->
<!-- as an appendix when present in the backmatter, so        -->
<!-- included in the count here.                              -->
<xsl:template match="appendix" mode="division-serial-number">
    <xsl:number from="backmatter" level="any" count="appendix|solutions" format="A"/>
</xsl:template>
<!-- NB: following do not assume an ordering on the subdivisions,     -->
<!-- since this has not been solidified in the schema. At that point, -->
<!-- we might enforce some assumptions here, and elsewhere, by only   -->
<!-- including predecessors in the @count attribute.                  -->
<xsl:template match="section" mode="division-serial-number">
    <xsl:number count="section|exercises|reading-questions|solutions|references|glossary|worksheet|handout" format="1" />
</xsl:template>
<xsl:template match="subsection" mode="division-serial-number">
    <xsl:number count="subsection|exercises|reading-questions|solutions|references|glossary|worksheet|handout" format="1" />
</xsl:template>
<xsl:template match="subsubsection" mode="division-serial-number">
    <xsl:number count="subsubsection|exercises|reading-questions|solutions|references|glossary|worksheet|handout" format="1" />
</xsl:template>

<!-- Specialized Divisions -->
<!-- "exercises", "solutions", references, "worksheet",-->
<!-- "handout", "reading-questions", "glossary"        -->
<!-- This is the case of a "structured" division,      -->
<!-- where we use the resulting number for the         -->
<!-- division. (In the unstructured case, the number   -->
<!-- will be inherited from the parent, so this number -->
<!-- is incorrect, meaningless, and ignored.)  So we   -->
<!-- simply count preceding peers.  Note that every    -->
<!-- possible traditional division that could be a     -->
<!-- peer is listed here in the "match", but only one  -->
<!-- type will actually be present in the structured   -->
<!-- division.                                         -->
<xsl:template match="exercises|reading-questions|solutions|references|glossary|worksheet|handout" mode="division-serial-number">
    <xsl:number count="chapter|section|subsection|subsubsection|exercises|reading-questions|solutions|references|glossary|worksheet|handout" format="1" />
</xsl:template>
<!-- Following "backmatter" matches will be more specific than above -->
<!-- A "solutions" is a specialized division, but is numbered        -->
<!-- as an appendix when present in the backmatter, see above        -->
<xsl:template match="backmatter/solutions" mode="division-serial-number">
    <xsl:number from="backmatter" level="any" count="appendix|solutions" format="A"/>
</xsl:template>

<!-- ######################### -->
<!-- Structured vs. Decorative -->
<!-- ######################### -->

<!-- These templates determine the structure of divisions, needed    -->
<!-- during assembly to compute block structure numbers.  They are   -->
<!-- also used at render time from pretext-common.xsl and elsewhere, -->
<!-- available via cross-import resolution (every conversion         -->
<!-- stylesheet imports both pretext-assembly.xsl and                -->
<!-- pretext-common.xsl).                                            -->

<!-- There are two models for most of the divisions (part -->
<!-- through subsubsection, plus appendix).  One has      -->
<!-- subdivisions, and possibly specialized subdivisions. -->
<!-- The other has no subdivisions, and then at most one  -->
<!-- of each type of specialized subdivision, which       -->
<!-- inherit numbers from their parent division. This is  -->
<!-- the test, which is very similar to "is-leaf" in      -->
<!-- pretext-common.xsl.                                  -->
<!--                                                      -->
<!-- A "part" must have chapters, so will always return   -->
<!-- 'true' and for a 'subsubsection' there are no more   -->
<!-- subdivisions to employ and so will return empty.     -->
<!--                                                      -->
<!-- An exception is a division of *only* worksheets.     -->
<!-- Although there could be titles and the like.         -->
<!-- So we compare all-children to  metadata + worksheet. -->
<!-- TODO: should there be a similar exception for handouts? -->
<xsl:template match="book|article|part|chapter|appendix|section|subsection|subsubsection" mode="is-structured-division">
    <xsl:variable name="has-traditional" select="boolean(&TRADITIONAL-DIVISION;)"/>
    <xsl:variable name="all-children" select="*"/>
    <xsl:variable name="all-worksheet" select="title|subtitle|shorttitle|plaintitle|idx|frontmatter|introduction|worksheet|handout|conclusion"/>
    <xsl:variable name="only-worksheets" select="count($all-children) = count($all-worksheet)"/>

    <xsl:value-of select="$has-traditional or $only-worksheets"/>
</xsl:template>

<xsl:template match="*" mode="is-structured-division">
    <xsl:message>PTX:BUG: asking if a non-traditional division (<xsl:value-of select="local-name(.)"/>) is structured or not</xsl:message>
</xsl:template>

<!-- Specialized divisions sometimes inherit a number from their  -->
<!-- parent (as part of an unstructured division) and sometimes   -->
<!-- they do not even have a number (singleton "references" as    -->
<!-- child of "backmatter").  This template returns "true" if a   -->
<!-- specialized division "owns" its "own" number.                -->
<xsl:template match="exercises|worksheet|handout|references|glossary|reading-questions|solutions" mode="is-specialized-own-number">
    <xsl:choose>
        <!-- *Some* specialized divisions can appear as a child of the    -->
        <!-- "backmatter" too.  But only those below.  The rest are       -->
        <!-- banned as top-level items in the backmatter, but might       -->
        <!-- occur in an "appendix" or below, with or without structure.  -->
        <!--   "solutions" will look like an appendix, thus numbered.     -->
        <!--   "references" or "glossary" are singletons, never numbered. -->
        <xsl:when test="parent::*[self::backmatter]">
            <xsl:choose>
                <xsl:when test="self::solutions">
                    <xsl:text>true</xsl:text>
                </xsl:when>
                <xsl:when test="self::references or self::glossary">
                    <xsl:text>false</xsl:text>
                </xsl:when>
                <xsl:otherwise>
                    <xsl:message>PTX:ERROR:   encountered a specialized division ("<xsl:value-of select="local-name(.)"/>") as a child of "backmatter" that was unexpected.  Results will be unpredictable</xsl:message>
                    <!-- no idea if we should say true or false here -->
                    <xsl:text>true</xsl:text>
                </xsl:otherwise>
            </xsl:choose>
        </xsl:when>
        <!-- parent must now be a "traditional" division -->
        <xsl:otherwise>
            <xsl:apply-templates select="parent::*" mode="is-structured-division"/>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<xsl:template match="*" mode="is-specialized-own-number">
    <xsl:message>PTX:BUG: asking if a non-specialized division (<xsl:value-of select="local-name(.)"/>) is numbered or not</xsl:message>
    <xsl:text>false</xsl:text>
</xsl:template>

<!-- ######################## -->
<!-- Position In a Node-Set   -->
<!-- ######################## -->

<!-- Returns the document-order position (1-indexed) of the matched -->
<!-- node within the given node-set.  Uses the union-count identity -->
<!-- test: count(.|$here) = 1 iff the two are the same node.        -->
<!-- Empty result if the node is not in the set.                    -->
<!--                                                                -->
<!-- Used by the serial-stamp passes to find a node's position -->
<!-- within a precomputed node-set of its countable peers      -->
<!-- (equations, footnotes, and the block families).           -->
<xsl:template match="*" mode="position-in-node-set">
    <xsl:param name="nodes"/>
    <!-- Save off the context node before the "for-each" switches it. -->
    <xsl:variable name="here" select="."/>
    <xsl:for-each select="$nodes">
        <xsl:if test="count(.|$here) = 1">
            <xsl:value-of select="position()"/>
        </xsl:if>
    </xsl:for-each>
</xsl:template>

<!-- ########### -->
<!-- Assembly ID -->
<!-- ########### -->

<!-- Some maniulations of source require stable identification *before*     -->
<!-- we assign @pi:unique-id values for general use in the very late        -->
<!-- "identification" phase.  This is a role for the "@pi:assembly-id" which-->
<!-- is formed after the author's source has been versioned, customized,    -->
<!-- repaired, but before replacements. It should suffice for "big" objects -->
<!-- which are unlikely to change much (other than going away in a version) -->
<!-- and may only be "repaired" in a one-to-one cosmetic rename.  We use    -->
<!-- this sparingly, thus we are careful about the match, along with        -->
<!-- documenting rationale for each object.                                 -->
<!--                                                                        -->
<!-- Another way to think about this is as an "early" id, versus the        -->
<!-- more general "late" id.                                                -->
<!--                                                                        -->
<!-- audio|video|interactive                                                -->
<!--     Static versions of these interactive elements have previews        -->
<!--     (YouTube thumbnails, automatically generated screenshots),         -->
<!--     generated QR codes, and various links meant for use in static      -->
<!--     contexts.  So we form names of these related objects based on      -->
<!--     an "earlier" id.                                                   -->
<!--                                                                        -->
<!-- datafile                                                               -->
<!--     For static versions of this Runestone component, when the file is  -->
<!--     a text file provided in the external directory, we need to         -->
<!--     interrogate the file manufactured in the generated directory in    -->
<!--     order to make a sample of its content.  This happens before we     -->
<!--     construct unique-id.                                               -->

<!-- NB: we believe the @pi:assembly-id will equal the @pi:unique-id -->
<!-- ("unique-id" template) for objects at the level of blocks,      -->
<!-- and certainly for any object replaced by a different static     -->
<!-- representation.  But for generated objects, e.g. QR codes,      -->
<!-- it would be best if the generation process used the             -->
<!-- "assembly-id" template for guranteed consistency.  This *is*    -->
<!-- being done for "datafile" but is technical debt otherwise.      -->

<!-- [Ed. this once prefaced an early, weak forerunner of the         -->
<!-- "assembly-id" template.  But the commentary                      -->
<!-- is still good, so we have preserved it here.]                    -->
<!-- This template produces identification that happens early in the  -->
<!-- passes this stylesheet executes.  The idea is that some elements -->
<!-- get replaced wholesale (such as an "interactive" being replaced  -->
<!-- by a "sidebyside" in the creation of a static precursor.  But we -->
<!-- want these ids, especially if automatic, to be consistent when   -->
<!-- used in derived versions (such as manufacturing, or displaying,  -->
<!-- a QR code file for a static "interactive").                      -->
<!-- NB: this template needs to be defined in this stylesheet, since  -->
<!-- we want the stylesheet to be independent, and the template is    -->
<!-- also applied here.                                               -->

<!-- Every element carrying an "@pi:assembly-id" stamp reports it  -->
<!-- through this single template.  The match list is deliberately -->
<!-- explicit — media and images, data files, STACK problems, and  -->
<!-- the fill-in dynamic-substitution owners (the exercise-like    -->
<!-- elements, or a "task" they contain, whichever holds "setup")  -->
<!-- — so the BUG fallback below can catch an unexpected           -->
<!-- application.                                                  -->
<xsl:template match="audio|video|interactive|image
                   | datafile
                   | exercise/stack
                   | exercise[@pi:exercise-interactive='fillin' and setup]
                   | project[@pi:exercise-interactive='fillin' and setup]
                   | activity[@pi:exercise-interactive='fillin' and setup]
                   | exploration[@pi:exercise-interactive='fillin' and setup]
                   | investigation[@pi:exercise-interactive='fillin' and setup]
                   | exercise[.//task and .//task/@pi:exercise-interactive='fillin' and .//setup]
                   | project[.//task and .//task/@pi:exercise-interactive='fillin' and .//setup]
                   | activity[.//task and .//task/@pi:exercise-interactive='fillin' and .//setup]
                   | exploration[.//task and .//task/@pi:exercise-interactive='fillin' and .//setup]
                   | investigation[.//task and .//task/@pi:exercise-interactive='fillin' and .//setup]
                   | exercise//task[@pi:exercise-interactive='fillin' and setup]
                   | project//task[@pi:exercise-interactive='fillin' and setup]
                   | activity//task[@pi:exercise-interactive='fillin' and setup]
                   | exploration//task[@pi:exercise-interactive='fillin' and setup]
                   | investigation//task[@pi:exercise-interactive='fillin' and setup]" mode="assembly-id">
    <xsl:value-of select="@pi:assembly-id"/>
</xsl:template>

<xsl:template match="*" mode="assembly-id">
    <xsl:message>
        <xsl:text>PTX:BUG:  the "assembly-id" template was applied to an element it did not expect--</xsl:text>
        <xsl:value-of select="name()"/>
        <xsl:text>.</xsl:text>
        <xsl:value-of select="@pi:exercise-interactive"/>
        <xsl:text>.</xsl:text>
        <xsl:value-of select="@pi:assembly-id"/>
    </xsl:message>
    <xsl:text>unexpected-assembly-id-template-use-here</xsl:text>
</xsl:template>

<!-- ###################################### -->
<!-- Static versions of Interactive Content -->
<!-- ###################################### -->

<!-- Templates for the pre-processor (and other stylesheets) to use -->
<!-- for the creation of static versions of interactive content.    -->

<!-- The HTML conversion generates "standalone" pages for videos   -->
<!-- and other interactives.  Then the LaTeX conversion will make  -->
<!-- links to these pages (eg, via QR codes).  And we might use    -->
<!-- these pages as the basis for scraping preview images.  So we  -->
<!-- place a template here to achieve consistency across uses.     -->
<!--                                                               -->
<!-- We need to always import this assembly stylesheet, so these   -->
<!-- templates will be available in all conversions, but notably   -->
<!-- in the creation of a "universal" static version of the        -->
<!-- document ("assembly-static" in the pretext/pretext script)    -->
<!-- which is fed to specific conversion into static output        -->
<!-- formats (e.g. LaTeX, braille).  As such, these templates      -->
<!-- should                                                        -->
<!--   (a) be applied someplace as part of the assembly process    -->
<!--   (b) produce only text (i.e. not XML, not HTML, not LaTeX)   -->


<!-- NB: it could be tempting to change the next template to stuff -->
<!-- these "iframe" files into a dedicated directory.  Even though -->
<!-- this template ensures some consistency, a pile of links still -->
<!-- need to change, such as the "script" tag for locations of     -->
<!-- extra JS as part of making one of these go.                   -->
<xsl:template match="audio|video|interactive" mode="iframe-filename">
    <xsl:apply-templates select="." mode="assembly-id" />
    <xsl:text>-if.html</xsl:text>
</xsl:template>

<xsl:template match="audio|video|interactive" mode="standalone-filename">
    <xsl:apply-templates select="." mode="assembly-id" />
    <xsl:text>.html</xsl:text>
</xsl:template>

<xsl:template match="exercise[@pi:exercise-interactive='fillin' and setup]
                   | project[@pi:exercise-interactive='fillin' and setup]
                   | activity[@pi:exercise-interactive='fillin' and setup]
                   | exploration[@pi:exercise-interactive='fillin' and setup]
                   | investigation[@pi:exercise-interactive='fillin' and setup]"
                   mode="standalone-filename">
    <xsl:apply-templates select="." mode="assembly-id" />
    <xsl:text>.html</xsl:text>
</xsl:template>

<xsl:template match="exercise[.//task/@pi:exercise-interactive='fillin' and .//task/setup]
                   | project[.//task/@pi:exercise-interactive='fillin' and .//task/setup]
                   | activity[.//task/@pi:exercise-interactive='fillin' and .//task/setup]
                   | exploration[.//task/@pi:exercise-interactive='fillin' and .//task/setup]
                   | investigation[.//task/@pi:exercise-interactive='fillin' and .//task/setup]"
                   mode="standalone-filename">
    <xsl:apply-templates select="." mode="assembly-id" />
    <xsl:text>.html</xsl:text>
</xsl:template>

<xsl:template match="*" mode="standalone-filename">
    <xsl:apply-templates select="." mode="unique-id" />
    <xsl:text>-ERROR-no-standalone-filename.html</xsl:text>
</xsl:template>

<xsl:template match="audio|video|interactive" mode="standalone-url">
    <xsl:if test="$b-has-baseurl">
        <xsl:value-of select="$baseurl"/>
        <xsl:apply-templates select="." mode="standalone-filename" />
    </xsl:if>
    <!-- empty without a baseurl -->
</xsl:template>

<xsl:template match="audio|video|interactive" mode="embed-iframe-url">
    <xsl:if test="$b-has-baseurl">
        <xsl:value-of select="$baseurl"/>
        <xsl:apply-templates select="." mode="iframe-filename" />
    </xsl:if>
    <!-- empty without a baseurl -->
</xsl:template>

<!-- These interactives *are* iFrames, so we don't build a dedicated   -->
<!-- page to make them into iFrames.  Over in -html we construct a URL -->
<!-- for each one, embedded in a iFrame construction.  We need to work -->
<!-- out the right thing to do for an "Embed" link in static formats.  -->
<!-- For now, an empty result means no link in sttic formats.          -->
<!-- NB: coordinate with "create-iframe-page" in -html                 -->
<xsl:template match="audio|video"  mode="embed-iframe-url"/>
<xsl:template match="interactive[@desmos|@geogebra|@calcplot3d|@circuitjs|@iframe]"  mode="embed-iframe-url"/>


<!-- Static URL's -->
<!-- Predictable and/or stable URLs for versions         -->
<!-- of interactives available online.  These are        -->
<!--                                                     -->
<!--   (1) "standalone" pages for author/local material, -->
<!--       as a product of the HTML conversion           -->
<!--   (2) computable addresses of network resources,    -->
<!--       eg the YouTube page of a resource             -->

<!-- Point to HTML-produced, and canonically-hosted, standalone page -->
<!-- NB: baseurl is assumed to have a trailing slash                 -->

<xsl:template match="audio[@source|@href]|video[@source|@href]|interactive" mode="static-url">
    <xsl:value-of select="$baseurl"/>
    <xsl:apply-templates select="." mode="standalone-filename" />
</xsl:template>

<!-- Natural override for YouTube videos               -->
<!-- Better - standalone page, with "View on You Tube" -->

<!-- NB: this is a genuine URL, with no escaping: consumers apply     -->
<!-- their own (the LaTeX conversion escapes the ampersand for its    -->
<!-- "\href", HTML-derived conversions use it as-is, and the QR code  -->
<!-- generator must encode exactly what a phone's camera should see). -->

<xsl:template match="video[@youtube|@youtubeplaylist]" mode="static-url">
    <xsl:apply-templates select="." mode="youtube-view-url" />
    <xsl:if test="@start">
        <xsl:text>&amp;start=</xsl:text>
        <xsl:value-of select="@start" />
    </xsl:if>
    <xsl:if test="@end">
        <xsl:text>&amp;end=</xsl:text>
        <xsl:value-of select="@end" />
    </xsl:if>
</xsl:template>

<xsl:template match="video[@youtube|@youtubeplaylist]" mode="youtube-view-url">
    <xsl:variable name="youtube">
        <xsl:choose>
            <xsl:when test="@youtubeplaylist">
                <xsl:value-of select="normalize-space(@youtubeplaylist)"/>
            </xsl:when>
            <xsl:otherwise>
                <xsl:value-of select="normalize-space(str:replace(@youtube, ',', ' '))" />
            </xsl:otherwise>
        </xsl:choose>
    </xsl:variable>
    <xsl:text>https://www.youtube.com/</xsl:text>
    <xsl:choose>
        <xsl:when test="@youtubeplaylist">
            <xsl:text>playlist?list=</xsl:text>
            <xsl:value-of select="$youtube" />
        </xsl:when>
        <xsl:when test="contains($youtube, ' ')">
            <xsl:text>watch_videos?video_ids=</xsl:text>
            <xsl:value-of select="str:replace($youtube, ' ', ',')" />
        </xsl:when>
        <xsl:otherwise>
            <xsl:text>watch?v=</xsl:text>
            <xsl:value-of select="$youtube" />
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

<!-- Vimeo view URL -->
<xsl:template match="video[@vimeo]" mode="static-url">
    <xsl:text>https://vimeo.com/</xsl:text>
    <xsl:value-of select="@vimeo"/>
</xsl:template>

<!-- A bit different than above, but same mode -->
<!-- When a "datafile" is produced in a static -->
<!-- context, then we append the $baseurl, and -->
<!-- provide the external directory.           -->
<xsl:template match="dataurl[@source]" mode="static-url">
    <xsl:value-of select="$baseurl"/>
    <!-- empty when not using managed directories -->
    <xsl:value-of select="$external-directory"/>
    <xsl:apply-templates select="@source" />
</xsl:template>

<!-- The contents of a datafile may be encoded as text in an XML   -->
<!-- file within the generated/datafile directory.  The filename   -->
<!-- has this construction, even if we do not always consult it.   -->
<!-- NB: these XML files will be read with a "document()" call,    -->
<!-- with a path relative to the author's main source file, hence  -->
<!-- the filename uses the directory name in author's source.      -->
<!-- NB: identical code in static constructions.                   -->
<xsl:template match="datafile" mode="datafile-filename">
    <xsl:value-of select="$generated-directory-source"/>
    <xsl:text>datafile/</xsl:text>
    <!-- context is "datafile", the basis for identifier -->
    <!-- ned an early identifier in assembly phase       -->
    <xsl:apply-templates select="." mode="assembly-id"/>
    <xsl:text>.xml</xsl:text>
</xsl:template>

<!-- The actual text contents of a "datafile", specified in a "pre" element.  -->
<!-- We assume (enforce) a "pre" child.  Then actual text comes authored in   -->
<!-- the source "pre" element or in an author-provided external file.         -->
<xsl:template match="datafile[pre]" mode="datafile-text-contents">
    <xsl:choose>
        <!-- via an external file -->
        <!-- Once upon a time, we hit the text from a file with   -->
        <!-- "sanitize-text".  This was a bad idea because        -->
        <!--   (a) the manipulations (especially pad-length (?) ) -->
        <!--       caused a false infinite recursion warning, and -->
        <!--   (b) the file should be *exactly* what is desired.  -->
        <xsl:when test="pre/@source">
            <!-- filename is relative to author's source -->
            <xsl:variable name="data-filename">
                <xsl:apply-templates select="."  mode="datafile-filename"/>
            </xsl:variable>
            <xsl:variable name="text-file-elt" select="document($data-filename, $original)/pi:text-file"/>
            <xsl:value-of select="$text-file-elt"/>
        </xsl:when>
        <!-- via source "pre" element content -->
        <xsl:otherwise>
            <xsl:call-template name="sanitize-text">
                <xsl:with-param name="text">
                    <xsl:value-of select="pre"/>
                </xsl:with-param>
            </xsl:call-template>
        </xsl:otherwise>
    </xsl:choose>
</xsl:template>

</xsl:stylesheet>
