Creating a Hierarchical HTML File

In order to get the hierarchical structure in our html that we desire, we can’t just let our “text:p” template match everything; we have to be more selective in how our templates are applied. This starts by creating an explicit “office:text” template that will match the <xsl:apply-templates />call in our root template.

The first two elements we have to deal with in this template are the ones for the title and author, so let’s have a look.


<xsl:template match="office:document/office:body/office:text">
	<div id="Novel-FrontMatter">
		<p class="Novel-FrontMatter-Title"><xsl:value-of select="text:p[@text:style-name='Novel-FrontMatter-Title' or @text:style-name = /office:document/office:automatic-styles/style:style[@style:parent-style-name = 'Novel-FrontMatter-Title']/@style:name]"/></p>
		<p class="Novel-FrontMatter-Author"><xsl:value-of select="text:p[@text:style-name='Novel-FrontMatter-Author' or @text:style-name = /office:document/office:automatic-styles/style:style[@style:parent-style-name = 'Novel-FrontMatter-Author']/@style:name]"/></p>
	</div>

	<mbp:pagebreak />

Hopefully that all makes sense to you. We haven’t used <xsl:value-of/> before. It just outputs the content of the node indicated by the “select” attribute. We are assuming here that all books have one title and one author. You may need to change this if you book has a subtitle or it is a writing collaboration. Note the use of the “or” statements to deal with automatic styles. The <mbp:pagebreak/> is new too. It is one of Amazon’s special elements. As you might expect, it adds a page break when the file is viewed on a kindle.

Note that that isn’t the end of our template. The template continues throughout the rest of this section.

Next in it we need to deal with the copyright page, which has any number of copyright elements. However, as an added bonus, we want to exclude from our ebook the ISBN the print version is using (as this is unique to the print version). Typically kindle books don’t have ISBNs, but if you are using one, rather than ignoring the ISBN entries, you just need to create a template that matches them and use it to replace them with the correct ones for your ebook.


<div id="Novel-Copyright">
	<xsl:apply-templates select="text:p[contains(@text:style-name,'Copyright') or @text:style-name = /office:document/office:automatic-styles/style:style[contains(@style:parent-style-name, 'Copyright')]/@style:name and not(contains(text(),'ISBN'))] "/>
</div>
	
<mbp:pagebreak />

As you can see, we excluded the ISBNs from the copyright page by adding an “and” statement that checks that the text in the elements doesn’t contain “ISBN”. The elements selected by this statement are matched by the generic “text:p” template we made earlier, which turns the matched elements into <p> elements with the style name as the class.

Next we come to the table of contents. The table of contents is just a list of links to the various bits of the book. Most of the uris are created with our old friend “generate-id()”. We will call it again when we get round to creating the actual entries and store the result id their “id” attributes.


<div id="toc">
	<p class="Novel-TOC-Heading">Contents</p>
	<ul>	
	<xsl:for-each select="$forward">
		<li><a href="#{generate-id(.)}"><xsl:value-of select="."/></a></li>	
	</xsl:for-each>

Hopefully that was all pretty readable despite it introducing a few new things like <xsl:for-each>. You probably saw that we got some use out of one of our variables for the first time, $forward, which contains all the <text:p> elements that have a forward-title style. We then used <xsl:for-each> to loop over each of them and our old friend “generate-id()” to create the uri. We passed it “.”, which just means “the current element” i.e the one we are currently looping over, and then we print out the current element’s value using <xsl:value> (remember that $forward contains all the elements with a forward-title style, so their value is the title of the forward itself).

Next we need to build the table of contents for the actual content of our novel. Here we are going to have to use the <xsl:choose> statement again so we can deal with books that have parts and chapters, chapters only, no parts or chapters etc.


<li><a href="#Novel-Content">Content</a></li>		
<xsl:choose>
	<xsl:when test="$parts">
	<ul>				
		<xsl:for-each select="$parts">
			<xsl:variable name="partID" select="generate-id(.)"/>
			<li><a href="#{$partID}"><xsl:value-of select="."/></a></li>
			<xsl:if test="key('chapters', $partID)">
				<ul>
				<xsl:for-each select="key('chapters', $partID)">
					<li><a href="#{generate-id(.)}"><xsl:value-of select="."/></a></li>
				</xsl:for-each>
				</ul>
			</xsl:if>
		</xsl:for-each>
	</ul>
</xsl:when>
<xsl:when test="$chapters">
	<ul>
	<xsl:for-each select="$chapters">
		<li><a href="#{generate-id(.)}"><xsl:value-of select="."/></a></li>
	</xsl:for-each>
	</ul>
</xsl:when>
</xsl:choose>

As you can see, a book with chapters but no parts is dealt with in pretty much the same as the forward was. However, dealing with books with parts is slightly more complex and requires the use of one of our keys. It starts off the same as the forward and chapters sections, but once we have written the entry for a part, we check to see if that part has chapters by seeing if our chapters key for the current part contains anything (we could have just checked $chapters, but this way covers the unlikely case of a book in which some parts are divided into chapters and others are not.)

Finally, our table of contents has a link to our about section. Compared to the last section, this is pretty straight forward.


<xsl:if test="$about">
	<li><a href="#Novel-About">About the Author</a></li>
</xsl:if>
</ul>
</div>
<mbp:pagebreak />

Now to actually deal with the content of our novel. This is actually simpler than the table of contents, as all the messy stuff is dealt with in the templates for the various elements.


<xsl:if test="$forward">
	<div id="Novel-Forward">
		<xsl:apply-templates select="$forward"/>
	</div>
</xsl:if>	
<div id="Novel-Content">
	<xsl:choose>
		<xsl:when test="$parts">	
			<xsl:apply-templates select="$parts"/>
		</xsl:when>
		<xsl:when test="$chapters">
			<xsl:apply-templates select="$chapters"/>
		</xsl:when>
		<xsl:otherwise>
			<xsl:apply-templates select="$paragraphs"/>
		</xsl:otherwise>
	</xsl:choose>
</div>
<xsl:if test="$about">
	<div id="Novel-About">
		<xsl:apply-templates select="$about"/>
	</div>
</xsl:if>
</xsl:template>

As you can see we just use <xsl:if> to see if we have a forward and about sections, and our friend <xsl:choose> to figure out whether or not our book has parts and chapters. We then start the processing at that correct level. Notice to that that is the end of our “office:text” template.

Series Navigation<< Using XSL Keys and VariablesTitle Templates >>

Leave a Reply

Your email address will not be published. Required fields are marked *