Book HomeXSLSearch this book

9.5. XSLT Source Code

Now that we've discussed the design issues we went through as we defined our XML document structure, we'll talk about how our XSLT stylesheets transform XML-tagged tutorials into the files we want.

9.5.1. Stylesheets and Modes

To start with, we use the XSLT mode attribute to process the same set of nodes several times. Our template for the root element is similar to a Java or C++ program whose main() method contains nothing but subroutine calls:

<xsl:template match="/">
  <xsl:apply-templates select="tutorial" mode="build-main-index"/>
  <xsl:apply-templates select="tutorial" mode="build-section-indexes"/>
  <xsl:apply-templates select="tutorial" mode="build-individual-panels"/>
  <xsl:apply-templates select="tutorial" mode="generate-graphics"/>
  <xsl:apply-templates select="tutorial" mode="generate-pdf-file">
    <xsl:with-param name="page-size" select="'letter'"/>
  </xsl:apply-templates>
  <xsl:apply-templates select="tutorial" mode="generate-pdf-file">
    <xsl:with-param name="page-size" select="'a4'"/>
  </xsl:apply-templates>
  <xsl:apply-templates select="tutorial" mode="generate-zip-file"/>
</xsl:template>

If this were a Java program, we might create a main() method that looks like this:

public static void main(String[] argv)
{
  buildMainIndex();
  buildSectionIndexes();
  buildIndividualPanels();
  generateGraphics();
  generatePDFFile("letter");

  generatePDFFile("a4");
  generateZipFile();
}

This style of coding facilitates maintenance; if the PDF files aren't generated correctly, the templates with mode="generate-pdf-file" are the obvious place to start debugging. In addition, we structured the files so that all the templates for a given mode are in a single file that can be included into our main stylesheet:

<xsl:include href="toot-o-matic-variables.xsl"/>

<xsl:include href="xslt-utilities.xsl"/>
<xsl:include href="dw-style.xsl"/>

<xsl:include href="build-main-index.xsl"/>
<xsl:include href="build-section-indexes.xsl"/>
<xsl:include href="build-individual-panels.xsl"/>
<xsl:include href="build-graphics.xsl"/>
<xsl:include href="build-pdf-file.xsl"/>
<xsl:include href="build-zip-file.xsl"/>

In addition to the obviously named files, the file toot-o-matic-variables.xsl defines several global variables used throughout the stylesheets, xslt-utilities.xsl is a library of generic routines (substring replacement, for example) we use, and dw-style.xsl defines the look and feel of our HTML pages.

9.5.2. Initializing Global Variables

It's worth discussing the global variables initialized in toot-o-matic-variables.xsl. All of these variables are used throughout our various stylesheets, and initializing them saves us a significant amount of processing time. The most significant variable is $mouse-effects. This variable is an automatically generated segment of JavaScript code used to process mouseover effects on all the HTML pages we generate. Here's how the generated code looks:

<!-- var emailAbstract="developerWorks is proud to present the Toot-O-Matic,...";
var justTitle="Building tutorials with the Toot-O-Matic";
var tutorialPrereqs="http://www-4.ibm.com/education/tootomatic";
var menu1blurb="Installing and configuring Toot-O-Matic"; 
var menu2blurb="Creating your first tutorial"; 
var menu3blurb="developerWorks editorial guidelines"; 
var menu4blurb="Toot-O-Matic tag guide"; 
var menu5blurb="Toot-O-Matic tag reference"; 
var menu6blurb="Troubleshooting"; 
var menu7blurb="Feedback"; 

var browser = "x";
if (navigator.userAgent.indexOf("Mozilla/4") != -1) browser = "N3";
else if (navigator.userAgent.indexOf("Mozilla/3") != -1) browser = "N3";
else browser = "x";
      
if (browser=="N3")
{
  var menu1over=new Image(108,68); 
  var menu1out=new Image(108,68); 
  var menu2over=new Image(108,68); 
  var menu2out=new Image(108,68); 
  var menu3over=new Image(108,68); 
  var menu3out=new Image(108,68); 
  ...
  var topmainover=new Image(77,15);
  var topmainout=new Image(77,15);
  var bottommainover=new Image(77,15);
  var bottommainout=new Image(77,15);
  var topsectionover=new Image(98,15);
  var topsectionout=new Image(98,15);
  var bottomsectionover=new Image(98,15);
  var bottomsectionout=new Image(98,15);
  var topfeedbackover=new Image(80,15);
  var topfeedbackout=new Image(80,15);
  var bottomfeedbackover=new Image(80,15);
  var bottomfeedbackout=new Image(80,15);
  var toppreviousover=new Image(77,15);
  var toppreviousout=new Image(77,15);
  var bottompreviousover=new Image(77,15);
  var bottompreviousout=new Image(77,15);
  var topnextover=new Image(60,15);
  var topnextout=new Image(60,15);
  var bottomnextover=new Image(60,15);
  var bottomnextout=new Image(60,15);
  var topnextsectionover=new Image(108,15);
  var topnextsectionout=new Image(108,15);
  var bottomnextsectionover=new Image(108,15);
  var bottomnextsectionout=new Image(108,15);
      
  menu1over.src="imagemaster/himenu1.jpg"; 
  menu1out.src="imagemaster/menu1.jpg"; 
  menu2over.src="imagemaster/himenu2.jpg"; 
  menu2out.src="imagemaster/menu2.jpg"; 
  menu3over.src="imagemaster/himenu3.jpg"; 
  menu3out.src="imagemaster/menu3.jpg"; 
  ...
  var mainblurb="Main menu";
  var sectionblurb="Section menu";
  var feedbackblurb="Give feedback on this tutorial";
  var previousblurb="Go to previous panel";
  var nextblurb="Go to next panel";
  var nextsectionblurb="Go to next section";

  topmainover.src="../i/h-main.gif";
  topmainout.src="../i/main.gif";
...
}
function iOut(image)
{
  if (browser=="N3")document[image].src=eval(image + "out.src");
}
function iOver(image)
{
  if (browser=="N3")document[image].src=eval(image + "over.src");
}
// -->

This JavaScript code is used for the mouseover effects on the HTML panels. To streamline processing, we generate this code as a variable. (We've removed roughly half the code here to keep the listing short; most of this code deals with initializing a number of variables.) Whenever we create a new HTML page, we simply insert this variable into the output document:

<script language="javascript">
  <xsl:value-of select="$mouse-effects"/>
</script>

As in traditional programming, storing frequently used values in a variable instead of calculating them each time simplifies the code and improves performance. Notice in the code listing that a significant amount of JavaScript code is generated from the XML source document. This fragment is the XSLT that generates a set of JavaScript variables that contain the titles of all the sections:

<xsl:for-each select="/tutorial/section">
  <xsl:text>var menu</xsl:text>
  <xsl:value-of select="position()"/>
  <xsl:text>blurb="</xsl:text>
  <xsl:value-of select="title"/>
  <xsl:text>"; </xsl:text>
  <xsl:value-of select="$newline"/>
</xsl:for-each>

The code generates seven new variables, one for each <section> element in our tutorial:

var menu1blurb="Installing and configuring Toot-O-Matic"; 
var menu2blurb="Creating your first tutorial"; 
var menu3blurb="developerWorks editorial guidelines"; 
var menu4blurb="Toot-O-Matic tag guide"; 
var menu5blurb="Toot-O-Matic tag reference"; 
var menu6blurb="Troubleshooting"; 
var menu7blurb="Feedback";
NOTE: This is one of many cases when a naming convention is invaluable. If we want the JavaScript variable that contains the title of the fifth section, we know that variable is named menu5blurb. This technique is useful in many other places, as well. If we're creating HTML files for the third <section>, and we're currently processing the fourth <panel> in that <section>, and the root filename we're using is tootomatic, the newly created HTML file will be named tootomatic-3-4.html. Similarly, if we want to create a link to the previous and next HTML files, those files are named tootomatic-3-3.html and tootomatic-3-5.html, respectively. You'll see this technique used throughout this case study.

9.5.3. Generating the Main Menu Panel

The Main menu panel consists of a standard header and footer, with a list of all sections of the tutorial in between. Clicking on any of the section titles takes you to the first panel in that section. To enhance the visual appeal of the panel, generated graphics and mouseover effects are used to display the panel title.

The stylesheet that generates the list of sections is straightforward. The header and footer are generated from boilerplate text; the list of sections is generated with an <xsl:for-each> element:

<xsl:for-each select="section">
  <a border="0">
    <xsl:attribute name="href">
      <xsl:value-of select="$fn"/>
      <xsl:text>-</xsl:text>
      <xsl:value-of select="position()"/>
      <xsl:text>-1.html</xsl:text>
    </xsl:attribute>
    <xsl:attribute name="onMouseOver">
      <xsl:text>iOver('menu</xsl:text>
      <xsl:value-of select="position()"/>
      <xsl:text>'); self.status=menu</xsl:text>
      <xsl:value-of select="position()"/>
      <xsl:text>blurb; return true;</xsl:text>
    </xsl:attribute>
    <xsl:attribute name="onMouseOut">
      <xsl:text>iOut('menu</xsl:text>
      <xsl:value-of select="position()"/>
      <xsl:text>'); self.status=''; return true;</xsl:text>
    </xsl:attribute>
    <img width="380" height="20" border="0">
      <xsl:attribute name="name">
        <xsl:text>menu</xsl:text>
        <xsl:value-of select="position()"/>
      </xsl:attribute>
      <xsl:attribute name="src">
        <xsl:text>imagemaster/</xsl:text>
        <xsl:text>menu</xsl:text>
        <xsl:value-of select="position()"/>
        <xsl:text>.jpg</xsl:text>
      </xsl:attribute>
      <xsl:attribute name="alt">
        <xsl:value-of select="position()"/>
        <xsl:text>. </xsl:text>
        <xsl:value-of select="title"/>
      </xsl:attribute>
    </img>
  </a>
  <br/>
</xsl:for-each>

This XSLT produces the following HTML code for our example XML file:

<a border="0" href="tootomatic-1-1.html" 
  onMouseOver="iOver('menu1'); self.status=menu1blurb; return true;" 
  onMouseOut="iOut('menu1'); self.status=''; return true;">
  <img border="0" height="20" width="380" name="menu1" 
    src="imagemaster/menu1.jpg" 
    alt="1. Installing and configuring Toot-O-Matic">
</a>
<br>
<a border="0" href="tootomatic-2-1.html" 
  onMouseOver="iOver('menu2'); self.status=menu2blurb; return true;" 
  onMouseOut="iOut('menu2'); self.status=''; return true;">
  <img border="0" height="20" width="380" name="menu2" 
    src="imagemaster/menu2.jpg" 
    alt="2. Creating your first tutorial">
</a>
<br>
<a border="0" href="tootomatic-3-1.html" 
  onMouseOver="iOver('menu3'); self.status=menu3blurb; return true;" 
  onMouseOut="iOut('menu3'); self.status=''; return true;">
  <img border="0" height="20" width="380" name="menu3" 
    src="imagemaster/menu3.jpg" 
    alt="3. developerWorks editorial guidelines">
</a>
<br>
<a border="0" href="tootomatic-4-1.html" 
  onMouseOver="iOver('menu4'); self.status=menu4blurb; return true;" 
  onMouseOut="iOut('menu4'); self.status=''; return true;">
  <img border="0" height="20" width="380" name="menu4" 
    src="imagemaster/menu4.jpg" 
    alt="4. Toot-O-Matic tag guide">
</a>
<br>
<a border="0" href="tootomatic-5-1.html" 
  onMouseOver="iOver('menu5'); self.status=menu5blurb; return true;" 
  onMouseOut="iOut('menu5'); self.status=''; return true;">
  <img border="0" height="20" width="380" name="menu5" 
    src="imagemaster/menu5.jpg" 
    alt="5. Toot-O-Matic tag reference">
</a>
<br>
<a border="0" href="tootomatic-6-1.html" 
  onMouseOver="iOver('menu6'); self.status=menu6blurb; return true;" 
  onMouseOut="iOut('menu6'); self.status=''; return true;">
  <img border="0" height="20" width="380" name="menu6" 
    src="imagemaster/menu6.jpg" 
    alt="6. Troubleshooting">
</a>
<br>
<a border="0" href="tootomatic-7-1.html" 
  onMouseOver="iOver('menu7'); self.status=menu7blurb; return true;" 
  onMouseOut="iOut('menu7'); self.status=''; return true;">
  <img border="0" height="20" width="380" name="menu7" 
    src="imagemaster/menu7.jpg" 
    alt="7. Feedback">
</a>

Notice the number of things that are automatically generated in this list of sections. We know the filename of any given section, thanks to our filenaming convention. The first panel in the fifth section of the tutorial is tootomatic-5-1.html, for example. For the JavaScript mouseover effects we mentioned previously, we name the <img> elements in the list menu1, menu2, etc. Similarly, the name of each graphic is imagemaster/menu1.jpg, imagemaster/menu2.jpg, etc. The onMouseOver attribute uses variables such as menu1blurb and menu2blurb. Generating these items removes the chance for human error (once the stylesheets are correct) and allows us to control the look and feel of the HTML pages in the tutorial.

9.5.4. Generating the Section Indexes

To generate a section index, we create an HTML file with an ordered list of all of the <panel> elements in the current <section>. Retrieving the titles of all the panels can be done with a <xsl:for-each> element in the stylesheet:

<xsl:for-each select="panel">
  <img border="0" src="../i/arrow.gif"/> 
  <a>
    <xsl:attribute name="href">
      <xsl:value-of select="$fn"/><xsl:text>-</xsl:text>
      <xsl:value-of select="$sectionNumber"/><xsl:text>-</xsl:text>
      <xsl:value-of select="position()"/><xsl:text>.html</xsl:text>
    </xsl:attribute>
    <xsl:value-of select="position()"/><xsl:text>. </xsl:text>
    <xsl:value-of select="title"/>
  </a>
  <br/>
</xsl:for-each>

In this listing, the variable $fn is defined as the root filename used to generate all HTML filenames for this tutorial. The filename convention used for section indexes is index1.html for the first section index, index2.html for the second section index, etc. This convention makes it easy to generate the section index when we need it, and it makes it easy for the individual panels in a given section to reference the proper section index on each panel.

We use the Xalan Redirect extension to write output to multiple files. Here's how we invoke that extension to begin writing output to another file:

<xsl:for-each select="section">
  <redirect:write select="concat($curDir, $fileSep, 'index', position(), 
    '.html')">

The select attribute of the <redirect:write> element defines the name of the output file. To generate this filename, we concatenate the current directory to which we're writing files (a global variable), the file separator character (another global variable), the text index, the position of this section, and the text .html. If we use the tootomatic directory on a Windows machine, the index for the second <section> will be written to the file tootomatic\index2.html. We use the Redirect extension whenever we need to generate an HTML file for a section index or an individual panel.

9.5.5. Generating the Individual Panels

The masthead and footer of each panel are fairly straightforward; both use a predefined format and a series of links common to all pages on IBM sites. This is a perfect use for named templates. We need to create certain HTML markup for the masthead of each HTML page, and we need to create more markup for the footer of each page. In addition, we need to create the title bar at the top of each page and a navigation bar (an area with Previous and Next links, among other things) at the top and bottom of most pages. We use four templates, cleverly named dw-masthead, dw-title-bar, dw-nav-bar, and dw-footer, to do this:

<xsl:call-template name="dw-masthead"/>
<xsl:call-template name="dw-title-bar"/>
<xsl:call-template name="dw-nav-bar">
  <xsl:with-param name="includeMain" select="'youBetcha'"/>
  <xsl:with-param name="sectionNumber" select="$sectionNumber"/>
  <xsl:with-param name="position" select="$pos"/>
  <xsl:with-param name="last" select="$last"/>
  <xsl:with-param name="topOrBottom" select="'top'"/>
  <xsl:with-param name="oneOrTwo" select="'two'"/>
</xsl:call-template>

<!-- Processing for the main body of the page goes here -->

<xsl:call-template name="dw-nav-bar">
  <xsl:with-param name="includeMain" select="'youBetcha'"/>
  <xsl:with-param name="sectionNumber" select="$sectionNumber"/>
  <xsl:with-param name="position" select="$pos"/>
  <xsl:with-param name="last" select="$last"/>
  <xsl:with-param name="topOrBottom" select="'bottom'"/>
  <xsl:with-param name="oneOrTwo" select="'two'"/>
</xsl:call-template>
<xsl:call-template name="dw-footer"/>

Of the four templates, only dw-nav-bar takes any parameters. Depending on the page we're currently generating, we may or may not need the Main menu button (we don't include this button on the Main menu panel). We need the current section number so the navigation bar can create filenames for the links to the section menu, the previous panel, and the next panel. The position parameter defines the position of this particular panel; last defines the position of the last panel. If position is 1, then the Previous button will be disabled. If position is equal to last, then the Next button will be disabled. The parameter topOrBottom defines whether this navigation bar is being created at the top or bottom of the panel (we have to name the images differently so the JavaScript mouseover effects work correctly). Finally, the oneOrTwo parameter determines whether this panel will have two navigation bars or just one. This is also necessary for the mouseover effects.

Now that we've built all these parts of the page, building the actual content of the panel is somewhat anticlimactic. We support a limited set of HTML tags (the 20 or so most-used tags, added sparingly as we've needed to add new functions to the tool), most of which are converted directly into their HTML equivalents.

9.5.6. Generating the PDF Files

Converting the XML document to an XSL Formatting Objects (XSL-FO) stream is fairly straightforward, as well. Our printed layout consists of the graphics and text from the tutorial, combined with page numbers, headers, and footers to create high-quality printed output. We use the Apache XML Project's FOP (Formatting Objects to PDF) tool to do this.

When we invoke the PDF-generating templates with the mode=generate-pdf attribute, we pass in the page-size parameter to set the dimensions of the printed page. We generate PDFs with both letter-sized and A4-sized pages to support our customers around the world.

To create the PDF, we first create the output file of formatting objects, converting the various XML tags from our source document into the various formatting objects we need:

<fo:block font-size="16pt" line-height="19pt" font-weight="bold" 
  space-after.optimum="12pt">
  Introduction to JavaServer Pages
</fo:block>
<fo:block space-after.optimum="6pt">
  In today's environment, most web sites want to display dynamic 
  content based on the user and the session. Most content, such 
  as images, text, and banner ads, is most easily built with 
  HTML editors. So we need to mix the "static" content of HTML 
  files with "directives" for accessing or generating dynamic 
  content. 
</fo:block>
<fo:block space-after.optimum="6pt">
  JavaServer Pages meet this need. They provide server-side 
  scripting support for generating web pages with combined 
  static and dynamic content. 
</fo:block>

Currently, the XSL:FO specification is a candidate recommendation at the World Wide Web Consortium (W3C). Because future changes are likely, we won't discuss the formatting objects themselves. It suffices to say that our stylesheet defines page layouts (margins, running headers and footers, etc.) and then creates a number of formatting objects inside those page layouts. The FOP tool handles the details of calculating line, page, and column breaks, page references, and hyperlinks.

Once the file of formatting objects is created, we call an extension function to convert the formatting objects file into a PDF. Here's the exension's main code:

public static void buildPDFFile(String foFilename, String pdfFilename)
{
  try
  {
    XMLReader parser = 
      (XMLReader) Class.forName("org.apache.xerces.parsers.SAXParser")
                       .newInstance();
    Driver driver = new Driver();
    driver.setRenderer("org.apache.fop.render.pdf.PDFRenderer", 
                       Version.getVersion());
    driver.addElementMapping("org.apache.fop.fo.StandardElementMapping");
    driver.addElementMapping("org.apache.fop.svg.SVGElementMapping");
    driver.addPropertyList("org.apache.fop.fo.StandardPropertyListMapping");
    driver.addPropertyList("org.apache.fop.svg.SVGPropertyListMapping");
    driver.setOutputStream(new FileOutputStream(pdfFilename));
    driver.buildFOTree(parser, new InputSource(foFilename));
    driver.format();
    driver.render();
  }

The code merely creates the FOP Driver object, sets its various properties, and then tells it to render the formatting objects in a PDF file. The main difficulty here is in determining how the various XML elements should be converted to formatting objects; once the conversion is done, we have a tool that generates high-quality printable output from our XML source files. Best of all, this code uses open source tools exclusively.

9.5.7. Generating the JPEG Files

Another thing we need to produce for the tutorial is a series of JPEG files. To have precise control over the appearance of the titles in the tutorial, we create a JPEG file in which the title text is written in a particular font. We discussed this code in Chapter 8, "Extending XSLT", so we won't go over it here. Here's the first significant section of the build-graphics.xsl file:

<xsl:template match="tutorial" mode="generate-graphics">
  <xsl:choose>
    <xsl:when test="function-available('jpeg:buildJPEGFile')">
      <xsl:value-of 
        select="jpeg:buildJPEGFile(title, 
        concat('master', $fileSep, 'masthead.jpg'),
        concat($curDir, $fileSep, 'imagemaster', $fileSep, 'masthead.jpg'),
        $baseFont, 27, 5, 30, 0, 0, 0)"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:message terminate="yes">
        Error! JPEG library not available!
      </xsl:message>
    </xsl:otherwise>
  </xsl:choose>

The buildJPEGFile function takes several parameters, including the title text (in the example, our XPath expression passes in the value of the title element), the name of the background JPEG file (we load this file, draw the text on top of it, and then save the new JPEG), the name of the new JPEG file, the name of the font, and other details about the font size, the x- and y-coordinates where the text should start, and the color in which to draw it.

Although neither this extension nor the stylesheet that calls it are rocket science, they save us a tremendous amount of time in the tutorial development process. Before we had the Toot-O-Matic, we had to ask our highly trained, highly talented, and highly overworked graphics staff to create these graphics for us; now we do it automatically and the graphics staff can focus their talents on more important things.

9.5.8. Generating the Zip File

Our last task is to generate a zip file that contains all the files needed to view the tutorial locally. This includes all HTML files, all standard graphics, all JPEGs we generate, and any graphics referenced in the XML source (anything in an <img> tag). We call another Java extension to build the zip file. Determining which files should be loaded into the zip file relies heavily on our naming conventions.

When we invoke the buildZipFile function, we pass in several arguments. The first three are the root filename, the directory to which we write the output files, and the file separator for this platform. The next argument is the <tutorial> node itself; the extension uses DOM functions to determine what files should be added to the zip file. The final argument is a node-set of all the things that reference graphics files in the XML source. That includes the img attribute of any tag and the src attribute of the <img> element. Here's what the function call looks like:

<xsl:template match="tutorial" mode="generate-zip-file">
  <xsl:choose>
    <xsl:when test="function-available('zip:buildZipFile')">
      <xsl:variable name="referencedGraphics" 
        select="./@img|//image-column/@img|//img/@src"/>
      <xsl:value-of 
        select="zip:buildZipFile($fn, $curDir, 
                                 $fileSep, ., $referencedGraphics)"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:message terminate="yes">
        Error! Zip file library not available!
      </xsl:message>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

In the extension function code itself, we start by creating the ZipOutputStream itself:

ZipOutputStream zipOut = 
  new ZipOutputStream(new FileOutputStream(currentDirectory + 
                                           fileSeparator + 
                                           baseFilename + ".zip"));

Once we've created our ZipOutputStream, we'll see if there's a comment for the zip file in the zip-file-comment attribute of the <tutorial> element:

Node currentNode = tutorialElement.nextNode();

while (currentNode != null)
{
  if (currentNode.getLocalName().equals("tutorial"))
  {
    ElementImpl currentElement = (ElementImpl)currentNode;
    String zipFileComment = currentElement.getAttribute("zip-file-comment");
    if (zipFileComment != null)
      zipOut.setComment(zipFileComment);
    else
    {
      zipFileComment = currentElement.getAttribute("alt");
      if (zipFileComment != null)
        zipOut.setComment(zipFileComment);
    }

With everything we do with the DOM nodes, we'll need to make sure we actually work with the appropriate nodes; that's why we use the function call getLocalName().equals("tutorial"). Once we've found the <tutorial> element, we can work with its children to figure out the names of all the HTML and JPEG files we need to add to the zip file. If the <tutorial> element has five <section> children, and the first <section> contains eleven <panel>s, then we'll need to write the files tootomatic-1-1.html through tootomatic-1-11.html to the zip file. (This assumes that the base filename we use is tootomatic.) Here's an excerpt from the code:

int numKids = currentElement.getChildCount();
int numSections = 0;
for (int i = 0; i < numKids; i++)
{
  Node currentChild = currentElement.getChild(i);
  if (currentChild.getLocalName().equals("section"))
  {
    ElementImpl currentChildElement = (ElementImpl)currentChild
    fileToZip = new File(currentDirectory + fileSeparator + "index" + 
                         ++numSections + ".html");
    fis = new FileInputStream(fileToZip);
    entry = new ZipEntry(currentDirectory + fileSeparator + 
                                  fileToZip.getName());
    if (zipOut != null)
    {
      zipOut.putNextEntry(entry);
      while((bytes_read = fis.read(buffer)) != -1)
        zipOut.write(buffer, 0, bytes_read);
    }
    fis.close();

    int numGrandkids = currentChildElement.getChildCount();
    int numPanels = 0;
    for (int j = 0; j < numGrandkids; j++)
    {
      Node currentGrandchildElement = currentChildElement.getChild(j);
      if (currentGrandchildElement.getLocalName().equals("panel"))
      {
        fileToZip = new File(currentDirectory + fileSeparator +
                             baseFilename + "-" + numSections + "-" + 
                             ++numPanels + ".html");
        fis = new FileInputStream(fileToZip);
        entry = new ZipEntry(currentDirectory + fileSeparator + 
                             fileToZip.getName());
        if (zipOut != null)
        {
          zipOut.putNextEntry(entry);
          while((bytes_read = fis.read(buffer)) != -1)
            zipOut.write(buffer, 0, bytes_read);
        }
        fis.close();
      }
    }
  }
}

Now that we know how many <section> elements are in our <tutorial>, we can write all the generated JPEG graphics to the zip file. Our extension function also contains a static array of the filenames of all standard files used by every tutorial:

static String standardFiles[] = {"c.gif", "sw-gold.gif", 
                                 "main.gif", "xmain.gif",
                                 "section.gif", "xsection.gif", 
                                 "feedback.gif", "xfeedback.gif", 
                                 "previous.gif", "xprevious.gif", 
                                 "next.gif", "xnext.gif",
                                 "icon-discuss.gif", "icon-email.gif", 
                                 "icon-pdf-ltr.gif", "icon-zip.gif",
                                 "icon-pdf-a4.gif", 
                                 "mast_logo.gif", "shopibm.gif", 
                                 "support.gif", "downloads.gif", 
                                 "mast_lnav_sp.gif", "about.gif",
                                 "h-menu.gif", "h-main.gif", 
                                 "h-section.gif", "h-feedback.gif",
                                 "h-previous.gif", "h-next.gif", 
                                 "nextsection.gif", "h-nextsection.gif",
                                 "arrow.gif", "mgradient.gif",
                                 "email.gif", "dw-logo2.gif", 
                                 "btn-send.gif", "btn-close.gif",
                                 "emailfriend.js"};

We store each of these standard files in the zip file for each tutorial. Storing the names of the files in an array makes it easy to add or delete new files from the list. If this list of files changed frequently, we would consider writing an XML-based configuration file that listed all standard files. We could then parse that file, extract the filenames from it, and add those files to the zip file.

Our next task is to use our node-set of graphics elements to add all referenced graphics to the zip file:

currentNode = graphicsElements.nextNode();

HashMap zipEntries = new HashMap();
while (currentNode != null)

{
  String nextGraphicsFile = currentNode.getNodeValue();
  if (!zipEntries.containsKey(nextGraphicsFile))
  {
    fileToZip = new File(currentNode.getNodeValue());
    fis = new FileInputStream(fileToZip);
    entry = new ZipEntry(currentDirectory + fileSeparator + 
                             currentNode.getNodeValue());
    zipOut.putNextEntry(entry);
    while ((bytes_read = fis.read(buffer)) != -1)
      zipOut.write(buffer, 0, bytes_read);
    zipEntries.put(nextGraphicsFile, nextGraphicsFile);
  }
  currentNode = graphicsElements.nextNode();
}

As we add a referenced graphics file to the zip file, we put the name of the file into a HashMap. If we attempt to add a file to the zip archive and that file is already in the archive, we'll get an exception. To avoid that problem, we check each filename before we add it to the zip file.

Our last task is to close the ZipOutputStream:

zipOut.flush();    
zipOut.close();


Library Navigation Links

Copyright © 2002 O'Reilly & Associates. All rights reserved.