Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/recipe-books.git

Oxford comma for equipment list.

AuthorDave Jarvis <email>
Date2013-04-13 17:15:08 GMT-0700
Commitdb8f86692d57b3f728dc200f8ccccd9144baf620
Parent66b6757
xsl/latex.xsl
exclude-result-prefixes="r str string">
-<xsl:import href="string-utilities.xsl" />
-<xsl:import href="fractions.xsl" />
-
-<xsl:output method="text" encoding="utf-8" />
-
-<xsl:strip-space elements="*" />
-
-<!-- LaTeX premable inserted at the top of the document as text. -->
-<xsl:param name="preamble" select="''" />
-
-<!-- If true, generate the table of contents. -->
-<xsl:param name="recipe-toc" select="true()" />
-
-<!-- If true, generate the index of ingredients (and categories). -->
-<xsl:param name="ingredient-index" select="true()" />
-
-<!-- If true, generate the copyright page. -->
-<xsl:param name="book-copyright" select="true()" />
-
-<!-- If true, include a front cover image. -->
-<xsl:param name="book-front-cover" select="true()" />
-
-<!-- If false, the will use a print layout. -->
-<xsl:param name="ebook" select="true()" />
-
-<!-- If true, the page number appears on full-page photo pages. -->
-<xsl:param name="page-photo-footer" select="false()" />
-
-<!--
- | Citation style format; valid values:
- | endcitations (default)
- | page-photo-footercitations
- | inlinecitations
- +-->
-<xsl:param name="citation-style" select="'endcitations'" />
-
-<!-- These will come from the XML...
-<xsl:param name="book-back-cover" select="true()" />
--->
-
-<!-- Keys -->
-<xsl:key name="ingredient-id" match="/recipe-book/ingredients/ingredient" use="@id" />
-<xsl:key name="recipe-by-category" match="recipe" use="tags/tag[1]" />
-
-<!--
- | A recipe book consists of book details, recipes, categories, and tags.
- +-->
-<xsl:template match="/recipe-book">
- <xsl:text>\documentclass</xsl:text>
- <xsl:text>[</xsl:text>
- <xsl:if test="not($ebook)">
- <xsl:text>print,</xsl:text>
- </xsl:if>
- <xsl:if test="$page-photo-footer">
- <xsl:text>fullpagepage-photo-footer,</xsl:text>
- </xsl:if>
- <xsl:value-of select="$citation-style"/>
- <xsl:text>]</xsl:text>
- <xsl:text>{recipe-book}&#xa;</xsl:text>
- <xsl:text>\usepackage{hyperref}&#xa;</xsl:text>
- <xsl:value-of select="$preamble" />
- <xsl:text>\makeindex[flatingred]&#xa;</xsl:text>
- <xsl:text>\makeindex[catingred]&#xa;</xsl:text>
- <xsl:apply-templates select='book' />
- <xsl:text>\begin{document}&#xa;</xsl:text>
- <xsl:text>\frontmatter&#xa;</xsl:text>
- <xsl:if test="$book-front-cover">
- <xsl:text>\makecover</xsl:text>
- <xsl:if test="$book-copyright">
- <xsl:text>[copyrightpage]</xsl:text>
- </xsl:if>
- <xsl:text>&#xa;</xsl:text>
- </xsl:if>
- <xsl:apply-templates select="book/overview" mode="book-overview" />
- <xsl:if test="$recipe-toc">
- <xsl:text>\cleartoverso\tableofcontents*&#xa;</xsl:text>
- </xsl:if>
- <xsl:text>\mainmatter&#xa;</xsl:text>
-
- <!--
- | "Muenchian Method" for sorting tagged recipes into chapters, described in
- | XSLT Cookbook, by Sal Mangano, O'Reilly, pg195.
- +-->
- <xsl:for-each select="recipe[count(. | key('recipe-by-category',tags/tag[1])[1]) = 1]">
- <xsl:variable name="current-grouping-key" select="tags/tag[1]"/>
- <xsl:if test="$current-grouping-key != ''">
- <xsl:text>&#xa;\recipecategory{</xsl:text>
- <xsl:apply-templates select="$current-grouping-key" mode="escape-capitalize"/>
- <xsl:text>}&#xa;&#xa;</xsl:text>
- <xsl:variable name="current-group"
- select="key('recipe-by-category', $current-grouping-key)" />
- <xsl:for-each select="$current-group">
- <xsl:apply-templates select="." />
- </xsl:for-each>
- </xsl:if>
- </xsl:for-each>
-
- <!--
- | All untagged recipes to go into a "Recipes" chapter.
- +-->
- <xsl:if test="recipe[tags/tag = '' or not(tags/tag)]">
- <xsl:text>&#xa;\recipecategory{Recipes}&#xa;&#xa;</xsl:text>
- <xsl:apply-templates select="recipe[tags/tag = '' or not(tags/tag)]" />
- </xsl:if>
-
- <xsl:text>\backmatter&#xa;</xsl:text>
- <xsl:text>\renewcommand{\clearforchapter}{\newpage}&#xa;</xsl:text>
- <xsl:if test="$ingredient-index">
- <xsl:text>\renewcommand{\indexname}{Ingredients in Alphabetic Order}&#xa;</xsl:text>
- <xsl:text>\printindex[flatingred]&#xa;</xsl:text>
- <xsl:text>\renewcommand{\indexname}{Ingredients by Categories}&#xa;</xsl:text>
- <xsl:text>\reduceindexindent&#xa;</xsl:text>
- <xsl:text>\printindex[catingred]&#xa;</xsl:text>
- </xsl:if>
- <xsl:text>\end{document}&#xa;</xsl:text>
-</xsl:template>
-
-<!--
- | Book meta information.
- +-->
-<xsl:template match="book">
- <xsl:text>\title{</xsl:text>
- <xsl:apply-templates select="title" mode="escape-capitalize" />
- <xsl:text>}&#xa;</xsl:text>
- <xsl:text>\date{</xsl:text>
- <xsl:apply-templates select="created" mode="escape-capitalize" />
- <xsl:text>}&#xa;</xsl:text>
- <xsl:text>\author{</xsl:text>
- <xsl:apply-templates select="author" mode="escape-capitalize" />
- <xsl:text>}&#xa;</xsl:text>
- <xsl:apply-templates select="photo" mode="book" />
-</xsl:template>
-
-<xsl:template match="overview" mode="book-overview">
- <xsl:text>&#xa;\begin{overview}{</xsl:text>
- <xsl:apply-templates select="@label" mode="escape-capitalize" />
- <xsl:text>}&#xa;</xsl:text>
- <xsl:if test="../photo[@use='overview']">
- <xsl:apply-templates select="../photo[@use='overview']/uri" mode="book-overview"/>
- </xsl:if>
- <xsl:apply-templates select="p" />
- <xsl:text>\end{overview}&#xa;&#xa;</xsl:text>
-</xsl:template>
-
-<!--
- | Allows users to provide descriptive text for the book.
- +-->
-<xsl:template match="p">
- <xsl:apply-templates mode="escape" />
- <xsl:text>&#xa;&#xa;</xsl:text>
-</xsl:template>
-
-<!--
- | Transforms a recipe from XML into LaTeX.
- +-->
-<xsl:template match="recipe">
- <!--
- | If a facing full-page photo is used, check whether a filler photo
- | (e.g., advertsement) is required to make up the page number.
- +-->
- <xsl:if test="description/photo[@use='one-page' or @use='two-page']">
- <xsl:text>\checkfiller&#xa;</xsl:text>
- </xsl:if>
- <xsl:text>\begin{recipe}</xsl:text>
- <xsl:apply-templates select="description/citation[descendant::*]" />
- <xsl:text>{</xsl:text>
- <xsl:apply-templates select="description/title" />
- <xsl:text>}&#xa;</xsl:text>
- <xsl:apply-templates select="description/photo[@use='recipe-inset']" mode="recipe" />
- <xsl:apply-templates />
- <xsl:apply-templates select="description/photo[@use='one-page' or @use='two-page']" mode="recipe" />
- <xsl:text>\end{recipe}&#xa;&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="photo[@use='front-cover']" mode="book">
- <xsl:apply-templates select="uri" mode="book" />
-</xsl:template>
-
-<xsl:template match="photo[@use='overview']" mode="book" />
-
-<!--
-<xsl:template match="photo[@use='recipe-inset']" mode="recipe">
- <xsl:apply-templates select="uri" mode="recipe-inset" />
-</xsl:template>
-
-<xsl:template match="photo[@use='one-page']" mode="recipe">
- <xsl:apply-templates select="uri" mode="one-page" />
-</xsl:template>
-
-<xsl:template match="photo[@use='two-page']" mode="recipe">
- <xsl:apply-templates select="uri" mode="two-page" />
-</xsl:template>
--->
-<xsl:template match="photo" mode="recipe">
- <xsl:text>\photo[embed=</xsl:text>
- <xsl:choose>
- <xsl:when test="@use='recipe-inset'">
- <xsl:text>inset</xsl:text>
- </xsl:when>
- <xsl:when test="@use='one-page'">
- <xsl:text>fullpage</xsl:text>
- </xsl:when>
- <xsl:when test="@use='two-page'">
- <xsl:text>twopagespread</xsl:text>
- </xsl:when>
- </xsl:choose>
- <xsl:text>,</xsl:text>
- <xsl:apply-templates match="citation[descendant::*]" />
- <xsl:text>]{</xsl:text>
- <xsl:apply-templates select="uri" mode="escape"/>
- <xsl:text>}&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="uri" mode="book-overview">
- <xsl:text>\begin{wrapfigure}{l}{.45\textwidth}&#xa;</xsl:text>
- <xsl:text>\insetphoto[</xsl:text>
- <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
- <xsl:text>]{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}&#xa;</xsl:text>
- <xsl:text>\end{wrapfigure}&#xa;&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="uri" mode="book">
- <xsl:if test="$book-front-cover">
- <xsl:text>\frontcoverphoto[</xsl:text>
- <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
- <xsl:text>]{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}&#xa;</xsl:text>
- </xsl:if>
-</xsl:template>
-
-<!--
-<xsl:template match="uri" mode="recipe-inset">
- <xsl:text>\insetphoto</xsl:text>
- <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
- <xsl:text>{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="uri" mode="one-page">
- <xsl:text>\fullpagephoto</xsl:text>
- <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
- <xsl:text>{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="uri" mode="two-page">
- <xsl:text>\twopagespreadphoto</xsl:text>
- <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
- <xsl:text>{</xsl:text>
- <xsl:apply-templates />
- <xsl:text>}&#xa;</xsl:text>
-</xsl:template>
--->
-<xsl:template match="citation[descendant::*]">
-<!-- <xsl:text>[</xsl:text>-->
- <xsl:if test="author">
- <xsl:text>author={</xsl:text>
- <xsl:apply-templates select="author" mode="escape" />
- <xsl:text>},</xsl:text>
- </xsl:if>
- <xsl:if test="author/@url">
- <xsl:text>authorurl={</xsl:text>
- <xsl:apply-templates select="author/@url" mode="escape" />
- <xsl:text>},</xsl:text>
- </xsl:if>
- <xsl:if test="license">
- <xsl:text>license={</xsl:text>
- <xsl:apply-templates select="license" mode="escape" />
- <xsl:text>},</xsl:text>
- </xsl:if>
- <xsl:if test="license/@url">
- <xsl:text>licenseurl={</xsl:text>
- <xsl:apply-templates select="license/@url" mode="escape" />
- <xsl:text>},</xsl:text>
- </xsl:if>
-<!-- <xsl:text>]</xsl:text> -->
-</xsl:template>
-
-<!--
-<xsl:template match="description/citation[descendant::*]">
- <xsl:if test="author">
- <xsl:text>\recipecredit</xsl:text>
- <xsl:if test="author/@url">
- <xsl:text>[</xsl:text>
- <xsl:apply-templates select="author/@url" mode="escape" />
- <xsl:text>]</xsl:text>
- </xsl:if>
- <xsl:text>{</xsl:text>
- <xsl:apply-templates select="author" mode="escape" />
- <xsl:text>}&#xa;</xsl:text>
- </xsl:if>
-
- <xsl:if test="license">
- <xsl:text>\recipelicense</xsl:text>
- <xsl:if test="license/@url">
- <xsl:text>[</xsl:text>
- <xsl:apply-templates select="license/@url" mode="escape" />
- <xsl:text>]</xsl:text>
- </xsl:if>
- <xsl:text>{</xsl:text>
- <xsl:apply-templates select="license" mode="escape" />
- <xsl:text>}&#xa;</xsl:text>
- </xsl:if>
-</xsl:template>
--->
-
-<xsl:template match="description/title">
- <xsl:apply-templates mode="escape-capitalize" />
-</xsl:template>
-
-<!-- Pre-heated oven temperature, not to be confused with ingredient prep. -->
-<xsl:template match="preparation">
- <xsl:apply-templates />
-</xsl:template>
-
-<xsl:template match="oven">
- <xsl:text>\oven{</xsl:text>
- <xsl:value-of select="@temperature" />
- <xsl:text>\,</xsl:text>
- <xsl:choose>
- <xsl:when test="@unit='C' or @unit='F'">
- <xsl:text>\textdegree </xsl:text>
- </xsl:when>
- </xsl:choose>
- <xsl:value-of select="@unit" />
- <xsl:text>}&#xa;&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="equipment[descendant::text()]">
- <xsl:text>\begin{equipment}&#xa;</xsl:text>
- <!-- Select non-empty child nodes. -->
- <xsl:for-each select="*[child::*]">
- <xsl:text> \item[</xsl:text>
- <xsl:call-template name="escape-capitalize">
- <xsl:with-param name="ec" select="name()" />
- </xsl:call-template>
- <xsl:text>] </xsl:text>
- <xsl:call-template name="escape-capitalize">
- <xsl:with-param name="ec" select="." />
- </xsl:call-template>
- <xsl:text>&#xa;</xsl:text>
- </xsl:for-each>
- <xsl:text>\end{equipment}&#xa;&#xa;</xsl:text>
-</xsl:template>
-
-<!--
- | Puts the value of all labels (ingredients and directions) in brackets.
- +-->
-<xsl:template match="@label">
- <xsl:text>[</xsl:text>
- <xsl:call-template name="escape-capitalize">
- <xsl:with-param name="ec" select="." />
- </xsl:call-template>
- <xsl:text>]</xsl:text>
- <xsl:text>&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="ingredients[parent::recipe]">
- <xsl:if test="count(ingredient[not(@substitute) or @substitute='']) &gt; 0">
- <xsl:text>\begin{ingredients}&#xa;</xsl:text>
- <xsl:apply-templates select="@label" />
- <xsl:apply-templates select="ingredient[not(@substitute) or @substitute='']" />
- <xsl:text>\end{ingredients}&#xa;&#xa;</xsl:text>
- </xsl:if>
-
- <!--
- | If non-empty conditions exist, insert a preparation section.
- +-->
- <xsl:if test="ingredient[@condition != '']">
- <xsl:text>\begin{preparation}&#xa;</xsl:text>
- <xsl:apply-templates select="ingredient[@condition != '']" mode="prep" />
- <xsl:text>\end{preparation}&#xa;</xsl:text>
- </xsl:if>
-
- <!--
- | If non-empty substitutions, insert a substitution section.
- +-->
- <xsl:if test="ingredient[@substitute != '']">
- <xsl:text>\begin{substitution}&#xa;</xsl:text>
- <xsl:apply-templates select="ingredient[@substitute != '']" mode="subst" />
- <xsl:text>\end{substitution}&#xa;</xsl:text>
- </xsl:if>
-</xsl:template>
-
-<xsl:template match="ingredient">
- <xsl:text> \ingred{</xsl:text>
-
- <xsl:call-template name="utf-fraction">
- <xsl:with-param name="quantity" select="@min-quantity" />
- </xsl:call-template>
-
- <xsl:if test="@max-quantity">
- <xsl:text>--</xsl:text>
-
- <xsl:call-template name="utf-fraction">
- <xsl:with-param name="quantity" select="@max-quantity" />
- </xsl:call-template>
- </xsl:if>
- <xsl:text>}{</xsl:text>
- <xsl:value-of select="@unit" />
- <xsl:text>}{</xsl:text>
- <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
- <xsl:text>}%&#xa;</xsl:text>
-
- <!-- Put the ingredients in a categorized list. -->
- <xsl:if test="key('ingredient-id',@id)/categories/category">
- <xsl:for-each select="key('ingredient-id',@id)/categories/category">
- <xsl:text> \index[catingred]{</xsl:text>
- <xsl:call-template name="replace">
- <xsl:with-param name="pText" select="@name" />
- <xsl:with-param name="pToken" select="','" />
- <xsl:with-param name="pSubst" select="'!'" />
- </xsl:call-template>
- <xsl:text>!</xsl:text>
- <xsl:apply-templates select="../../@name" />
- <xsl:text>}%&#xa;</xsl:text>
- </xsl:for-each>
- </xsl:if>
-
- <!-- Index ingredients into a flat list. -->
- <xsl:text> \index[flatingred]{</xsl:text>
- <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
- <xsl:text>!</xsl:text>
- <xsl:apply-templates select="ancestor::recipe/description/title" />
- <xsl:text>}%&#xa;</xsl:text>
-</xsl:template>
-
-<!--
- | Extracts preparation instructions from ingredients.
- +-->
-<xsl:template match="ingredient[@condition != '']" mode="prep">
- <xsl:for-each select=".">
- <xsl:text>\item </xsl:text>
- <xsl:apply-templates select="@condition" mode="prep" />
- <xsl:text> the </xsl:text>
- <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
- <xsl:text>.&#xa;</xsl:text>
- </xsl:for-each>
-</xsl:template>
-
-<!--
- | Extracts substitution instructions from ingredients.
- +-->
-<xsl:template match="ingredient[@substitute != '']" mode="subst">
- <xsl:text> \item </xsl:text>
-
- <xsl:call-template name="utf-fraction">
- <xsl:with-param name="quantity" select="@min-quantity" />
- </xsl:call-template>
-
- <xsl:if test="@max-quantity">
- <xsl:text>--</xsl:text>
-
- <xsl:call-template name="utf-fraction">
- <xsl:with-param name="quantity" select="@max-quantity" />
- </xsl:call-template>
- </xsl:if>
- <xsl:text> </xsl:text>
- <xsl:value-of select="@unit" />
- <xsl:text> </xsl:text>
- <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
- <xsl:text> for </xsl:text>
- <xsl:apply-templates select="key('ingredient-id', @substitute)/@name" />
- <xsl:text>&#xa;</xsl:text>
-
- <!-- Put the ingredients in a categorized list. -->
- <xsl:if test="key('ingredient-id',@id)/categories/category">
- <xsl:for-each select="key('ingredient-id',@id)/categories/category">
- <xsl:text> \index[catingred]{</xsl:text>
- <xsl:call-template name="replace">
- <xsl:with-param name="pText" select="@name" />
- <xsl:with-param name="pToken" select="','" />
- <xsl:with-param name="pSubst" select="'!'" />
- </xsl:call-template>
- <xsl:text>!</xsl:text>
- <xsl:apply-templates select="../../@name" />
- <xsl:text>}%&#xa;</xsl:text>
- </xsl:for-each>
- </xsl:if>
-
- <!-- Index ingredients into a flat list. -->
- <xsl:text> \index[flatingred]{</xsl:text>
- <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
- <xsl:text>!</xsl:text>
- <xsl:apply-templates select="ancestor::recipe/description/title" />
- <xsl:text>}%&#xa;</xsl:text>
-</xsl:template>
-
-<!--
- | Splits the ingredient preconditions using the Oxford serial comma:
- | http://www.mhonarc.org/archive/html/xsl-list/2008-06/msg00401.html
- | by Ronnie Royston
- +-->
-<xsl:template match="@condition" mode="prep">
- <xsl:variable name="actions" select="str:tokenize( ., ',' )" />
- <xsl:variable name="tokens" select="count( $actions )" />
-
- <xsl:variable name="condition">
- <xsl:for-each select="$actions">
- <xsl:choose>
- <xsl:when test="$tokens > 2">
- <xsl:choose>
- <xsl:when test="position()=1">
- <xsl:value-of select="concat(' ', ., ',')"/>
- </xsl:when>
- <xsl:when test="position()=last()">
- <xsl:value-of select="concat(' and ', .)"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="concat(' ', .,',')"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:when>
- <xsl:when test="$tokens = 2">
- <xsl:choose>
- <xsl:when test="position()=1">
- <xsl:value-of select="concat(' ', .,' and')"/>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="concat(' ', .)"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:when>
- <xsl:otherwise>
- <xsl:value-of select="concat(' ', .)"/>
- </xsl:otherwise>
- </xsl:choose>
- </xsl:for-each>
- </xsl:variable>
-
- <xsl:call-template name="escape">
- <xsl:with-param
- name="e" select="string:capitalize(normalize-space($condition))" />
- </xsl:call-template>
-</xsl:template>
-
-<!--
- | Splits a delimited string by substitution text.
- +-->
-<xsl:template name="replace">
- <xsl:param name="pText"/>
- <xsl:param name="pToken"/>
- <xsl:param name="pSubst" />
-
- <xsl:choose>
- <!-- End of recursion. -->
- <xsl:when test="string-length($pText) = 0" />
-
- <!-- While there are more tokens... -->
- <xsl:when test="contains($pText, $pToken)">
- <xsl:value-of select="substring-before($pText, $pToken)"/>
- <xsl:value-of select="$pSubst"/>
-
- <xsl:call-template name="replace">
- <xsl:with-param name="pText" select="substring-after($pText, $pToken)"/>
- <xsl:with-param name="pToken" select="$pToken"/>
- <xsl:with-param name="pSubst" select="$pSubst" />
- </xsl:call-template>
- </xsl:when>
- <xsl:otherwise>
- <!-- The text had no tokens, so display it without parsing. -->
- <xsl:value-of select="$pText"/>
- </xsl:otherwise>
- </xsl:choose>
-</xsl:template>
-
-<xsl:template match="directions[1]">
- <xsl:text>\startinstructions&#xa;&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="directions[descendant::text()]">
- <xsl:text>\begin{instructions}&#xa;</xsl:text>
- <xsl:apply-templates select="@label" />
- <xsl:apply-templates select="step" />
- <xsl:text>\end{instructions}&#xa;&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="step">
- <xsl:text> \item </xsl:text>
- <xsl:value-of select="normalize-space(string:capitalize(@action))" />
- <xsl:text> </xsl:text>
- <xsl:apply-templates mode="escape" />
- <xsl:text>.&#xa;</xsl:text>
-</xsl:template>
-
-<!--
- | Provides output escaping around user-defined text. This ensures that
- | users cannot embed malicious LaTeX code inside their recipes.
- |
- | All text that must be escaped is transformed through this template.
- |
- | @param e - The string to escape.
- | @see http://stackoverflow.com/a/2627303/59087
- | @see http://tex.stackexchange.com/q/97448/2148
- +-->
-<xsl:template name="escape">
- <xsl:param name="e" />
-
- <xsl:call-template name="replace">
- <xsl:with-param name="pText" select="$e" />
- <xsl:with-param name="pToken" select="'&amp;'" />
- <xsl:with-param name="pSubst" select="'\&amp;'" />
- </xsl:call-template>
-</xsl:template>
-
-<!--
- | Normalizes and capitalizes all the words of an escaped string.
- |
- | @param ec - The string to normalize, capitalize, and escape.
- +-->
-<xsl:template name="escape-capitalize">
+ <xsl:import href="string-utilities.xsl" />
+ <xsl:import href="fractions.xsl" />
+
+ <xsl:output method="text" encoding="utf-8" />
+
+ <xsl:strip-space elements="*" />
+
+ <!-- LaTeX premable inserted at the top of the document as text. -->
+ <xsl:param name="preamble" select="''" />
+
+ <!-- If true, generate the table of contents. -->
+ <xsl:param name="recipe-toc" select="true()" />
+
+ <!-- If true, generate the index of ingredients (and categories). -->
+ <xsl:param name="ingredient-index" select="true()" />
+
+ <!-- If true, generate the copyright page. -->
+ <xsl:param name="book-copyright" select="true()" />
+
+ <!-- If true, include a front cover image. -->
+ <xsl:param name="book-front-cover" select="true()" />
+
+ <!-- If false, the will use a print layout. -->
+ <xsl:param name="ebook" select="true()" />
+
+ <!-- If true, the page number appears on full-page photo pages. -->
+ <xsl:param name="page-photo-footer" select="false()" />
+
+<!--
+ | Citation style format; valid values:
+ | endcitations (default)
+ | page-photo-footercitations
+ | inlinecitations
+ +-->
+ <xsl:param name="citation-style" select="'endcitations'" />
+
+<!-- These will come from the XML...
+<xsl:param name="book-back-cover" select="true()" />
+-->
+
+<!-- Keys -->
+<xsl:key name="ingredient-id" match="/recipe-book/ingredients/ingredient" use="@id" />
+<xsl:key name="recipe-by-category" match="recipe" use="tags/tag[1]" />
+
+<!--
+ | A recipe book consists of book details, recipes, categories, and tags.
+ +-->
+ <xsl:template match="/recipe-book">
+ <xsl:text>\documentclass</xsl:text>
+ <xsl:text>[</xsl:text>
+ <xsl:if test="not($ebook)">
+ <xsl:text>print,</xsl:text>
+ </xsl:if>
+ <xsl:if test="$page-photo-footer">
+ <xsl:text>fullpagepage-photo-footer,</xsl:text>
+ </xsl:if>
+ <xsl:value-of select="$citation-style"/>
+ <xsl:text>]</xsl:text>
+ <xsl:text>{recipe-book}&#xa;</xsl:text>
+ <xsl:text>\usepackage{hyperref}&#xa;</xsl:text>
+ <xsl:value-of select="$preamble" />
+ <xsl:text>\makeindex[flatingred]&#xa;</xsl:text>
+ <xsl:text>\makeindex[catingred]&#xa;</xsl:text>
+ <xsl:apply-templates select='book' />
+ <xsl:text>\begin{document}&#xa;</xsl:text>
+ <xsl:text>\frontmatter&#xa;</xsl:text>
+ <xsl:if test="$book-front-cover">
+ <xsl:text>\makecover</xsl:text>
+ <xsl:if test="$book-copyright">
+ <xsl:text>[copyrightpage]</xsl:text>
+ </xsl:if>
+ <xsl:text>&#xa;</xsl:text>
+ </xsl:if>
+ <xsl:apply-templates select="book/overview" mode="book-overview" />
+ <xsl:if test="$recipe-toc">
+ <xsl:text>\cleartoverso\tableofcontents*&#xa;</xsl:text>
+ </xsl:if>
+ <xsl:text>\mainmatter&#xa;</xsl:text>
+
+ <!--
+ | "Muenchian Method" for sorting tagged recipes into chapters, described in
+ | XSLT Cookbook, by Sal Mangano, O'Reilly, pg195.
+ +-->
+ <xsl:for-each select="recipe[count(. | key('recipe-by-category',tags/tag[1])[1]) = 1]">
+ <xsl:variable name="current-grouping-key" select="tags/tag[1]"/>
+ <xsl:if test="$current-grouping-key != ''">
+ <xsl:text>&#xa;\recipecategory{</xsl:text>
+ <xsl:apply-templates select="$current-grouping-key" mode="escape-capitalize"/>
+ <xsl:text>}&#xa;&#xa;</xsl:text>
+ <xsl:variable name="current-group"
+ select="key('recipe-by-category', $current-grouping-key)" />
+ <xsl:for-each select="$current-group">
+ <xsl:apply-templates select="." />
+ </xsl:for-each>
+ </xsl:if>
+ </xsl:for-each>
+
+ <!--
+ | All untagged recipes to go into a "Recipes" chapter.
+ +-->
+ <xsl:if test="recipe[tags/tag = '' or not(tags/tag)]">
+ <xsl:text>&#xa;\recipecategory{Recipes}&#xa;&#xa;</xsl:text>
+ <xsl:apply-templates select="recipe[tags/tag = '' or not(tags/tag)]" />
+ </xsl:if>
+
+ <xsl:text>\backmatter&#xa;</xsl:text>
+ <xsl:text>\renewcommand{\clearforchapter}{\newpage}&#xa;</xsl:text>
+ <xsl:if test="$ingredient-index">
+ <xsl:text>\renewcommand{\indexname}{Ingredients in Alphabetic Order}&#xa;</xsl:text>
+ <xsl:text>\printindex[flatingred]&#xa;</xsl:text>
+ <xsl:text>\renewcommand{\indexname}{Ingredients by Categories}&#xa;</xsl:text>
+ <xsl:text>\reduceindexindent&#xa;</xsl:text>
+ <xsl:text>\printindex[catingred]&#xa;</xsl:text>
+ </xsl:if>
+ <xsl:text>\end{document}&#xa;</xsl:text>
+</xsl:template>
+
+<!--
+ | Book meta information.
+ +-->
+ <xsl:template match="book">
+ <xsl:text>\title{</xsl:text>
+ <xsl:apply-templates select="title" mode="escape-capitalize" />
+ <xsl:text>}&#xa;</xsl:text>
+ <xsl:text>\date{</xsl:text>
+ <xsl:apply-templates select="created" mode="escape-capitalize" />
+ <xsl:text>}&#xa;</xsl:text>
+ <xsl:text>\author{</xsl:text>
+ <xsl:apply-templates select="author" mode="escape-capitalize" />
+ <xsl:text>}&#xa;</xsl:text>
+ <xsl:apply-templates select="photo" mode="book" />
+</xsl:template>
+
+<xsl:template match="overview" mode="book-overview">
+ <xsl:text>&#xa;\begin{overview}{</xsl:text>
+ <xsl:apply-templates select="@label" mode="escape-capitalize" />
+ <xsl:text>}&#xa;</xsl:text>
+ <xsl:if test="../photo[@use='overview']">
+ <xsl:apply-templates select="../photo[@use='overview']/uri" mode="book-overview"/>
+ </xsl:if>
+ <xsl:apply-templates select="p" />
+ <xsl:text>\end{overview}&#xa;&#xa;</xsl:text>
+</xsl:template>
+
+<!--
+ | Allows users to provide descriptive text for the book.
+ +-->
+ <xsl:template match="p">
+ <xsl:apply-templates mode="escape" />
+ <xsl:text>&#xa;&#xa;</xsl:text>
+</xsl:template>
+
+<!--
+ | Transforms a recipe from XML into LaTeX.
+ +-->
+ <xsl:template match="recipe">
+ <!--
+ | If a facing full-page photo is used, check whether a filler photo
+ | (e.g., advertsement) is required to make up the page number.
+ +-->
+ <xsl:if test="description/photo[@use='one-page' or @use='two-page']">
+ <xsl:text>\checkfiller&#xa;</xsl:text>
+ </xsl:if>
+ <xsl:text>\begin{recipe}</xsl:text>
+ <xsl:apply-templates select="description/citation[descendant::*]" />
+ <xsl:text>{</xsl:text>
+ <xsl:apply-templates select="description/title" />
+ <xsl:text>}&#xa;</xsl:text>
+ <xsl:apply-templates select="description/photo[@use='recipe-inset']" mode="recipe" />
+ <xsl:apply-templates />
+ <xsl:apply-templates select="description/photo[@use='one-page' or @use='two-page']" mode="recipe" />
+ <xsl:text>\end{recipe}&#xa;&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="photo[@use='front-cover']" mode="book">
+ <xsl:apply-templates select="uri" mode="book" />
+</xsl:template>
+
+<xsl:template match="photo[@use='overview']" mode="book" />
+
+<!--
+<xsl:template match="photo[@use='recipe-inset']" mode="recipe">
+ <xsl:apply-templates select="uri" mode="recipe-inset" />
+</xsl:template>
+
+<xsl:template match="photo[@use='one-page']" mode="recipe">
+ <xsl:apply-templates select="uri" mode="one-page" />
+</xsl:template>
+
+<xsl:template match="photo[@use='two-page']" mode="recipe">
+ <xsl:apply-templates select="uri" mode="two-page" />
+</xsl:template>
+-->
+<xsl:template match="photo" mode="recipe">
+ <xsl:text>\photo[embed=</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@use='recipe-inset'">
+ <xsl:text>inset</xsl:text>
+ </xsl:when>
+ <xsl:when test="@use='one-page'">
+ <xsl:text>fullpage</xsl:text>
+ </xsl:when>
+ <xsl:when test="@use='two-page'">
+ <xsl:text>twopagespread</xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ <xsl:text>,</xsl:text>
+ <xsl:apply-templates match="citation[descendant::*]" />
+ <xsl:text>]{</xsl:text>
+ <xsl:apply-templates select="uri" mode="escape"/>
+ <xsl:text>}&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="uri" mode="book-overview">
+ <xsl:text>\begin{wrapfigure}{l}{.45\textwidth}&#xa;</xsl:text>
+ <xsl:text>\insetphoto[</xsl:text>
+ <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
+ <xsl:text>]{</xsl:text>
+ <xsl:apply-templates />
+ <xsl:text>}&#xa;</xsl:text>
+ <xsl:text>\end{wrapfigure}&#xa;&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="uri" mode="book">
+ <xsl:if test="$book-front-cover">
+ <xsl:text>\frontcoverphoto[</xsl:text>
+ <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
+ <xsl:text>]{</xsl:text>
+ <xsl:apply-templates />
+ <xsl:text>}&#xa;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<!--
+<xsl:template match="uri" mode="recipe-inset">
+ <xsl:text>\insetphoto</xsl:text>
+ <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
+ <xsl:text>{</xsl:text>
+ <xsl:apply-templates />
+ <xsl:text>}&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="uri" mode="one-page">
+ <xsl:text>\fullpagephoto</xsl:text>
+ <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
+ <xsl:text>{</xsl:text>
+ <xsl:apply-templates />
+ <xsl:text>}&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="uri" mode="two-page">
+ <xsl:text>\twopagespreadphoto</xsl:text>
+ <xsl:apply-templates select="following-sibling::citation[descendant::*]" />
+ <xsl:text>{</xsl:text>
+ <xsl:apply-templates />
+ <xsl:text>}&#xa;</xsl:text>
+</xsl:template>
+-->
+<xsl:template match="citation[descendant::*]">
+ <!-- <xsl:text>[</xsl:text>-->
+ <xsl:if test="author">
+ <xsl:text>author={</xsl:text>
+ <xsl:apply-templates select="author" mode="escape" />
+ <xsl:text>},</xsl:text>
+ </xsl:if>
+ <xsl:if test="author/@url">
+ <xsl:text>authorurl={</xsl:text>
+ <xsl:apply-templates select="author/@url" mode="escape" />
+ <xsl:text>},</xsl:text>
+ </xsl:if>
+ <xsl:if test="license">
+ <xsl:text>license={</xsl:text>
+ <xsl:apply-templates select="license" mode="escape" />
+ <xsl:text>},</xsl:text>
+ </xsl:if>
+ <xsl:if test="license/@url">
+ <xsl:text>licenseurl={</xsl:text>
+ <xsl:apply-templates select="license/@url" mode="escape" />
+ <xsl:text>},</xsl:text>
+ </xsl:if>
+ <!-- <xsl:text>]</xsl:text> -->
+</xsl:template>
+
+<!--
+<xsl:template match="description/citation[descendant::*]">
+ <xsl:if test="author">
+ <xsl:text>\recipecredit</xsl:text>
+ <xsl:if test="author/@url">
+ <xsl:text>[</xsl:text>
+ <xsl:apply-templates select="author/@url" mode="escape" />
+ <xsl:text>]</xsl:text>
+ </xsl:if>
+ <xsl:text>{</xsl:text>
+ <xsl:apply-templates select="author" mode="escape" />
+ <xsl:text>}&#xa;</xsl:text>
+ </xsl:if>
+
+ <xsl:if test="license">
+ <xsl:text>\recipelicense</xsl:text>
+ <xsl:if test="license/@url">
+ <xsl:text>[</xsl:text>
+ <xsl:apply-templates select="license/@url" mode="escape" />
+ <xsl:text>]</xsl:text>
+ </xsl:if>
+ <xsl:text>{</xsl:text>
+ <xsl:apply-templates select="license" mode="escape" />
+ <xsl:text>}&#xa;</xsl:text>
+ </xsl:if>
+</xsl:template>
+-->
+
+<xsl:template match="description/title">
+ <xsl:apply-templates mode="escape-capitalize" />
+</xsl:template>
+
+<!-- Pre-heated oven temperature, not to be confused with ingredient prep. -->
+<xsl:template match="preparation">
+ <xsl:apply-templates />
+</xsl:template>
+
+<xsl:template match="oven">
+ <xsl:text>\oven{</xsl:text>
+ <xsl:value-of select="@temperature" />
+ <xsl:text>\,</xsl:text>
+ <xsl:choose>
+ <xsl:when test="@unit='C' or @unit='F'">
+ <xsl:text>\textdegree </xsl:text>
+ </xsl:when>
+ </xsl:choose>
+ <xsl:value-of select="@unit" />
+ <xsl:text>}&#xa;&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="equipment[descendant::text()]">
+ <xsl:text>\begin{equipment}&#xa;</xsl:text>
+ <!-- Select non-empty child nodes. -->
+ <xsl:for-each select="*[child::*]">
+ <xsl:text> \item[</xsl:text>
+ <xsl:call-template name="escape-capitalize">
+ <xsl:with-param name="ec" select="name()" />
+ </xsl:call-template>
+ <xsl:text>] </xsl:text>
+ <!-- Oxford-comma the "object" children nodes -->
+ <xsl:call-template name="escape-capitalize-oxfordcomma">
+ <xsl:with-param name="itemlist" select="object" />
+ </xsl:call-template>
+ <xsl:text>&#xa;</xsl:text>
+ </xsl:for-each>
+ <xsl:text>\end{equipment}&#xa;&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template name="escape-capitalize-oxfordcomma">
+ <xsl:param name="itemlist" />
+ <xsl:variable name="itemcount" select="count($itemlist)" />
+ <xsl:variable name="items">
+ <xsl:for-each select="$itemlist">
+ <xsl:choose>
+ <xsl:when test="$itemcount > 2">
+ <xsl:choose>
+ <xsl:when test="position()=1">
+ <xsl:value-of select="concat(' ', ., ',')"/>
+ </xsl:when>
+ <xsl:when test="position()=last()">
+ <xsl:value-of select="concat(' and ', .)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', .,',')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="$itemcount = 2">
+ <xsl:choose>
+ <xsl:when test="position()=1">
+ <xsl:value-of select="concat(' ', .,' and')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', .)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', .)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </xsl:variable>
+
+ <xsl:call-template name="escape">
+ <xsl:with-param
+ name="e" select="string:capitalize(normalize-space($items))" />
+ </xsl:call-template>
+ </xsl:template>
+
+<!--
+ | Puts the value of all labels (ingredients and directions) in brackets.
+ +-->
+ <xsl:template match="@label">
+ <xsl:text>[</xsl:text>
+ <xsl:call-template name="escape-capitalize">
+ <xsl:with-param name="ec" select="." />
+ </xsl:call-template>
+ <xsl:text>]</xsl:text>
+ <xsl:text>&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="ingredients[parent::recipe]">
+ <xsl:if test="count(ingredient[not(@substitute) or @substitute='']) &gt; 0">
+ <xsl:text>\begin{ingredients}&#xa;</xsl:text>
+ <xsl:apply-templates select="@label" />
+ <xsl:apply-templates select="ingredient[not(@substitute) or @substitute='']" />
+ <xsl:text>\end{ingredients}&#xa;&#xa;</xsl:text>
+ </xsl:if>
+
+ <!--
+ | If non-empty conditions exist, insert a preparation section.
+ +-->
+ <xsl:if test="ingredient[@condition != '']">
+ <xsl:text>\begin{preparation}&#xa;</xsl:text>
+ <xsl:apply-templates select="ingredient[@condition != '']" mode="prep" />
+ <xsl:text>\end{preparation}&#xa;</xsl:text>
+ </xsl:if>
+
+ <!--
+ | If non-empty substitutions, insert a substitution section.
+ +-->
+ <xsl:if test="ingredient[@substitute != '']">
+ <xsl:text>\begin{substitution}&#xa;</xsl:text>
+ <xsl:apply-templates select="ingredient[@substitute != '']" mode="subst" />
+ <xsl:text>\end{substitution}&#xa;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+<xsl:template match="ingredient">
+ <xsl:text> \ingred{</xsl:text>
+
+ <xsl:call-template name="utf-fraction">
+ <xsl:with-param name="quantity" select="@min-quantity" />
+ </xsl:call-template>
+
+ <xsl:if test="@max-quantity">
+ <xsl:text>--</xsl:text>
+
+ <xsl:call-template name="utf-fraction">
+ <xsl:with-param name="quantity" select="@max-quantity" />
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:text>}{</xsl:text>
+ <xsl:value-of select="@unit" />
+ <xsl:text>}{</xsl:text>
+ <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
+ <xsl:text>}%&#xa;</xsl:text>
+
+ <!-- Put the ingredients in a categorized list. -->
+ <xsl:if test="key('ingredient-id',@id)/categories/category">
+ <xsl:for-each select="key('ingredient-id',@id)/categories/category">
+ <xsl:text> \index[catingred]{</xsl:text>
+ <xsl:call-template name="replace">
+ <xsl:with-param name="pText" select="@name" />
+ <xsl:with-param name="pToken" select="','" />
+ <xsl:with-param name="pSubst" select="'!'" />
+ </xsl:call-template>
+ <xsl:text>!</xsl:text>
+ <xsl:apply-templates select="../../@name" />
+ <xsl:text>}%&#xa;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+
+ <!-- Index ingredients into a flat list. -->
+ <xsl:text> \index[flatingred]{</xsl:text>
+ <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
+ <xsl:text>!</xsl:text>
+ <xsl:apply-templates select="ancestor::recipe/description/title" />
+ <xsl:text>}%&#xa;</xsl:text>
+</xsl:template>
+
+<!--
+ | Extracts preparation instructions from ingredients.
+ +-->
+ <xsl:template match="ingredient[@condition != '']" mode="prep">
+ <xsl:for-each select=".">
+ <xsl:text>\item </xsl:text>
+ <xsl:apply-templates select="@condition" mode="prep" />
+ <xsl:text> the </xsl:text>
+ <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
+ <xsl:text>.&#xa;</xsl:text>
+ </xsl:for-each>
+</xsl:template>
+
+<!--
+ | Extracts substitution instructions from ingredients.
+ +-->
+ <xsl:template match="ingredient[@substitute != '']" mode="subst">
+ <xsl:text> \item </xsl:text>
+
+ <xsl:call-template name="utf-fraction">
+ <xsl:with-param name="quantity" select="@min-quantity" />
+ </xsl:call-template>
+
+ <xsl:if test="@max-quantity">
+ <xsl:text>--</xsl:text>
+
+ <xsl:call-template name="utf-fraction">
+ <xsl:with-param name="quantity" select="@max-quantity" />
+ </xsl:call-template>
+ </xsl:if>
+ <xsl:text> </xsl:text>
+ <xsl:value-of select="@unit" />
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
+ <xsl:text> for </xsl:text>
+ <xsl:apply-templates select="key('ingredient-id', @substitute)/@name" />
+ <xsl:text>&#xa;</xsl:text>
+
+ <!-- Put the ingredients in a categorized list. -->
+ <xsl:if test="key('ingredient-id',@id)/categories/category">
+ <xsl:for-each select="key('ingredient-id',@id)/categories/category">
+ <xsl:text> \index[catingred]{</xsl:text>
+ <xsl:call-template name="replace">
+ <xsl:with-param name="pText" select="@name" />
+ <xsl:with-param name="pToken" select="','" />
+ <xsl:with-param name="pSubst" select="'!'" />
+ </xsl:call-template>
+ <xsl:text>!</xsl:text>
+ <xsl:apply-templates select="../../@name" />
+ <xsl:text>}%&#xa;</xsl:text>
+ </xsl:for-each>
+ </xsl:if>
+
+ <!-- Index ingredients into a flat list. -->
+ <xsl:text> \index[flatingred]{</xsl:text>
+ <xsl:apply-templates select="key('ingredient-id', @id)/@name" />
+ <xsl:text>!</xsl:text>
+ <xsl:apply-templates select="ancestor::recipe/description/title" />
+ <xsl:text>}%&#xa;</xsl:text>
+</xsl:template>
+
+<!--
+ | Splits the ingredient preconditions using the Oxford serial comma:
+ | http://www.mhonarc.org/archive/html/xsl-list/2008-06/msg00401.html
+ | by Ronnie Royston
+ +-->
+ <xsl:template match="@condition" mode="prep">
+ <xsl:variable name="actions" select="str:tokenize( ., ',' )" />
+ <xsl:variable name="tokens" select="count( $actions )" />
+
+ <xsl:variable name="condition">
+ <xsl:for-each select="$actions">
+ <xsl:choose>
+ <xsl:when test="$tokens > 2">
+ <xsl:choose>
+ <xsl:when test="position()=1">
+ <xsl:value-of select="concat(' ', ., ',')"/>
+ </xsl:when>
+ <xsl:when test="position()=last()">
+ <xsl:value-of select="concat(' and ', .)"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', .,',')"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:when test="$tokens = 2">
+ <xsl:choose>
+ <xsl:when test="position()=1">
+ <xsl:value-of select="concat(' ', .,' and')"/>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', .)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="concat(' ', .)"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:for-each>
+ </xsl:variable>
+
+ <xsl:call-template name="escape">
+ <xsl:with-param
+ name="e" select="string:capitalize(normalize-space($condition))" />
+ </xsl:call-template>
+ </xsl:template>
+
+<!--
+ | Splits a delimited string by substitution text.
+ +-->
+ <xsl:template name="replace">
+ <xsl:param name="pText"/>
+ <xsl:param name="pToken"/>
+ <xsl:param name="pSubst" />
+
+ <xsl:choose>
+ <!-- End of recursion. -->
+ <xsl:when test="string-length($pText) = 0" />
+
+ <!-- While there are more tokens... -->
+ <xsl:when test="contains($pText, $pToken)">
+ <xsl:value-of select="substring-before($pText, $pToken)"/>
+ <xsl:value-of select="$pSubst"/>
+
+ <xsl:call-template name="replace">
+ <xsl:with-param name="pText" select="substring-after($pText, $pToken)"/>
+ <xsl:with-param name="pToken" select="$pToken"/>
+ <xsl:with-param name="pSubst" select="$pSubst" />
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <!-- The text had no tokens, so display it without parsing. -->
+ <xsl:value-of select="$pText"/>
+ </xsl:otherwise>
+ </xsl:choose>
+</xsl:template>
+
+<xsl:template match="directions[1]">
+ <xsl:text>\startinstructions&#xa;&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="directions[descendant::text()]">
+ <xsl:text>\begin{instructions}&#xa;</xsl:text>
+ <xsl:apply-templates select="@label" />
+ <xsl:apply-templates select="step" />
+ <xsl:text>\end{instructions}&#xa;&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="step">
+ <xsl:text> \item </xsl:text>
+ <xsl:value-of select="normalize-space(string:capitalize(@action))" />
+ <xsl:text> </xsl:text>
+ <xsl:apply-templates mode="escape" />
+ <xsl:text>.&#xa;</xsl:text>
+</xsl:template>
+
+<!--
+ | Provides output escaping around user-defined text. This ensures that
+ | users cannot embed malicious LaTeX code inside their recipes.
+ |
+ | All text that must be escaped is transformed through this template.
+ |
+ | @param e - The string to escape.
+ | @see http://stackoverflow.com/a/2627303/59087
+ | @see http://tex.stackexchange.com/q/97448/2148
+ +-->
+ <xsl:template name="escape">
+ <xsl:param name="e" />
+
+ <xsl:call-template name="replace">
+ <xsl:with-param name="pText" select="$e" />
+ <xsl:with-param name="pToken" select="'&amp;'" />
+ <xsl:with-param name="pSubst" select="'\&amp;'" />
+ </xsl:call-template>
+</xsl:template>
+
+<!--
+ | Normalizes and capitalizes all the words of an escaped string.
+ |
+ | @param ec - The string to normalize, capitalize, and escape.
+ +-->
+ <xsl:template name="escape-capitalize">
<xsl:param name="ec" />
Delta659 lines added, 616 lines removed, 43-line increase