| | 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}
</xsl:text> |
| | - <xsl:text>\usepackage{hyperref}
</xsl:text> |
| | - <xsl:value-of select="$preamble" /> |
| | - <xsl:text>\makeindex[flatingred]
</xsl:text> |
| | - <xsl:text>\makeindex[catingred]
</xsl:text> |
| | - <xsl:apply-templates select='book' /> |
| | - <xsl:text>\begin{document}
</xsl:text> |
| | - <xsl:text>\frontmatter
</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>
</xsl:text> |
| | - </xsl:if> |
| | - <xsl:apply-templates select="book/overview" mode="book-overview" /> |
| | - <xsl:if test="$recipe-toc"> |
| | - <xsl:text>\cleartoverso\tableofcontents*
</xsl:text> |
| | - </xsl:if> |
| | - <xsl:text>\mainmatter
</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>
\recipecategory{</xsl:text> |
| | - <xsl:apply-templates select="$current-grouping-key" mode="escape-capitalize"/> |
| | - <xsl:text>}

</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>
\recipecategory{Recipes}

</xsl:text> |
| | - <xsl:apply-templates select="recipe[tags/tag = '' or not(tags/tag)]" /> |
| | - </xsl:if> |
| | - |
| | - <xsl:text>\backmatter
</xsl:text> |
| | - <xsl:text>\renewcommand{\clearforchapter}{\newpage}
</xsl:text> |
| | - <xsl:if test="$ingredient-index"> |
| | - <xsl:text>\renewcommand{\indexname}{Ingredients in Alphabetic Order}
</xsl:text> |
| | - <xsl:text>\printindex[flatingred]
</xsl:text> |
| | - <xsl:text>\renewcommand{\indexname}{Ingredients by Categories}
</xsl:text> |
| | - <xsl:text>\reduceindexindent
</xsl:text> |
| | - <xsl:text>\printindex[catingred]
</xsl:text> |
| | - </xsl:if> |
| | - <xsl:text>\end{document}
</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>}
</xsl:text> |
| | - <xsl:text>\date{</xsl:text> |
| | - <xsl:apply-templates select="created" mode="escape-capitalize" /> |
| | - <xsl:text>}
</xsl:text> |
| | - <xsl:text>\author{</xsl:text> |
| | - <xsl:apply-templates select="author" mode="escape-capitalize" /> |
| | - <xsl:text>}
</xsl:text> |
| | - <xsl:apply-templates select="photo" mode="book" /> |
| | -</xsl:template> |
| | - |
| | -<xsl:template match="overview" mode="book-overview"> |
| | - <xsl:text>
\begin{overview}{</xsl:text> |
| | - <xsl:apply-templates select="@label" mode="escape-capitalize" /> |
| | - <xsl:text>}
</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}

</xsl:text> |
| | -</xsl:template> |
| | - |
| | -<!-- |
| | - | Allows users to provide descriptive text for the book. |
| | - +--> |
| | -<xsl:template match="p"> |
| | - <xsl:apply-templates mode="escape" /> |
| | - <xsl:text>

</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
</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>}
</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}

</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>}
</xsl:text> |
| | -</xsl:template> |
| | - |
| | -<xsl:template match="uri" mode="book-overview"> |
| | - <xsl:text>\begin{wrapfigure}{l}{.45\textwidth}
</xsl:text> |
| | - <xsl:text>\insetphoto[</xsl:text> |
| | - <xsl:apply-templates select="following-sibling::citation[descendant::*]" /> |
| | - <xsl:text>]{</xsl:text> |
| | - <xsl:apply-templates /> |
| | - <xsl:text>}
</xsl:text> |
| | - <xsl:text>\end{wrapfigure}

</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>}
</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>}
</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>}
</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>}
</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>}
</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>}
</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>}

</xsl:text> |
| | -</xsl:template> |
| | - |
| | -<xsl:template match="equipment[descendant::text()]"> |
| | - <xsl:text>\begin{equipment}
</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>
</xsl:text> |
| | - </xsl:for-each> |
| | - <xsl:text>\end{equipment}

</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>
</xsl:text> |
| | -</xsl:template> |
| | - |
| | -<xsl:template match="ingredients[parent::recipe]"> |
| | - <xsl:if test="count(ingredient[not(@substitute) or @substitute='']) > 0"> |
| | - <xsl:text>\begin{ingredients}
</xsl:text> |
| | - <xsl:apply-templates select="@label" /> |
| | - <xsl:apply-templates select="ingredient[not(@substitute) or @substitute='']" /> |
| | - <xsl:text>\end{ingredients}

</xsl:text> |
| | - </xsl:if> |
| | - |
| | - <!-- |
| | - | If non-empty conditions exist, insert a preparation section. |
| | - +--> |
| | - <xsl:if test="ingredient[@condition != '']"> |
| | - <xsl:text>\begin{preparation}
</xsl:text> |
| | - <xsl:apply-templates select="ingredient[@condition != '']" mode="prep" /> |
| | - <xsl:text>\end{preparation}
</xsl:text> |
| | - </xsl:if> |
| | - |
| | - <!-- |
| | - | If non-empty substitutions, insert a substitution section. |
| | - +--> |
| | - <xsl:if test="ingredient[@substitute != '']"> |
| | - <xsl:text>\begin{substitution}
</xsl:text> |
| | - <xsl:apply-templates select="ingredient[@substitute != '']" mode="subst" /> |
| | - <xsl:text>\end{substitution}
</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>}%
</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>}%
</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>}%
</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>.
</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>
</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>}%
</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>}%
</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

</xsl:text> |
| | -</xsl:template> |
| | - |
| | -<xsl:template match="directions[descendant::text()]"> |
| | - <xsl:text>\begin{instructions}
</xsl:text> |
| | - <xsl:apply-templates select="@label" /> |
| | - <xsl:apply-templates select="step" /> |
| | - <xsl:text>\end{instructions}

</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>.
</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="'&'" /> |
| | - <xsl:with-param name="pSubst" select="'\&'" /> |
| | - </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}
</xsl:text> |
| | + <xsl:text>\usepackage{hyperref}
</xsl:text> |
| | + <xsl:value-of select="$preamble" /> |
| | + <xsl:text>\makeindex[flatingred]
</xsl:text> |
| | + <xsl:text>\makeindex[catingred]
</xsl:text> |
| | + <xsl:apply-templates select='book' /> |
| | + <xsl:text>\begin{document}
</xsl:text> |
| | + <xsl:text>\frontmatter
</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>
</xsl:text> |
| | + </xsl:if> |
| | + <xsl:apply-templates select="book/overview" mode="book-overview" /> |
| | + <xsl:if test="$recipe-toc"> |
| | + <xsl:text>\cleartoverso\tableofcontents*
</xsl:text> |
| | + </xsl:if> |
| | + <xsl:text>\mainmatter
</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>
\recipecategory{</xsl:text> |
| | + <xsl:apply-templates select="$current-grouping-key" mode="escape-capitalize"/> |
| | + <xsl:text>}

</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>
\recipecategory{Recipes}

</xsl:text> |
| | + <xsl:apply-templates select="recipe[tags/tag = '' or not(tags/tag)]" /> |
| | + </xsl:if> |
| | + |
| | + <xsl:text>\backmatter
</xsl:text> |
| | + <xsl:text>\renewcommand{\clearforchapter}{\newpage}
</xsl:text> |
| | + <xsl:if test="$ingredient-index"> |
| | + <xsl:text>\renewcommand{\indexname}{Ingredients in Alphabetic Order}
</xsl:text> |
| | + <xsl:text>\printindex[flatingred]
</xsl:text> |
| | + <xsl:text>\renewcommand{\indexname}{Ingredients by Categories}
</xsl:text> |
| | + <xsl:text>\reduceindexindent
</xsl:text> |
| | + <xsl:text>\printindex[catingred]
</xsl:text> |
| | + </xsl:if> |
| | + <xsl:text>\end{document}
</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>}
</xsl:text> |
| | + <xsl:text>\date{</xsl:text> |
| | + <xsl:apply-templates select="created" mode="escape-capitalize" /> |
| | + <xsl:text>}
</xsl:text> |
| | + <xsl:text>\author{</xsl:text> |
| | + <xsl:apply-templates select="author" mode="escape-capitalize" /> |
| | + <xsl:text>}
</xsl:text> |
| | + <xsl:apply-templates select="photo" mode="book" /> |
| | +</xsl:template> |
| | + |
| | +<xsl:template match="overview" mode="book-overview"> |
| | + <xsl:text>
\begin{overview}{</xsl:text> |
| | + <xsl:apply-templates select="@label" mode="escape-capitalize" /> |
| | + <xsl:text>}
</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}

</xsl:text> |
| | +</xsl:template> |
| | + |
| | +<!-- |
| | + | Allows users to provide descriptive text for the book. |
| | + +--> |
| | + <xsl:template match="p"> |
| | + <xsl:apply-templates mode="escape" /> |
| | + <xsl:text>

</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
</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>}
</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}

</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>}
</xsl:text> |
| | +</xsl:template> |
| | + |
| | +<xsl:template match="uri" mode="book-overview"> |
| | + <xsl:text>\begin{wrapfigure}{l}{.45\textwidth}
</xsl:text> |
| | + <xsl:text>\insetphoto[</xsl:text> |
| | + <xsl:apply-templates select="following-sibling::citation[descendant::*]" /> |
| | + <xsl:text>]{</xsl:text> |
| | + <xsl:apply-templates /> |
| | + <xsl:text>}
</xsl:text> |
| | + <xsl:text>\end{wrapfigure}

</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>}
</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>}
</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>}
</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>}
</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>}
</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>}
</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>}

</xsl:text> |
| | +</xsl:template> |
| | + |
| | +<xsl:template match="equipment[descendant::text()]"> |
| | + <xsl:text>\begin{equipment}
</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>
</xsl:text> |
| | + </xsl:for-each> |
| | + <xsl:text>\end{equipment}

</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>
</xsl:text> |
| | +</xsl:template> |
| | + |
| | +<xsl:template match="ingredients[parent::recipe]"> |
| | + <xsl:if test="count(ingredient[not(@substitute) or @substitute='']) > 0"> |
| | + <xsl:text>\begin{ingredients}
</xsl:text> |
| | + <xsl:apply-templates select="@label" /> |
| | + <xsl:apply-templates select="ingredient[not(@substitute) or @substitute='']" /> |
| | + <xsl:text>\end{ingredients}

</xsl:text> |
| | + </xsl:if> |
| | + |
| | + <!-- |
| | + | If non-empty conditions exist, insert a preparation section. |
| | + +--> |
| | + <xsl:if test="ingredient[@condition != '']"> |
| | + <xsl:text>\begin{preparation}
</xsl:text> |
| | + <xsl:apply-templates select="ingredient[@condition != '']" mode="prep" /> |
| | + <xsl:text>\end{preparation}
</xsl:text> |
| | + </xsl:if> |
| | + |
| | + <!-- |
| | + | If non-empty substitutions, insert a substitution section. |
| | + +--> |
| | + <xsl:if test="ingredient[@substitute != '']"> |
| | + <xsl:text>\begin{substitution}
</xsl:text> |
| | + <xsl:apply-templates select="ingredient[@substitute != '']" mode="subst" /> |
| | + <xsl:text>\end{substitution}
</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>}%
</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>}%
</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>}%
</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>.
</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>
</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>}%
</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>}%
</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

</xsl:text> |
| | +</xsl:template> |
| | + |
| | +<xsl:template match="directions[descendant::text()]"> |
| | + <xsl:text>\begin{instructions}
</xsl:text> |
| | + <xsl:apply-templates select="@label" /> |
| | + <xsl:apply-templates select="step" /> |
| | + <xsl:text>\end{instructions}

</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>.
</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="'&'" /> |
| | + <xsl:with-param name="pSubst" select="'\&'" /> |
| | + </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" /> |
| | |