Newer
Older
XML / modules / lists.xml
<?xml version="1.0" encoding="utf-8"?>

<!--
	Environments (in the LaTeX terminology) for enumerated, ordered, and numbered lists. Note the lack of orthgonality that means that elements other than <item> inside lists are ignored.
	
	TODO: Use just xsl:apply-templates and instead of using modes, check what the parent is inside the item template?
-->

<stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform">


	<!-- Unordered lists -->
	
	<template name="itemised-list" match="itemised-list|itemize|unordered-list|bulleted-list|bullet-list|bullet-points|UL|ul">
		<common formats="/latex/xelatex/">
			<xsl:call-template name="newline-internal" />
			<xsl:text>\begin{itemize}</xsl:text>
			<xsl:call-template name="newline-internal" />
			<xsl:apply-templates select="item" mode="normal" />
			<xsl:call-template name="newline-internal" />
			<xsl:text>\end{itemize}</xsl:text>
			<xsl:call-template name="newline-internal" />
		</common>
		<common formats="/html/xhtml/">
			<ul>
				<xsl:apply-templates select="item" mode="normal" />
			</ul>
		</common>
	</template>
	

	<!--
		Ordered lists.
		
		@start: The starting number for the list. [default 1]
	-->
	<template name="enumerated-list" match="enumerated-list|enumerate|ordered-list|numbered-list|question-list|OL|ol">
		<common formats="/latex/xelatex/">
			<xsl:call-template name="newline-internal" />
			<xsl:text>\begin{enumerate}</xsl:text>
			<xsl:call-template name="newline-internal" />
			<xsl:if test='@start'>
				<!--
					Set the appropriate enum counter according to the depth. Use the format attribute of xsl:number to output it in Roman numerals. Depth must be at least 1 (enumi) and at most 4 (enumiv).
				-->
				<xsl:text>\setcounter{enum</xsl:text>
				<xsl:number value="min( ( 1 + count( ancestor::enumerated-list|ancestor::enumerate|ancestor::ordered-list|ancestor::numbered-list|ancestor::question-list|ancestor::OL|ancestor::ol ), 4 ) )" format="i" />
				<xsl:text>}{</xsl:text>
				<!-- Remember that LaTeX adds 1 to the counter before using it. -->
				<xsl:value-of select="@start - 1" />
				<xsl:text>}</xsl:text>
			</xsl:if>
			<xsl:apply-templates select="item" mode="enumerated-list" />
			<xsl:call-template name="newline-internal" />
			<xsl:text>\end{enumerate}</xsl:text>
			<xsl:call-template name="newline-internal" />
		</common>
		<common formats="/html/xhtml/">
			<ol>
				<xsl:if test="@start">
					<!--
						Note that we're using the START attribute of OL, which is actually deprecated in HTML 4 and XHTML. However, the alternative (using CSS styling) is useless, as it doesn't permit different number formatting for different levels of list (e.g., 1., a., ii., etc.). The only standards-compliant solution to this is to forgo HTML list elements completely and do it all ourselves. Ugh, no thanks!
					-->
					<xsl:attribute name="start">
						<xsl:value-of select="@start" />
					</xsl:attribute>
				</xsl:if>
				<xsl:apply-templates select="item" mode="enumerated-list" />
			</ol>
		</common>
	</template>
	
	
	<!-- Definition or description lists. -->
	<template name="definition-list" match="definition-list|description-list|DL|dl">
		<common formats="/latex/xelatex/">
			<xsl:call-template name="newline-internal" />
			<xsl:text>\begin{description}</xsl:text>
			<xsl:call-template name="newline-internal" />
			<xsl:apply-templates select="item" mode="definition-list" />
			<xsl:call-template name="newline-internal" />
			<xsl:text>\end{description}</xsl:text>
			<xsl:call-template name="newline-internal" />
		</common>
		<common formats="/html/xhtml/">
			<!--
				<dl>s in HTML tend to come out with the spacing a bit wrong (or maybe it's just my browser?). Anyway, they don't appear to be displayed correctly. A more portable solution is to use plain <p>s with CSS margins to control the hanging indent. The tricky part is dealing with embedded <paragraph>s inside the <description-list>. The clever details are handled in the "definition-item" template.
			-->
			<xsl:apply-templates select="item" mode="definition-list" />
		</common>
	</template>


	<!--
	    List items for non-definition lists.
	    
		@label: A label that can be used for cross referencing. Any value can be used, as long as it's a legal identifier in both LaTeX and HTML. This is really only applicable to enumerated lists.
	-->
	<template name="list-item" match="item" mode="normal">
		<common formats="/latex/xelatex/">
			<xsl:text>\item </xsl:text>
			<xsl:if test="@label">
			    <xsl:text>\label{</xsl:text>
			    <xsl:value-of select="@label" />
			    <xsl:text>}</xsl:text>
			</xsl:if>
			<xsl:apply-templates />
			<xsl:call-template name="newline-internal" />
		</common>
		<common formats="/html/xhtml/">
			<xsl:choose>
				<!--
					Check whether there are actual paragraphs or things that should be treated like paragraphs inside the item (there are rather a lot of these!). If so, let them worry about inserting the <p> tags.
				-->
				<xsl:when test="count(paragraph|para|p|question|answer|code-block|itemised-list|itemize|unordered-list|bulleted-list|bullet-list|bullet-points|UL|ul|enumerated-list|enumerate|ordered-list|numbered-list|question-list|OL|ol|definition-list|description-list|DL|dl) ne 0">
					<li><xsl:apply-templates /></li>
				</xsl:when>
				<!--
					Otherwise, insert <p> tags surrounding the item content so that the item spacing looks OK.
				-->
				<xsl:otherwise>
					<li>
                        <xsl:if test="@label">
                            <xsl:element name="a">
                                <xsl:attribute name="name">
                    			    <xsl:value-of select="@label" />
                    			</xsl:attribute>
                    		</xsl:element>
                        </xsl:if>
					    <p><xsl:apply-templates /></p>
					</li>
				</xsl:otherwise>
			</xsl:choose>
		</common>
	</template>
	
	<!--
		Provide the ability to set the numbering of specific items in enumerated lists. Results could be unpredictable if this is applied to an itemised list!
		
		@value: the number to use for the next list item. [default 1]
	-->
	<template name="enumerated-item-value" match="item[@value]" mode="enumerated-list">
		<common formats="/latex/xelatex/">
			<!-- Set the appropriate enum counter. -->
			<xsl:text>\setcounter{enum</xsl:text>
			<xsl:number value="min( ( count( ancestor::enumerated-list|ancestor::enumerate|ancestor::ordered-list|ancestor::numbered-list|ancestor::question-list|ancestor::OL|ancestor::ol ), 4 ) )" format="i" />
			<xsl:text>}{</xsl:text>
			<!-- Remember that LaTeX adds 1 to the counter before using it. -->
			<xsl:value-of select="@value - 1" />
			<xsl:text>}</xsl:text>
			<xsl:call-template name="newline-internal" />
			<xsl:apply-templates select="." mode="normal" />
		</common>
		<!--
			Unfortunately this can't just be a wrapper around the "normal" item template because of the nesting of attributes inside the element.
		-->
		<common formats="/html/xhtml/">
			<xsl:choose>
				<!--
					Check whether there are actual paragraphs or things that should be treated like paragraphs inside the item (there are rather a lot of these!). If so, let them worry about inserting the <p> tags.
				-->
				<xsl:when test="count(paragraph|para|p|question|answer|code-block|itemised-list|itemize|unordered-list|bulleted-list|bullet-list|bullet-points|UL|ul|enumerated-list|enumerate|ordered-list|numbered-list|question-list|OL|ol|definition-list|description-list|DL|dl) ne 0">
					<li value="{@value}">
						<xsl:apply-templates />
					</li>
				</xsl:when>
				<!--
					Otherwise, insert <p> tags surrounding the item content
					so that the item spacing looks OK.
				-->
				<xsl:otherwise>
					<li value="{@value}">
						<p><xsl:apply-templates /></p>
					</li>
				</xsl:otherwise>
			</xsl:choose>
		</common>
	</template>
	
	<!-- Normally numbered enumerated item. -->
	<template name="enumerated-item" match="item[not( @value )]" mode="enumerated-list">
		<common>
			<xsl:apply-templates select="." mode="normal" />
		</common>
	</template>
	

	<!-- List items for definition lists. -->
	<template name="definition-item" match="item" mode="definition-list">
		<common formats="/latex/xelatex/">
			<xsl:apply-templates select="keyword|term|topic|DT|dt" mode="definition-list" />
			<xsl:apply-templates select="definition|description|discourse|DD|dd" mode="definition-list" />
			<xsl:call-template name="newline-internal" />
		</common>
		<common formats="/html/xhtml/">
			<!--
				We have to be a little clever here, because we're transforming potentially multiple embedded elements (embedded <paragraph>s in particular) into one or more <p> tags in the HTML. We need to ensure that:
				
					(a) we don't end up with nested <p> tags,
					    
					(b) all paragraphs are correctly indented, and
					
					(c) if the first thing following the <definition> is a <paragraph> or text node, it gets positioned correctly relative to the <keyword>.

				We know that the <keyword> won't have embedded <paragraph>s (it doesn't really make sense, not that we actually check :). The only thing we therefore need to check for is <paragraph> elements inside the <definition> element.
			-->
			<xsl:choose>
				<!--
					If the first sub-node of the <definition> is NOT a <p> node, then the whole thing should be just simple text and contain only basic formatting elements, if anything (i.e., no lists!). We therefore just wrap the <keyword> and <definition> within a <p> with a hanging indent and finish. Note the use of child::node()[1|2], because we don't necessarily know what variants of <keyword> and <definition> have actually been used, but we know that they will (should!) always be the first and second children of the <item> respectively.
					
					This probably is the most common case, so test for it first.
				-->
				<xsl:when test="not( child::node()[2]/child::node()[1][self::p] or child::node()[2]/child::node()[1][self::paragraph] )">
					<p class="definition1">
						<xsl:apply-templates select="child::node()[1]" />
						<xsl:text> </xsl:text>
						<xsl:apply-templates select="child::node()[2]" />
					</p>
				</xsl:when>
				<!--
					If the first sub-node of the <definition> IS a <p> or <paragraph>, then we need to skip over the processing of this element to avoid nested <p>s in the output. Any remaining sub-nodes are processed normally, except that we pass in the HTMLStyle parameter to ensure that any remaining paragraph elements are correctly indented. I would have used modes for this, but it killed embedded <itemize>s, because there isn't an <itemize> template with the appropriate mode (nor is there a need for one). Grr.
				-->
				<xsl:otherwise>
					<p class="definition1">
						<xsl:apply-templates select="child::node()[1]" />
						<xsl:text> </xsl:text>
						<xsl:apply-templates select="child::node()[2]/child::node()[1]/node()" />
					</p>
					<xsl:apply-templates select="child::node()[2]/*[position() > 1]">
						<xsl:with-param name="HTMLStyle">definition2</xsl:with-param>
					</xsl:apply-templates>
				</xsl:otherwise>
			</xsl:choose>
		</common>
	</template>
	
	<!--
		We need to use a mode here as, e.g., <term> is also a standalone element.
		
		@linebreak: If "yes", insert a line break after the item label. [default "no"]
		    This attribute really only makes sense for the keyword elements of definition lists.
	-->
	<template name="keyword" match="keyword|term|topic|DT|dt" mode="definition-list">
		<common formats="/latex/xelatex/">
			<xsl:text>\item[</xsl:text>
			<xsl:apply-templates />
			<xsl:text>] </xsl:text>
			<xsl:if test="@linebreak">
			    <xsl:text>\mbox{}\newline</xsl:text>
			</xsl:if>
		</common>
		<common formats="/html/xhtml/">
			<strong><xsl:apply-templates /></strong>
            <xsl:if test="@linebreak">
                <br />
            </xsl:if>
		</common>
	</template>
	
	<!--
		Need to provide some context here as, e.g., <description> is also used within <image>.
	-->
	<template name="definition" match="definition|description|discourse|DD|dd" mode="definition-list">
		<common>
			<xsl:apply-templates />
		</common>
	</template>


</stylesheet>