tame/src/current/include/preproc/template.xsl

1574 lines
50 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!--
Performs template processing and expansion
Copyright (C) 2014-2023 Ryan Specialty, LLC.
This file is part of TAME.
TAME 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 3 of the License, or
(at your option) any later version.
This program 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 this program. If not, see
<http://www.gnu.org/licenses/>.
-->
<stylesheet version="2.0"
xmlns="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:preproc="http://www.lovullo.com/rater/preproc"
xmlns:lv="http://www.lovullo.com/rater"
xmlns:t="http://www.lovullo.com/rater/apply-template"
xmlns:c="http://www.lovullo.com/calc"
xmlns:ext="http://www.lovullo.com/ext"
xmlns:util="http://www.lovullo.com/util"
xmlns:eseq="http://www.lovullo.com/tame/preproc/expand/eseq"
exclude-result-prefixes="xs ext eseq lv util">
<import href="../../tame/src/preproc/expand/expand-sequence.xsl" />
<!--
Macro that abstracts away the boilerplate template application code when
dealing with a single root template
This macro will cause another macro expansion pass.
-->
<template match="lv:rate-each-template" mode="preproc:macros" priority="5">
<!-- TODO: debug flag
<message>
<text>[preproc] expanding rate-each-template </text>
<value-of select="@yields" />
<text>...</text>
</message>
-->
<lv:rate-each>
<!-- copy all attributes except for those we will explicitly handle below -->
<sequence select="@*[
not( local-name() = 'name' )
]" />
<!-- since the entire body is composed from a template, we do not need to
accept an index from the user; traditionally, we use 'k' to leave
indexes like 'i' and 'j' open -->
<variable name="index">
<text>k</text>
</variable>
<attribute name="index">
<value-of select="$index" />
</attribute>
<!-- any lv:* nodes should be placed at the top -->
<sequence select="./lv:class" />
<!-- the template is expected to have an @index@ parameter -->
<lv:apply-template name="{@name}">
<lv:with-param name="@index@" value="{$index}" />
<!-- copy any user-provided params into the template application -->
<sequence select="./lv:with-param" />
</lv:apply-template>
</lv:rate-each>
<!-- since we generated another preprocessor macro (lv:rate-each), notify the
system that we will need to make another pass -->
<preproc:repass tpl="{@name}" />
</template>
<!--
If the symbol table is not yet available, defer short-hand
applications for now
It is far less costly to leave the node alone than it is to process
and copy potentially massive trees only to have to repass anyway.
-->
<template mode="preproc:macros" priority="9"
match="t:*[ not( root(.)/preproc:symtable ) ]">
<sequence select="." />
<preproc:repass need-sym="true" />
</template>
<!--
Converts shorthand template applications into full template expansions
TODO: This causes an extra pass; we'd like to avoid having to do that.
-->
<template match="t:*" mode="preproc:macros" priority="5">
<!-- TODO: debug flag
<message>
<text>[preproc] expanding template shorthand for </text>
<value-of select="local-name()" />
<text>...</text>
</message>
-->
<variable name="name" as="xs:string"
select="concat( '_', local-name(), '_' )" />
<variable name="params" as="element( lv:with-param )*">
<for-each select="@*">
<lv:with-param name="@{local-name()}@" value="{.}" />
</for-each>
<!-- there may be existing lv:with-param nodes -->
<sequence select="./lv:with-param" />
<variable name="paramnodes"
select="*[ not( local-name() = 'with-param' ) ]" />
<!-- if sub-nodes were provided, pass it as the "@values@" param -->
<if test="$paramnodes">
<lv:with-param name="@values@">
<sequence select="$paramnodes" />
</lv:with-param>
</if>
</variable>
<!-- XXX: a large chunk of this is duplicate code; factor out -->
<variable name="tpl" as="element( lv:template )?"
select="preproc:locate-template( $name, root( . ) )" />
<variable name="src-root" as="element( lv:package )"
select="if ( root(.)/lv:package ) then
root(.)/lv:package
else
root(.)" />
<choose>
<when test="$tpl">
<!-- avoid a costly repass; apply immediately -->
<sequence select="preproc:expand-template(
$tpl, $src-root, $params, . )" />
</when>
<otherwise>
<preproc:error>Undefined template <value-of select="$name" /></preproc:error>
</otherwise>
</choose>
</template>
<function name="preproc:locate-template"
as="element( lv:template )?">
<param name="name" as="xs:string" />
<param name="root" as="element( lv:package )" />
<variable name="sym" as="element( preproc:sym )?"
select="$root/preproc:symtable/preproc:sym[
@name = $name ]" />
<variable name="package" as="element( lv:package )?"
select="if ( $sym/@src ) then
document( concat( $sym/@src, '.xmlo' ), $__entry-root )
/lv:package
else
$root/lv:package" />
<!-- legacy lookup for inline-templates; needs to regenerate symbol
list, then this can be removed -->
<variable name="inline-match" as="element( lv:template )?"
select="$root//lv:template[ @name = $name ]" />
<sequence select="if ( $inline-match ) then
$inline-match
else
$package//lv:template[ @name=$name ]" />
</function>
<!--
Applies a template in place as if it were pasted directly into the XML in that
location
This works much like a C macro and is intended to be a very simple means of
code re-use that permits free construction of lv:rate blocks. See XSD
documentation.
It is *very important* that these macros be expanded before the templates
themselves are removed from the XML by further processing.
Note that if the attribute shorthand is used for params, one extra expansion
will have to occur, which is an additional performance hit.
-->
<template match="lv:apply-template[
@name=root(.)/preproc:symtable/preproc:sym/@name ]
|lv:apply-template[ @name=root(.)//lv:template/@name ]"
mode="preproc:macros" priority="6">
<variable name="name" select="@name" />
<variable name="attrparams" select="@*[ not( local-name() = 'name' ) ]" />
<!-- used for type checking -->
<variable name="root" as="element( lv:package )"
select="if ( root( . ) instance of document-node() ) then
root( . )/lv:package
else
root( . )" />
<variable name="src-root" as="element( lv:package )"
select="if ( $root/lv:package ) then
$root/lv:package
else
$root" />
<variable name="tpl" as="element( lv:template )?"
select="preproc:locate-template( $name, $root )" />
<choose>
<when test="exists( $tpl ) and $attrparams">
<message>
<text>[preproc] expanding template parameters for </text>
<value-of select="@name" />
<text>...</text>
</message>
<variable name="params" as="element( lv:with-param )*">
<for-each select="$attrparams">
<lv:with-param name="@{local-name()}@" value="{.}" />
</for-each>
<!-- there may also be non-shorthand params -->
<sequence select="lv:with-param" />
</variable>
<!-- immediately apply without a wasteful repass -->
<sequence select="preproc:expand-template(
$tpl, $src-root, $params, . )" />
</when>
<when test="$tpl">
<sequence select="preproc:expand-template(
$tpl, $src-root, lv:with-param, . )" />
</when>
<otherwise>
<preproc:error>Undefined template <value-of select="$name" /></preproc:error>
</otherwise>
</choose>
</template>
<function name="preproc:expand-template">
<param name="tpl" as="element( lv:template )" />
<param name="src-root" />
<param name="params" as="element( lv:with-param )*" />
<param name="context" as="node()" />
<variable name="name" as="xs:string"
select="$tpl/@name" />
<!-- TODO: debug flag
<message>
<text>[preproc] applying template </text>
<value-of select="@name" />
<text>...</text>
</message>
-->
<!-- check to ensure that each given argument is defined, unless @strict
is false -->
<for-each select="
$params[
not( @name=$tpl/lv:param/@name )
and not( @strict='false' )
]
">
<preproc:error>
<text>undefined template param </text>
<value-of select="$name" />/<value-of select="@name" />
<text>; available params are </text>
<for-each select="$tpl/lv:param/@name">
<if test="position() > 1">
<text>; </text>
</if>
<value-of select="." />
</for-each>
</preproc:error>
</for-each>
<!-- replace this node with a copy of all the child nodes of the given
template; this inlines it as if it were copied and pasted directly
into the XML, much like a C macro -->
<variable name="apply-result">
<preproc:tpl-barrier>
<apply-templates
select="$tpl[ 1 ]/*"
mode="preproc:apply-template">
<with-param name="apply" select="$context"
tunnel="yes" />
<with-param name="tpl" select="$tpl[ 1 ]"
tunnel="yes" />
<with-param name="apply-tpl-name" select="$name"
tunnel="yes" />
<with-param name="params" select="$params"
tunnel="yes" />
<with-param name="first-child" select="true()" />
<with-param name="src-root" select="$src-root"
tunnel="yes" />
</apply-templates>
</preproc:tpl-barrier>
</variable>
<apply-templates mode="preproc:mark-tpl-expansion"
select="$apply-result">
<with-param name="tpl-name" select="$name"
tunnel="yes" />
</apply-templates>
<!-- since templates can include anything, we should perform another pass
to be sure that all macros that this template may contain/generate
are expanded -->
<preproc:repass tpl="{$name}" />
<preproc:tpl-step name="{$name}" type="apply-template" />
</function>
<!--
Strip tpl barriers, which serve to scope metadata.
-->
<template mode="preproc:strip-tpl-cruft" priority="5"
match="preproc:tpl-barrier">
<apply-templates mode="preproc:strip-tpl-cruft"
select="node()" />
</template>
<!--
Everything else stays.
-->
<template mode="preproc:strip-tpl-cruft" priority="3"
match="node()">
<copy>
<sequence select="@*" />
<apply-templates mode="preproc:strip-tpl-cruft"
select="node()" />
</copy>
</template>
<!--
Add nodes describing where the parent node came from.
This helps in debugging and understanding code created by templates.
This isn't the best way, but e.g. enclosing all expanded nodes in a parent
tag causes problems with the rest of the system. Adding an attribute to
the node is an option, but this implementation allows a node to be marked
by multiple templates, should such a thing ever occur.
-->
<template mode="preproc:mark-tpl-expansion" priority="5"
match="element()">
<param name="tpl-name" tunnel="yes" />
<copy>
<sequence select="@*" />
<preproc:from-template name="{$tpl-name}" />
<sequence select="node()" />
</copy>
</template>
<template mode="preproc:mark-tpl-expansion" priority="3"
match="node()|attribute()">
<sequence select="." />
</template>
<!--
Inline templates depending on the symbol table must not be expanded
until the symbol table is actually available
-->
<template mode="preproc:macros" priority="6"
match="lv:inline-template[ lv:for-each/lv:sym-set
and not( root(.)/preproc:symtable ) ]">
<lv:expand-sequence>
<sequence select="." />
</lv:expand-sequence>
<message select="'[preproc] deferring inline-template waiting for symbol table'" />
</template>
<!--
An inline template implicitly defines and then immediately applies a template
with an optional looping construct
-->
<template match="lv:inline-template" mode="preproc:macros" priority="5">
<variable name="name" select="concat( '___i', generate-id(.), '___' )" />
<variable name="inline" select="." />
<!-- generate template -->
<lv:template name="{$name}" desc="Inline template"
preproc:from-inline="{$name}"
preproc:generated="true">
<!-- generate params (from both our own attrs and any for-each sets) -->
<variable name="params">
<preproc:params>
<for-each select="@*, lv:for-each/lv:set/@*">
<!-- manual lv:param's will override default param generation -->
<if test="not( local-name() = $inline/lv:param/@name )">
<lv:param name="@{local-name()}@" desc="Generated param" />
</if>
</for-each>
<if test="lv:for-each/lv:sym-set">
<lv:param name="@sym_name@" desc="Symbol name" />
<lv:param name="@sym_type@" desc="Symbol type" />
<lv:param name="@sym_dim@" desc="Symbol degree (dimensions)" />
<lv:param name="@sym_desc@" desc="Symbol description (if any)" />
<lv:param name="@sym_yields@" desc="Symbol yield name (if any)" />
<lv:param name="@sym_parent@" desc="Symbol parent (if any)" />
<lv:param name="@sym_external@" desc="Symbol external to classifier" />
</if>
</preproc:params>
</variable>
<!-- use only unique params from each attribute source -->
<sequence select="
$params//lv:param[
not( @name=preceding-sibling::lv:param/@name )
]
" />
<!-- include any params priovided by the user (they may be used to
generate content) -->
<sequence select="lv:param" />
<!-- copy template body -->
<!-- FIXME: do not use local-name here; use instance of -->
<sequence select="*[ not( local-name() = 'for-each' ) ]" />
</lv:template>
<choose>
<when test="lv:for-each">
<apply-templates select="lv:for-each/lv:*" mode="preproc:inline-apply">
<with-param name="name" select="$name" />
</apply-templates>
</when>
<otherwise>
<apply-templates select="." mode="preproc:inline-apply">
<with-param name="name" select="$name" />
</apply-templates>
</otherwise>
</choose>
<!-- the next pass will perform the template application -->
<preproc:repass tpl="{$name}" />
</template>
<template match="lv:inline-template|lv:for-each/lv:set" mode="preproc:inline-apply">
<param name="name" />
<!-- immediately apply the template -->
<lv:apply-template name="{$name}">
<!-- each attribute will be considered to be a template param (and any
parent lv:inline-template attributes will be added to each and every
set) -->
<for-each select="@*|ancestor::lv:inline-template/@*">
<lv:with-param name="@{local-name()}@" value="{.}" />
</for-each>
</lv:apply-template>
</template>
<template mode="preproc:inline-apply"
match="lv:for-each/lv:sym-set">
<param name="name" />
<param name="src-root" as="element( lv:package )"
select="root(.)"
tunnel="yes" />
<!-- TODO: moar predicates! -->
<variable name="syms" as="element( preproc:sym )*"
select="$src-root/preproc:symtable/preproc:sym[
( not( current()/@type )
or @type = current()/@type )
and ( not( current()/@yields )
or @yields = current()/@yields )
and ( not( current()/@parent )
or @parent = current()/@parent )
and ( not( current()/@imports = 'true' )
or not( @src ) )
and ( not( current()/@name-prefix )
or substring( @name,
1,
string-length(current()/@name-prefix ) )
= current()/@name-prefix
or substring( @orig-name,
1,
string-length(current()/@name-prefix ) )
= current()/@name-prefix ) ]" />
<for-each select="$syms">
<!-- some symbols are modified to include a prefix; these internal
details should not be exposed to the user, since they are
useless within the DSL itself -->
<variable name="name-strip"
select="substring-after(
substring-after( @name, ':' ),
':' )" />
<variable name="sym-name"
select="if ( $name-strip ) then
$name-strip
else
@name" />
<lv:apply-template name="{$name}">
<lv:with-param name="@sym_name@" value="{$sym-name}" />
<lv:with-param name="@sym_type@" value="{@type}" />
<lv:with-param name="@sym_dim@" value="{@dim}" />
<lv:with-param name="@sym_desc@" value="{@desc}" />
<lv:with-param name="@sym_yields@" value="{@yields}" />
<lv:with-param name="@sym_parent@" value="{@parent}" />
</lv:apply-template>
</for-each>
</template>
<!--
This block is used when we attempt to apply a template that has not been
defined
-->
<template match="lv:apply-template" mode="preproc:macros" priority="5">
<!-- keep this application around for later -->
<sequence select="." />
<!-- nothing we can do yet -->
<message>
<text>[preproc] deferring application of unknown template </text>
<value-of select="@name" />
</message>
<preproc:repass need-sym="{@name}" />
</template>
<template match="lv:template/lv:param" mode="preproc:apply-template" priority="5">
<param name="first-child" select="false()" />
<!-- do not copy the template params for the template being applied; that
could result in some potentially inteesting bugs -->
<if test="not( $first-child )">
<sequence select="." />
</if>
</template>
<!--
Halt expansion at this point and remove the barrier for future expansions
-->
<template match="lv:expand-barrier" mode="preproc:apply-template" priority="5">
<sequence select="node()" />
</template>
<!--
Leave immediate children alone but continue expanding grandchildren
-->
<template match="lv:skip-child-expansion" mode="preproc:apply-template" priority="5">
<for-each select="element()">
<copy>
<copy-of select="@*" />
<apply-templates mode="preproc:apply-template" />
</copy>
</for-each>
</template>
<!--
Preprocesses template nodes
Currently, only attributes will be processed. Otherwise, the template ends up
recursing on all children to process all attributes.
-->
<template match="*" mode="preproc:apply-template" priority="1">
<copy>
<apply-templates select="@*|node()"
mode="preproc:apply-template" />
</copy>
</template>
<!--
Generate template metadata from lv:param-meta nodes.
Metadata contained within an lv:param-copy has its scope limited to
children only. Standalone lv:param-meta nodes are "hoisted" and are
available to siblings.
-->
<template match="lv:param-meta" mode="preproc:apply-template" priority="5">
<param name="apply" as="node()"
tunnel="yes" />
<variable name="name">
<call-template name="preproc:template-param-value">
<with-param name="name" select="@name" />
</call-template>
</variable>
<variable name="value">
<call-template name="preproc:template-param-value">
<with-param name="name" select="@value" />
</call-template>
</variable>
<preproc:tpl-meta name="{$name}" value="{$value}">
<if test="not( parent::lv:param-copy )">
<attribute name="hoist" select="'true'" />
</if>
</preproc:tpl-meta>
</template>
<!--
Copies child nodes associated with the param during application into the
replacement
This effectively allows the definition of custom node types.
-->
<template match="lv:param-copy" mode="preproc:apply-template" priority="5">
<param name="params" as="element( lv:with-param )*"
tunnel="yes" />
<!-- if any metadata has been defined, retain it so that it may be queried by
future templates -->
<apply-templates mode="preproc:apply-template"
select="lv:param-meta" />
<variable name="varname" select="@name" />
<variable name="param" select="$params[ @name=$varname ]" />
<variable name="copy" as="node()*">
<choose>
<!-- TAMER desugared @values@ application convention (see tplshort.rs) -->
<when test="$param/@value">
<!-- the value is the name of a template to copy the body from -->
<variable name="dsgr" select="$param/@value" />
<!-- the template is always positioned as the immeditely-following
sibling of the `lv:apply-template` node -->
<sequence select="$params/parent::lv:apply-template
/following-sibling::lv:template[ @name=$dsgr ]
/*" />
</when>
<!-- old applicatication convention has child nodes within
`with-param` -->
<otherwise>
<sequence select="$param/*" />
</otherwise>
</choose>
</variable>
<choose>
<!-- if the @expand flag is set, then immediately begin expanding any
template params that it may have (that we know of), in effect making
the body of the template application *part* of the template itself -->
<when test="@expand='true'">
<message>[preproc] expanding param-copy...</message>
<apply-templates select="$copy" mode="preproc:apply-template">
</apply-templates>
</when>
<!-- by default, expansion is not requested, meaning that no template param
expansion will take place (on this pass) for the copied nodes; if any
template params exist, they will be handled on the next pass -->
<otherwise>
<sequence select="$copy" />
</otherwise>
</choose>
</template>
<template match="lv:dyn-node" mode="preproc:apply-template" priority="5">
<variable name="dyn-name">
<call-template name="preproc:template-param-value">
<with-param name="name" select="@name" />
</call-template>
</variable>
<element name="{$dyn-name}">
<apply-templates mode="preproc:apply-template" />
</element>
</template>
<!--
Display an arbitrary error to the user during compilation
This produces a compile-time error and immediately halts
compilation.
-->
<template mode="preproc:apply-template" priority="5"
match="lv:error">
<param name="apply-tpl-name" as="xs:string"
tunnel="yes" />
<variable name="message">
<apply-templates mode="preproc:gen-param-value" />
</variable>
<preproc:error>
<value-of select="$apply-tpl-name" />
<text>: </text>
<value-of select="normalize-space( $message )" />
</preproc:error>
</template>
<!--
Display an arbitrary warning to the user during compilation
This produces a compile-time warning that is purely informational
and does not halt compilation. It is intended to point out
undesirable conditions.
-->
<template mode="preproc:apply-template" priority="5"
match="lv:warning">
<param name="apply-tpl-name" as="xs:string"
tunnel="yes" />
<variable name="message">
<apply-templates mode="preproc:gen-param-value" />
</variable>
<message>
<text>warning: </text>
<value-of select="$apply-tpl-name" />
<text>: </text>
<value-of select="normalize-space( $message )" />
</message>
</template>
<template mode="preproc:apply-template" priority="5"
match="text()">
<value-of select="." />
</template>
<!--
Basic conditional support within templates
Simple concept, powerful consequences.
***No recursion limit check...TODO
-->
<template match="lv:if|lv:unless" mode="preproc:apply-template" priority="5">
<param name="apply-tpl-name" as="xs:string"
tunnel="yes" />
<choose>
<!-- if the param is unknown, retain; it may be intended for a generated
template -->
<when test="
not(
@name=ancestor::lv:template[
@name=$apply-tpl-name
]/lv:param/@name
)
">
<sequence select="." />
</when>
<otherwise>
<!-- calculate the param value -->
<variable name="param-value">
<call-template name="preproc:template-param-value">
<with-param name="name" select="@name" />
</call-template>
</variable>
<!-- @value value -->
<variable name="cmp-value">
<call-template name="preproc:template-param-value">
<with-param name="name" select="@*[ not( local-name() = 'name' ) ]" />
</call-template>
</variable>
<apply-templates select="." mode="preproc:apply-template-cmp">
<with-param name="param-value" select="$param-value" />
<with-param name="cmp-value" select="$cmp-value" />
<with-param name="negate">
<choose>
<when test="local-name() = 'if'">
<value-of select="false()" />
</when>
<otherwise>
<value-of select="true()" />
</otherwise>
</choose>
</with-param>
</apply-templates>
</otherwise>
</choose>
</template>
<template match="lv:*[@eq]" mode="preproc:apply-template-cmp" priority="5">
<param name="negate" select="false()" />
<param name="param-value" />
<param name="cmp-value" />
<if test="
( ( $negate = 'true' ) and not( $param-value = $cmp-value ) )
or ( ( $negate = 'false' ) and ( $param-value = $cmp-value ) )
">
<apply-templates select="*" mode="preproc:apply-template" />
</if>
</template>
<template match="lv:*[@gt]" mode="preproc:apply-template-cmp" priority="5">
<param name="negate" select="false()" />
<param name="param-value" />
<param name="cmp-value" />
<if test="
( ( $negate = 'true' ) and not( $param-value &gt; $cmp-value ) )
or ( ( $negate = 'false' ) and ( $param-value &gt; $cmp-value ) )
">
<apply-templates select="*" mode="preproc:apply-template" />
</if>
</template>
<template match="lv:*[@gte]" mode="preproc:apply-template-cmp" priority="5">
<param name="negate" select="false()" />
<param name="param-value" />
<param name="cmp-value" />
<if test="
( ( $negate = 'true' ) and not( $param-value &gt;= $cmp-value ) )
or ( ( $negate = 'false' ) and ( $param-value &gt;= $cmp-value ) )
">
<apply-templates select="*" mode="preproc:apply-template" />
</if>
</template>
<template match="lv:*[@lt]" mode="preproc:apply-template-cmp" priority="5">
<param name="negate" select="false()" />
<param name="param-value" />
<param name="cmp-value" />
<if test="
( ( $negate = 'true' ) and not( $param-value &lt; $cmp-value ) )
or ( ( $negate = 'false' ) and ( $param-value &lt; $cmp-value ) )
">
<apply-templates select="*" mode="preproc:apply-template" />
</if>
</template>
<template match="lv:*[@lte]" mode="preproc:apply-template-cmp" priority="5">
<param name="negate" select="false()" />
<param name="param-value" />
<param name="cmp-value" />
<if test="
( ( $negate = 'true' ) and not( $param-value &lt;= $cmp-value ) )
or ( ( $negate = 'false' ) and ( $param-value &lt;= $cmp-value ) )
">
<apply-templates select="*" mode="preproc:apply-template" />
</if>
</template>
<template mode="preproc:apply-template-cmp" priority="5"
match="lv:*[ @prefix ]">
<param name="negate" select="false()" />
<param name="param-value" />
<param name="cmp-value" />
<variable name="has-prefix" as="xs:boolean"
select="starts-with( $param-value, $cmp-value )" />
<if test="( $negate and not( $has-prefix ) )
or ( not( $negate ) and $has-prefix )">
<apply-templates select="*" mode="preproc:apply-template" />
</if>
</template>
<template mode="preproc:apply-template-cmp" priority="5"
match="lv:*[ @suffix ]">
<param name="negate" select="false()" />
<param name="param-value" />
<param name="cmp-value" />
<variable name="has-suffix" as="xs:boolean"
select="ends-with( $param-value, $cmp-value )" />
<if test="( $negate and not( $has-suffix ) )
or ( not( $negate ) and $has-suffix )">
<apply-templates select="*" mode="preproc:apply-template" />
</if>
</template>
<template match="lv:*[not(@*[not(local-name()='name')])]" mode="preproc:apply-template-cmp" priority="2">
<param name="negate" select="'false'" />
<param name="param-value" />
<param name="cmp-value" />
<!-- TODO: This is convoluted...we "know" it's empty because it'll return its
own name as a value by default... -->
<if test="
( ( $negate = 'true' ) and ( $param-value = @name ) )
or ( ( $negate = 'false' ) and not( $param-value = @name ) )
">
<apply-templates select="*" mode="preproc:apply-template" />
</if>
</template>
<template match="lv:*" mode="preproc:apply-template-cmp" priority="1">
<preproc:error>
<text>Unknown template comparison attribute for </text>
<value-of select="name()" />
<text> in </text>
<value-of select="ancestor::lv:template/@name" />
<text>:</text>
<for-each select="@*">
<text> </text>
<value-of select="local-name()" />
<text>="</text>
<value-of select="." />
<text>"</text>
</for-each>
</preproc:error>
</template>
<template mode="preproc:gen-param-value" priority="5"
match="lv:param-add">
<!-- calculate the param value -->
<variable name="name-value">
<call-template name="preproc:template-param-value">
<with-param name="name" select="@name" />
</call-template>
</variable>
<variable name="value-value">
<call-template name="preproc:template-param-value">
<with-param name="name" select="@value" />
</call-template>
</variable>
<if test="not( $name-value castable as xs:float )">
<message terminate="yes"
select="concat( 'error: non-numeric lv:param-add/@name: ',
$name-value )" />
</if>
<if test="not( $value-value castable as xs:float )">
<message terminate="yes"
select="concat( 'error: non-numeric lv:param-add/@value: ',
$name-value )" />
</if>
<value-of select="number( $name-value ) + number( $value-value )" />
</template>
<!--
Process template attributes
Provides primitive parameter support. If a parameter is found (convention is
that it must begin and end with '@', but that convention is not enforced
here), it is replaced by the value given by the calling lv:apply-template.
Note that we currently do not perform any string replacements. As such, we
only support full replacement of an attribute (that is, the attribute must
contain only a parameter, not a parameter as part of a larger string); it is
for this reason that the convention of beginning and ending with '@' arose: it
allows us to perform substring replacements later on with clear delimiters.
TODO: substring replacement for added flexibility
-->
<template match="@*" mode="preproc:apply-template" priority="5">
<param name="tpl" tunnel="yes" />
<variable name="name" select="local-name()" />
<variable name="varname" select="string(.)" />
<!-- compile param value -->
<variable name="value">
<call-template name="preproc:template-param-value">
<with-param name="name" select="$varname" />
</call-template>
</variable>
<choose>
<!-- if the template being applied does not itself define this
parameter, and we're performing a full var replacement, keep the
name verbatim for later expansion -->
<when test="starts-with( $varname, '@' )
and not( $tpl/lv:param[ @name = $varname ] )">
<attribute name="{$name}" select="$varname" />
</when>
<!-- if the result is an empty string, then do not output the attribute (this
allows for conditional attributes -->
<when test="not( $value = '' )">
<attribute name="{$name}" select="$value" />
</when>
</choose>
</template>
<template match="lv:with-param/@name" mode="preproc:apply-template" priority="9">
<!-- do not do any sort of expansion of recursive template param names -->
<copy />
</template>
<template name="preproc:template-param-value">
<param name="name" />
<param name="params" as="element( lv:with-param )*"
tunnel="yes" />
<variable name="after" select="substring-after( $name, '@' )" />
<!-- TODO: support multiple @vars@ per attribute -->
<choose>
<when test="$name = ''">
<!-- we're done; nothing more to do -->
</when>
<!-- no vars; just output and abort -->
<when test="not( $after )">
<value-of select="$name" />
</when>
<!-- if the variable is inline, output the value up until the inline
delimiter (curly brace); this supports inline vars at any point in the
string -->
<when test="not( starts-with( $name, '@' ) )">
<!-- this might be nothing (if we start with a curly brace) -->
<value-of select="substring-before( $name, '{' )" />
<!-- get the param between {} -->
<variable name="param"
select="substring-before( substring-after( $name, '{' ), '}' )" />
<!-- if a param was found, process it -->
<if test="$param">
<variable name="result">
<call-template name="preproc:template-param-value">
<with-param name="name" select="$param" />
</call-template>
</variable>
<!-- if the result is the same as the param name, then it was not found;
re-enclose in braces so that it can be processed later (where we
may find a value) -->
<choose>
<when test="$result = $param">
<text>{</text>
<value-of select="$result" />
<text>}</text>
</when>
<otherwise>
<value-of select="$result" />
</otherwise>
</choose>
</if>
<!-- recurse to generate the remainder of the string -->
<call-template name="preproc:template-param-value">
<with-param name="name" select="substring-after( $name, '}' )" />
</call-template>
</when>
<!-- starts with a macro param -->
<otherwise>
<!-- get the param, since we may have additional text following it -->
<!-- get the value of this parameter, if it exists, from the caller (note
below that we will consider a value of the same name as the param to
be undefined, allowing defaults to be generated; this is useful when
proxying params between templates)-->
<variable name="val"
select="$params[ @name=$name ]/@value" />
<!-- the param node itself -->
<variable name="param"
select="ancestor::lv:template/lv:param[ @name=$name ]" />
<choose>
<!-- this test is structured to fail if the param has not been defined
on the templates parameter list; this is a restriction imposed
purely for documentation; the preprocessor can do just fine without
-->
<when test="$val and not( $val = $name ) and $param">
<value-of select="$val" />
</when>
<!-- otherwise, if we just have the param and it has child nodes,
generate a value (in essence: generate a default value if one is
not provided) -->
<when test="$param and $param/lv:*">
<apply-templates select="$param/lv:*"
mode="preproc:gen-param-value" />
</when>
<otherwise>
<value-of select="$name" />
</otherwise>
</choose>
</otherwise>
</choose>
</template>
<template mode="preproc:gen-param-value" priority="6"
match="lv:param/lv:text[ @unique='true' ]">
<param name="apply" as="node()"
tunnel="yes" />
<value-of select="." />
<value-of select="generate-id( $apply )" />
</template>
<template match="lv:param/lv:text" mode="preproc:gen-param-value"
priority="4">
<value-of select="." />
</template>
<template mode="preproc:gen-param-value" priority="5"
match="lv:param/lv:param-inherit[@meta]">
<param name="apply" as="node()"
tunnel="yes" />
<variable name="name">
<call-template name="preproc:template-param-value">
<with-param name="name" select="@meta" />
</call-template>
</variable>
<!-- find the metadata -->
<variable name="values"
select="$apply/ancestor::*/preceding-sibling::preproc:tpl-meta[ @name=$name ]/@value
, $apply/ancestor::*/preproc:tpl-barrier
/preproc:tpl-meta[ @hoist = 'true' and @name=$name ]/@value
, $apply/preceding-sibling::preproc:tpl-meta[ @name=$name ]/@value" />
<!-- take the last one (precedence) -->
<value-of select="$values[ count( $values ) ]" />
</template>
<template mode="preproc:gen-param-value" priority="4"
match="lv:param-value">
<param name="params" as="element( lv:with-param )*"
tunnel="yes" />
<!-- name of the parameter we're referencing -->
<variable name="pname" as="xs:string"
select="@name" />
<!-- the original string -->
<variable name="str" as="xs:string?"
select="$params[ @name=$pname ]/@value" />
<!-- apply @snake if need be -->
<variable name="processed">
<choose>
<!-- snakeify -->
<when test="@snake">
<value-of select="translate( $str, '-', '_' )" />
</when>
<!-- convert spaces and underscores to dashes -->
<when test="@dash">
<value-of select="translate( $str, '_ ', '--' )" />
</when>
<when test="@rmdash">
<value-of select="translate( $str, '-', '' )" />
</when>
<when test="@rmunderscore">
<value-of select="translate( $str, '_', '' )" />
</when>
<!-- do nothing -->
<otherwise>
<value-of select="$str" />
</otherwise>
</choose>
</variable>
<!-- perform any additional processing -->
<apply-templates select="." mode="preproc:gen-param-value-style">
<with-param name="str" select="$processed" />
</apply-templates>
</template>
<template match="lv:param-value[@ucfirst]" mode="preproc:gen-param-value-style" priority="5">
<param name="str" />
<call-template name="util:ucfirst">
<with-param name="str" select="$str" />
</call-template>
</template>
<!-- slightly higher priority than @ucfirst, since this obviously should
override -->
<template match="lv:param-value[@upper]" mode="preproc:gen-param-value-style" priority="6">
<param name="str" />
<call-template name="util:uppercase">
<with-param name="str" select="$str" />
</call-template>
</template>
<template match="lv:param-value[@lower]" mode="preproc:gen-param-value-style" priority="6">
<param name="str" />
<call-template name="util:lowercase">
<with-param name="str" select="$str" />
</call-template>
</template>
<!-- convert into valid identifier name -->
<template match="lv:param-value[@identifier]"
mode="preproc:gen-param-value-style" priority="6">
<param name="str" />
<variable name="norm" as="xs:string"
select="normalize-unicode( $str, 'NFC' )" />
<variable name="pre" as="xs:string">
<choose >
<when test="@identifier = 'class'">
<sequence select="replace( lower-case( $norm ), '[_ ]', '-' )" />
</when>
<when test="@identifier = 'param'">
<sequence select="replace( lower-case( $norm ), '[- ]', '_' )" />
</when>
<when test="@identifier = 'const'">
<sequence select="replace( upper-case( $norm ), '[- ]', '_' )" />
</when>
<!-- TODO: camelCase -->
<when test="@identifier = 'rate'">
<sequence select="replace( $norm, '[-_ ]', '' )" />
</when>
<otherwise>
<sequence select="$norm" />
</otherwise>
</choose>
</variable>
<!-- everything else gets removed -->
<sequence select="replace(
$pre,
'[^a-zA-Z0-9_-]',
'' )" />
</template>
<!-- no other styling -->
<template match="lv:param-value" mode="preproc:gen-param-value-style" priority="1">
<param name="str" />
<value-of select="$str" />
</template>
<!--
Converts class name to its @yields variable name
-->
<template mode="preproc:gen-param-value" priority="5"
match="lv:param-class-to-yields">
<param name="params" as="element( lv:with-param )*"
tunnel="yes" />
<param name="src-root" as="element( lv:package )"
select="root(.)"
tunnel="yes" />
<!-- get the class name from the param -->
<variable name="pname" select="@name" />
<variable name="as" select="$params[ @name=$pname ]/@value" />
<!-- get @yields from class -->
<variable name="yields" select="
$src-root/preproc:symtable/preproc:sym[
@name=concat( ':class:', $as )
]/@yields
" />
<choose>
<when test="not( $yields ) or $yields=''">
<preproc:error>
<text>error: unable to determine @yields for class `</text>
<value-of select="$as" />
<text>' (has the class been imported?)</text>
</preproc:error>
</when>
<otherwise>
<value-of select="$yields" />
</otherwise>
</choose>
</template>
<!--
Retrieve symbol metadata.
-->
<template mode="preproc:gen-param-value" priority="5"
match="lv:param-sym-value">
<param name="params" as="element( lv:with-param )*"
tunnel="yes" />
<param name="src-root" as="element( lv:package )"
select="root(.)"
tunnel="yes" />
<variable name="pname" as="xs:string" select="@name" />
<variable name="value" as="xs:string" select="@value" />
<variable name="name" as="xs:string"
select="$params[ @name = $pname ]/@value" />
<variable name="sym-name" as="xs:string"
select="concat( @prefix, $name )" />
<!-- get @yields from class -->
<variable name="sym-value" as="xs:string?" select="
$src-root/preproc:symtable/preproc:sym[
@name = $sym-name ]
/@*[ local-name() = $value ]" />
<choose>
<when test="not( $sym-value ) or $sym-value = ''">
<!-- error out only if lookup failures aren't explicitly suppressed -->
<if test="not( @ignore-missing = 'true' )">
<message>
<text>warning: unable to find `@</text>
<value-of select="$value" />
<text>' for symbol `</text>
<value-of select="$sym-name" />
<text>' (does the symbol support `@</text>
<value-of select="$value" />
<text>' and has it been imported?)</text>
</message>
<!-- just use the name if nothing is available -->
<value-of select="$name" />
</if>
</when>
<otherwise>
<value-of select="$sym-value" />
</otherwise>
</choose>
</template>
<!--
Look up the name of a constant within a typedef by its value.
This exists because sym-set is far too slow because of the inline
templates; maybe it can be deprecated in the future or replaced with a
more general primitive.
The intent of this is to allow code generators to simply output integers
and let Tame figure out what constant to use, thus avoiding a map to
constant names in the code generator. By using constant names, the values
acquire meaning that is persisted onto the dependency graph and can be
used for reporting and static analysis.
-->
<template mode="preproc:gen-param-value" priority="5"
match="lv:param-typedef-lookup">
<param name="params" as="element( lv:with-param )*"
tunnel="yes" />
<param name="src-root" as="element( lv:package )"
select="root(.)"
tunnel="yes" />
<variable name="tname" as="xs:string" select="@name" />
<variable name="value" as="xs:string" select="@value" />
<variable name="typedef-name" as="xs:string"
select="if ( starts-with( $tname, '@' ) ) then
$params[ @name = $tname ]/@value
else
$tname" />
<variable name="query-value" as="xs:string"
select="$params[ @name = $value ]/@value" />
<variable name="typedef-sym" as="element( preproc:sym )"
select="$src-root/preproc:symtable/preproc:sym[
@name = $typedef-name
and @type = 'type' ]" />
<variable name="typedef-pkg" as="element( lv:package )"
select="if ( $typedef-sym/@src ) then
document( concat( $typedef-sym/@src, '.xmlo' ), $__entry-root )
/lv:package
else
$src-root/lv:package" />
<variable name="typedef" as="element( lv:typedef )"
select="$typedef-pkg//lv:typedef[
@name = $typedef-name ]" />
<!-- get @yields from class -->
<variable name="const-name" as="xs:string*"
select="$typedef//lv:item[ @value = $query-value ]/@name" />
<choose>
<when test="count( $const-name ) gt 1">
<message terminate="yes"
select="concat( 'error: the value ''', $query-value, ''' ',
'is associated with more than one item in ''',
$tname, ''': ',
string-join( $const-name, ', ' ) )" />
</when>
<when test="not( $const-name )">
<!-- error out only if lookup failures aren't explicitly suppressed -->
<if test="not( @ignore-missing = 'true' )">
<message terminate="yes">
<text>error: the value `</text>
<value-of select="$query-value" />
<text>' does not exist in the domain of `</text>
<value-of select="$tname" />
<text>'</text>
</message>
</if>
</when>
<otherwise>
<value-of select="$const-name" />
</otherwise>
</choose>
</template>
<template mode="preproc:gen-param-value" priority="5"
match="text()">
<value-of select="." />
</template>
<template mode="preproc:gen-param-value" priority="1"
match="node()">
<message terminate="yes">
<text>error: unknown param node content: </text>
<copy-of select="." />
</message>
</template>
<!-- Templates should never be expanded until they are actually replaced by
macro expansion using apply-templates; just keep ignoring this until it's
eventually removed by the expand phase -->
<template match="lv:template" mode="preproc:macros" priority="5">
<sequence select="." />
</template>
<!--
expand-sequence
TODO: move to a better place
-->
<template mode="preproc:macros" priority="5"
match="lv:expand-sequence">
<sequence select="eseq:expand-step( . )" />
</template>
<!--
expand-group is our means of grouping together expressions to be
expanded as a group; this is far more efficient than expanding each
one individually, when that is unneeded.
-->
<template mode="preproc:macros" priority="5"
match="lv:expand-group">
<!-- strip expand-group -->
<apply-templates mode="preproc:macros" />
</template>
<function name="eseq:is-expandable" as="xs:boolean">
<param name="node" as="node()" />
<!-- TODO: what a mess; clean me up by changing the point at which
this is processed, which will expand all of these into
lv:apply-template -->
<sequence select="$node instance of element()
and not( $node instance of
element( lv:template ) )
and (
$node instance of
element( lv:apply-template )
or $node instance of
element( lv:inline-template )
or namespace-uri( $node )
= namespace-uri-for-prefix( 't', $node )
or (
$node//lv:inline-template,
$node//lv:apply-template,
$node//t:* )
[ not(
ancestor::lv:template
or ancestor::lv:apply-template ) ] )" />
</function>
<function name="eseq:expand-node" as="node()*">
<param name="node" as="node()" />
<apply-templates mode="preproc:macros"
select="$node" />
</function>
<template mode="preproc:macros" priority="9"
match="node()[ not( . instance of element() ) ]">
<sequence select="." />
</template>
</stylesheet>