Dave Jarvis' Repositories

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

Formatting. Escaped ampersands.

Author Dave Jarvis <email>
Date 2013-02-27 19:08:02 GMT-0800
Commit 83b17c03d747412fa68a212fe78e571f02c672c1
Parent d27f7ed
xsl/latex.xsl
<xsl:text>\mainmatter&#xa;</xsl:text>
- <!--
- <xsl:apply-templates select="*[name()!='book']" />
- -->
- <!--
- | "Muenchian Method" as described in
- | XSLT Cookbook, by Sal Mangano, O'Reilly, pg195
- | for sorting tagged recipes into "chapters"
- +-->
- <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>
-
-<!--
- | Meta information about the book.
- +-->
-<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>
-
-<!--
- | Transforms the recipe from XML into LaTeX.
- +-->
-<xsl:template match="recipe">
- <!--
- | If a facing full-page photo is used, check whether
- | a filler photo (advert) 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/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='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="uri" mode="book">
- <xsl:if test="$book-front-cover">
- <xsl:text>\frontcoverphoto{</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 />
- <xsl:text>}&#xa;</xsl:text>
-</xsl:template>
-
-<xsl:template match="uri" mode="one-page">
- <xsl:text>\fullpagephoto{</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 />
- <xsl:text>}&#xa;</xsl:text>
-</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:text>\begin{ingredients}&#xa;</xsl:text>
- <xsl:apply-templates select="@label" />
- <xsl:apply-templates select="ingredient" />
- <xsl:text>\end{ingredients}&#xa;&#xa;</xsl:text>
-
- <!--
- | 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>
-</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="split">
- <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>
-
- <!-- Put the ingredients in 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>
-
-<!--
- | 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="split">
- <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="split">
- <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>
-
-<!--
- | Splits a space-delimited string, wrapping values in an "id" element.
- +-->
-<xsl:template name="output-tokens">
- <xsl:param name="list" />
-
- <xsl:variable name="newlist" select="concat(normalize-space($list), ' ')" />
- <xsl:variable name="first" select="substring-before($newlist, ' ')" />
- <xsl:variable name="remaining" select="substring-after($newlist, ' ')" />
- <id>
- <xsl:value-of select="$first" />
- </id>
- <xsl:if test="$remaining">
- <xsl:call-template name="output-tokens">
- <xsl:with-param name="list" select="$remaining" />
- </xsl:call-template>
- </xsl:if>
-</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 needs to be escaped ultimately 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:value-of select="$e" />
+ <!--
+ | "Muenchian Method" as described in
+ | XSLT Cookbook, by Sal Mangano, O'Reilly, pg195
+ | for sorting tagged recipes into "chapters"
+ +-->
+ <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>
+
+<!--
+ | Meta information about the book.
+ +-->
+<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>
+
+<!--
+ | Transforms the recipe from XML into LaTeX.
+ +-->
+<xsl:template match="recipe">
+ <!--
+ | If a facing full-page photo is used, check whether
+ | a filler photo (advert) 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/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='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="uri" mode="book">
+ <xsl:if test="$book-front-cover">
+ <xsl:text>\frontcoverphoto{</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 />
+ <xsl:text>}&#xa;</xsl:text>
+</xsl:template>
+
+<xsl:template match="uri" mode="one-page">
+ <xsl:text>\fullpagephoto{</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 />
+ <xsl:text>}&#xa;</xsl:text>
+</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:text>\begin{ingredients}&#xa;</xsl:text>
+ <xsl:apply-templates select="@label" />
+ <xsl:apply-templates select="ingredient" />
+ <xsl:text>\end{ingredients}&#xa;&#xa;</xsl:text>
+
+ <!--
+ | 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>
+</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>
+
+ <!-- Put the ingredients in 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>
+
+<!--
+ | 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>
+
+<!--
+ | Splits a space-delimited string, wrapping values in an "id" element.
+ +-->
+<xsl:template name="output-tokens">
+ <xsl:param name="list" />
+
+ <xsl:variable name="newlist" select="concat(normalize-space($list), ' ')" />
+ <xsl:variable name="first" select="substring-before($newlist, ' ')" />
+ <xsl:variable name="remaining" select="substring-after($newlist, ' ')" />
+ <id>
+ <xsl:value-of select="$first" />
+ </id>
+ <xsl:if test="$remaining">
+ <xsl:call-template name="output-tokens">
+ <xsl:with-param name="list" select="$remaining" />
+ </xsl:call-template>
+ </xsl:if>
+</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:value-of select="$e" /-->
</xsl:template>
Delta 378 lines added, 377 lines removed, 1-line increase