JavaOne Hands-On Lab

ODFDOM - Changing ODF documents using the new opensourced multi-tiered API

LAB-5569

Copyright 2009 Sun Microsystems

 

LAB-5569: ODFDOM - Changing ODF documents using the new opensourced multi-tiered API

Expected Duration: 100 minutes

Last Updated: May 13, 2009

The OpenDocument format (ODF) is an XML based, ISO standardized file format for electronic office documents such as spreadsheets, charts, presentations and word processing documents.

The new opensource library ODFDOM is a free Java 5 framework sponsored by Sun Microsystems Inc. to easily create and manipulate such ODF documents.

This workshop will give the opportunity to get in touch with this exciting new API and the possibility to talk with their architects.

Aside of the exercises, insights about further future goals of ODFDOM and its language independence are topic of this workshop.

Copyright
Copyright  2008 Sun Microsystems, Inc. All rights reserved. Sun, Sun Microsystems,
the Sun logo, Solaris, Java, the Java Coffee Cup logo, JavaOne, the JavaOne logo,
and all Solaris-based and Java-based marks and logos are trademarks or registered
trademarks of Sun Microsystems, Inc. in the United States and other countries.
                        

Authors
Svante Schubert
Steffen Grund


Prerequisites

 

This hands-on lab assumes you have some basic knowledge of, or programming experience in, the following technologies:


System Requirements

 

Software Needed For This Lab

 

Please install the following set of software. If you have any questions on installation, please feel free to send questions to the lab forum mentioned below.


Notations Used in This Documentation

 

Lab Exercises

 

Additional Resources

 

Where To Go For Help

 

Exercise 0: Install and Configure Lab Environment

 

For JavaOne attendees using the lab machines provided, the steps in Exercise 0 are done for you as part of the login process. So please proceed to Exercise 1.


 

Install OpenOffice.org

 

Install and Configure NetBeans

 

Download and Install the ODFDOM

Note: you do not have to download ODFDOM. The authors of this lab have included a compiled and working version of ODFDOM in the lab: odfdom.jar
The reason for this is that the API of ODFDOM is a work in progress and may not be compatible with the code presented in the exercises.
We do encourage the download of the latest version, though, to benefit from additions and bugfixes.

 

Optional configuration

The following configuration steps are not mandatory to do the exercises, but they may help in working with ODFDOM. The authors of the lab strongly encourage to do the following to make working with the lab easier.

Add the javadoc of ODFDOM to NetBeans.


Now you are all set to proceed to the exercises!

Exercise 1: Exploring the ODFDOM layers (20 minutes)

 

The goal of this exercise is to get an impression of the ODFDOM layers following step by step instructions.


Steps to Follow

 

Step 1: Opening the Project

  1. If NetBeans is not already running, start it.
    1. Select File Menu
    2. Select New Project
    3. Select Category 'Java' and Project 'Java Application'
    4. Press 'Next'

    Figure-01: New Project: Choose Project

  2. In the next dialog, do the following
    1. Set 'Project Name' to OdfdomLayers
    2. Set 'Project Location' to '<lab_root>' - note for JavaOne attendees: this is correct as it is - just keep the default.
    3. Enter 'odfdom.example1.RunExample1' as main class.
    4. Press 'Finish'

    Figure-02: New Java Application: Name and Location

  3. We are adding some jar libraries to our project - for future usage.

    Right-click the CustomerInvoice project and choose 'Properties'. In the 'Libraries' category, click 'Add JAR/Folder'. Add the following libraries to the project:

    • <lab_root>/resources/odfdom/jars/xercesImpl.jar
    • <lab_root>/resources/odfdom/jars/odfdom.jar

    For JavaOne attendees, the libraries are here:

    • /export/home/lab5569/odfdom/resources/odfdom/jars/xercesImpl.jar
    • /export/home/lab5569/odfdom/resources/odfdom/jars/odfdom.jar

    Click "OK" afterwards.


    Figure-03: Add required libraries to the project

  4. After the project creation, the main class is automatically opened by NetBeans. When you enter the following code inside of the main(...) method, do not forget to set the LAB_ROOT variable. For JavaOne attendees, the line in the following code snippet is already correct. The second part of the exercise is commented at first since this project will be run twice.

        // For JavaOne the place for the project is this; change it for your machine
        private static final String LAB_ROOT = "/export/home/lab5569/odfdom/";
        private static final Logger LOG = Logger.getLogger(RunExample1.class.getName());
    
        /**
         *  Executes the first lab exercise.
         * 
         * @param args the command line arguments
         */
        public static void main(String[] args) {
            try {
                // 1st ODFDOM LAYER - PACKAGE / PHYSICAL LAYER:
                // Creating an ODF package containing an image not shown in the text
                firstExercicePart1();
            } catch (Exception ex) {
                LOG.log(Level.SEVERE, "Something weired happened in the first part..", ex);
            }
    
    //        try {
    //            // 2nd ODFDOM LAYER - TYPED DOM / XML LAYER:
    //            // Creating an ODF Text document showing the image from the package
    //            firstExercicePart2();
    //        } catch (Exception ex) {
    //            LOG.log(Level.SEVERE, "Something weired happened in the second part..", ex);
    //        }
    //
    //        try {
    //            // 3rd ODFDOM LAYER - CONVENIENCE LAYER:
    //            // Quite similar to the previous DOM handling, as a DOC layer is always a child
    //            // of a DOM layer object therefore as well a DOM tree.
    //            //
    //            //
    //            // Two things are done for the user behind the scene:
    //            //
    //            // 1) The package handling to insert the image file
    //            //
    //            // 2) The default size is received from the image via AWT helper and
    //            // added to the attributes of the sourrounding draw:frame.
    //            firstExercicePart3();
    //        } catch (Exception ex) {
    //            LOG.log(Level.SEVERE, "Something weired happened in the third part..", ex);
    //        }
        }
    
                                  
  5. Now implement the first exercise method. Notice that the output directory without path is the root of your current NetBeans project.
    The file is saved twice here with different extensions. With the .odt extension, you can open the file in OpenOffice.org, and with the .zip extension, the content can be viewed in NetBeans.

      
        /**
         * 1st ODFDOM LAYER - PACKAGE / PHYSICAL LAYER:
         * Creating an ODF package containing an image not shown in the text
         *
         * @throws java.lang.Exception
         */
        private static void firstExercicePart1() throws Exception {
            // ODF KNOW-HOW: One ODF Package is only a zipped container for arbitrary files of some document.
            OdfPackage pkg = OdfPackage.loadPackage(LAB_ROOT + "resources/"e1-input.odt");
            // Inserting an image into the 'Pictures' directory within the ODF package.
            pkg.insert(new FileInputStream(LAB_ROOT + "resources/e1-01_DukeToEmbed.png"), 
                    "Pictures/e1-01_DukeToEmbed.png");
    
            // Save the package with the picture; no path used here, will be stored in project root
            pkg.save("output_1_seems-empty.odt");
            // zip can be opened with NetBeans
            pkg.save("output_1_seems-empty.zip");
    
            // ODF-KNOW-HOW: Only images referenced from the text will be viewed in ODF.
            //              Therefore if the document is being opened (for instance with OpenOffice.org)
            //              The document appears to be empty.
            LOG.info("SUCCESS: You were able to save an ODF Package containing an image, which is not shown!");
        }
                                  
  6. Press CTRL+S to save your file. Then press CTRL+SHIFT+I to fix the missing imports from the libraries.
  7. Your project should now be able to run. Press F6 for this. If it does not work, verify your code or ask a proctor. The result is shown in Figure-04. Activate the "Files" tab where the resulting document is displayed. NetBeans offers a simple way to browse into zip files: just click on the symbol before the file, then open the "Pictures" folder.
    You can open the .odt file in OpenOffice.org from here, too: click right on the document and select "Open With OpenOffice.org"


    Verify the correct document

    Figure-04: Verify the correct document in Netbeans

  8. We implement the second exercise method. Please uncomment the execution of firstExercisePart2(); in the main(...) method and implement the method like this:

    
        /**
         * 2nd ODFDOM LAYER - TYPED DOM / XML LAYER
         * Creating an ODF Text document showing the image from the package
         *
         * @throws java.lang.Exception
         */
        private static void firstExercicePart2() throws Exception {
            // ODF-KNOW-HOW: An ODF Package contains one or more ODF Documents.
            //               The ODF XML elements part of an ODF Document.
            // Load the ODF Package as an ODF Document.
            OdfDocument odfDoc = OdfDocument.loadDocument("output_1_seems-empty.odt");
    
            // Get the ODF XML content as DOM tree representation.
            OdfFileDom odfContent = odfDoc.getContentDom();
    
            // ODF KNOW-HOW: The element name of a paragraph is <text:p>
            // Receiving the first paragraph  "//text:p[1]" (using XPath functionality from JDK).
            XPath xpath = getXPathForODF();
            OdfParagraphElement para = (OdfParagraphElement) xpath.evaluate("//text:p[1]", odfContent, XPathConstants.NODE);
    
            // ODF KNOW-HOW: An image consists always of a <draw:frame> parent and an embedded <draw:image> child.
            // Create the <draw:frame> ODF element.
            OdfFrameElement odfFrame = (OdfFrameElement) OdfElementFactory.createOdfElement(odfContent,        
            OdfFrame.ELEMENT_NAME);
    
            // Add the created frame to the earlier found first paragraph.
            para.appendChild(odfFrame);
    
            // create the <draw:image> ODF element viewing the previous (Part1 )inserted image
            OdfImageElement odfImage = (OdfImageElement) OdfElementFactory.createOdfElement(odfContent,
            OdfImage.ELEMENT_NAME);
    
            // Add the created image to the earlier created frame.
            odfFrame.appendChild(odfImage);
    
            // setting the xlink:href attribute
            odfImage.setHref("Pictures/e1-01_DukeToEmbed.png");
    
            // Save the document; no path used here, will be stored in project root
            odfDoc.save("output_2_image-visible-small.odt");
            // zip can be opened with NetBeans
            odfDoc.save("output_2_image-visible-small.zip");
            LOG.info("SUCCESS: You were able to save an ODF Document again, now showing the earlier added image (small)!!");
    
        }
                                
  9. For the second exercise method to work, we need some helper. This is the helper function:

        private static XPath mXPath;
    
        private static XPath getXPathForODF() {
            if (mXPath == null) {
                // W3C XPath initialization (i.e. JDK5 functionality)  -
                // XPath is the path within the XML file
                mXPath = XPathFactory.newInstance().newXPath();
                mXPath.setNamespaceContext(new OdfNamespace());
            }
            return mXPath;
        }
                                
  10. Again press CTRL+S to save your file, followed by CTRL+SHIFT+I to fix the missing imports. This time you will be asked for correct classes. Figure-05 shows the correct imports.


    The correct imports

    Figure-05: Correct imports for the used classes

  11. Your project should again be able to run. Press F6 for this. If it does not work, verify your code or ask a proctor. The result is shown in Figure-06. This time we use OpenOffice.org to verify: Open OpenOffice.org and click on "File - Open". Browse to your NetBeans project and open the file "e1-output-2.odt":


    Verify the correct document

    Figure-06: Image is visiable, but small

  12. In the last layer, the convenient layer ODFDOM adds enhanced functionality and eases the developer's life. We implement the third exercise method. Please uncomment the execution of firstExercisePart3(); in the main(...) method and implement the method like this:

      /**
         * 3rd ODFDOM LAYER - CONVENIENCE LAYER:
         * Quite similar to the previous DOM handling, as a DOC layer is always a child
         * of a DOM layer object therefore as well a DOM tree.
         *
         *
         * Two things are done for the user behind the scene:
         *
         * 1) The package handling to insert the image file
         * 
         * 2) The default size is received from the image via AWT helper and
         * added to the attributes of the sourrounding draw:frame.
         *
         * @throws java.lang.Exception
         */
        private static void firstExercicePart3() throws Exception {
    
            OdfTextDocument odfText = OdfTextDocument.createTextDocument();
            // Get the ODF XML content as DOM tree representation.
            OdfFileDom odfContent = odfText.getContentDom();
    
            // ODF KNOW-HOW: The element name of a paragraph is <text:p>
            // Receiving the first paragraph  "//text:p[1]" (using XPath functionality from JDK).
            XPath xpath = getXPathForODF();
            OdfParagraph para = (OdfParagraph) xpath.evaluate("//text:p[1]", odfContent, XPathConstants.NODE);
    
            // ODF KNOW-HOW: An image consists always of a <draw:frame> parent and an embedded <draw:image> child.
            // Create the <draw:frame> ODF element.
            OdfFrame odfFrame = (OdfFrame) OdfElementFactory.createOdfElement(odfContent, OdfFrame.ELEMENT_NAME);
    
            // Add the created frame to the earlier found first paragraph.
            para.appendChild(odfFrame);
    
            // create the <draw:image> ODF element to view the similar image file
            OdfImage odfImage = (OdfImage) OdfElementFactory.createOdfElement(odfContent, OdfImage.ELEMENT_NAME);
    
            // Add the created image to the earlier created frame.
            odfFrame.appendChild(odfImage);
    
            // NOTE: Loading the image (relativ to <LABROOT>/solution/solution1
            // Adding the image to the package, getting the default
            // size via AWT helper and setting the size at the draw:frame parent.
            odfImage.insertImage(new URI("../../odfdom/resources/e1-01_DukeToEmbed.png"));
    
            // Save the document; no path used here, will be stored in project root
            odfText.save("output_3_image-visible-big.odt");
            // zip can be opened with NetBeans
            odfText.save("output_3_image-visible-big.zip");
            LOG.info("SUCCESS: You were able to add an image into an ODF Text Document using the convenient API!!");
    
    
        }            
                
  13. Again press CTRL+S to save your file, followed by CTRL+SHIFT+I to fix the missing imports.


  14. Your project should again be able to run. Press F6 for this. If it does not work, verify your code or ask a proctor. The result is shown in Figure-07. This time we use OpenOffice.org to verify: Open OpenOffice.org and click on "File - Open". Browse to your NetBeans project and open the file "e1-output-3.odt":


    Verify the correct document

    Figure-07: Image is visable with it's default size

  15. For troubleshooting, the complete example should look like this (with first example method enabled):

    /*
     * To change this template, choose Tools | Templates
     * and open the template in the editor.
     */
    

Summary

 

In this exercise, you learned what the different layers of ODFDOM offer in regard of inserting an image file.

Exercise 2: Create Web Application Project (30 minutes)

 

The goal of this exercise is to let you build a simple Netbeans Web Application Project by following step by step instructions.


Steps to Follow

 

Step 1: Creating a web based Project

  1. If NetBeans is not already running, start it.
    1. Select File Menu
    2. Select New Project
    3. Select Category 'Java Web' and Project 'Web Application'
    4. Press 'Next'

    Figure-01: New Project: Choose Project

  2. In the next dialog, do the following
    1. Set 'Project Name' to CustomerInvoice
    2. Set 'Project Location' to '<lab_root>' - note for JavaOne attendees: this is correct as it is - just keep the default.
    3. Press 'Next'

    Figure-02: New Web Application: Name and Location

  3. In the next dialog, do the following
    1. Set 'Server' to 'Glassfish V2'
    2. Set 'Java EE Version' to 'Java EE 5'
    3. Press 'Finish'

    Figure-03: New Web Application: Server and Settings

  4. After the project creation, the index.jsp file is automatically opened by NetBeans. This is how it looks like:


    Figure-04: New Web Application: Project View

  5. The following code creates a web application form to get data. Replace the complete content of the index.jsp file with it:
    <%--
        Document   : index
        Created on : June 04, 2009, 01:19:08 PM
        Author     : your name
    --%>
    
    <%@page contentType="text/html" pageEncoding="UTF-8"%>
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
    
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
            <title>Customer Invoice</title>
        </head>
        <body>
            <h2>Create a Customer Invoice</h2>
            <form action="createInvoice.jsp" method="POST">
                <table border="0">
                    <tbody>
                        <tr>
                            <td>Name, First Name</td>
                            <td><input type="text" name="myName" value=""/></td>
                        </tr>
                        <tr>
                            <td>Street</td>
                            <td><input type="text" name="myStreet" value=""/></td>
                        </tr>
                        <tr>
                            <td>Zip Code</td>
                            <td><input type="text" name="myZip" value=""/></td>
                        </tr>
                        <tr>
                            <td>City</td>
                            <td><input type="text" name="myCity" value=""/></td>
                        </tr>
                    </tbody>
                </table>
                <table border="0">
                    <thead>
                        <tr>
                            <th>Product ID</th>
                            <th>Product Name</th>
                            <th>Quantity</th>
                            <th>Single Price</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td><input type="text" name="myProdID" value=""/></td>
                            <td><input type="text" name="myProdName" value=""/></td>
                            <td><input type="text" name="myProdQuantity" value=""/></td>
                            <td><input type="text" name="myProdPrice" value=""/></td>
                        </tr>
                        <tr>
                            <td><input type="text" name="myProdID" value=""/></td>
                            <td><input type="text" name="myProdName" value=""/></td>
                            <td><input type="text" name="myProdQuantity" value=""/></td>
                            <td><input type="text" name="myProdPrice" value=""/></td>
                        </tr>
                        <tr>
                            <td><input type="text" name="myProdID" value=""/></td>
                            <td><input type="text" name="myProdName" value=""/></td>
                            <td><input type="text" name="myProdQuantity" value=""/></td>
                            <td><input type="text" name="myProdPrice" value=""/></td>
                        </tr>
                    </tbody>
                </table>
                <input type="submit" value="Send" />
            </form>
        </body>
    </html>

    Do not forget to save the file.

  6. We are adding some jar libraries to our project - for future usage.

    Right-click the CustomerInvoice project and choose 'Properties'. In the 'Libraries' category, click 'Add JAR/Folder'. Add the following libraries to the project:

    • <lab_root>/resources/odfdom/jars/xercesImpl.jar
    • <lab_root>/resources/odfdom/jars/odfdom.jar

    For JavaOne attendees, the libraries are here:

    • /export/home/lab5569/odfdom/resources/odfdom/jars/xercesImpl.jar
    • /export/home/lab5569/odfdom/resources/odfdom/jars/odfdom.jar

    Click "OK" afterwards.


    Figure-05: Add required libraries to the project

  7. Press F6 to start the project. Verify the form in your browser. Note that it may take a while to start glassfish.


    createInvoice JSP File

    Figure-06: Verify the form in the browser

  8. In Netbeans, right-click the 'Web Pages' folder and create a new JSP file named "createInvoice". Use the default settings in the 'New JSP File' dialog.


    createInvoice JSP File

    Figure-07: createInvoice JSP File

  9. You have to add a couple of 'imports' to the "createInvoice.jsp" file:

    <%@page import="java.text.*,
                    java.util.*,
                    org.w3c.dom.*,
                    org.odftoolkit.odfdom.dom.OdfNamespace,
                    org.odftoolkit.odfdom.doc.OdfDocument,
                    org.odftoolkit.odfdom.doc.OdfFileDom,
                    org.odftoolkit.odfdom.doc.element.text.*,
                    org.odftoolkit.odfdom.doc.element.office.*,
                    org.odftoolkit.odfdom.doc.element.table.*,
                    javax.xml.xpath.*" %>
                                      

    Add required imports

    Figure-08: Add required imports

  10. Now change the text of the h1 element in "createInvoice.jsp" to 'Customer invoice generated.'
  11. Store the data which has been passed from the web front end form in variables. The data can be accessed using the getParameter() method of the request object. The parameter of the getParameter method is the name of the text input form which has been assigned in index.jsp. You will need to define the following variables:

        <%
            // Read received post parameter
            String name = request.getParameter("myName");
            String street = request.getParameter("myStreet");            
            String zip = request.getParameter("myZip");
            String city = request.getParameter("myCity"); 
            String[] productIds = request.getParameterValues("myProdID");
            String[] productNames = request.getParameterValues("myProdName");
            String[] productQuantities = request.getParameterValues("myProdQuantity");
            String[] productPrices = request.getParameterValues("myProdPrice");
        %>
                            

    This code has to be inserted after the h1 element and has to be enclosed by '<%' and '%>' to denote that this is Java code.


    Get data from request

    Figure-09: Get data from request

  12. We will extend the Java part in the "createInvoice.jsp" file. Now we start using the ODFDOM API. First we want to open a template bill file and get access to the content dom.

              ////////////////////////////////////////////////////////////////
              // Start ODFDOM handling
              ////////////////////////////////////////////////////////////////
              // 1. Load invoice template
              // if you are not attending JavaOneplease edit the <lab_root>
              OdfDocument document = OdfDocument.loadDocument("/export/home/lab5569/odfdom/resources/invoice_template.odt");
              // 1.1 Get the Content DOM of this Text Document
              OdfFileDom content = document.getContentDom();
    
                            

    Please note that you have to adjust the file location passed to the loadDocument method.

  13. Have a look at the template bill document. This document contains 4 placeholder fields: _NAME_, _STREET_, _ZIPCITY_, and _CURRENTDATE_. We want to replace the address placeholder fields with the data from the web front end and the date placeholder field should be replaced with the current date. For this we first create 4 new OdfSpan objects:

                               
              ////////////////////////////////////////////////////////////////
              // 2. Create OdfSpan elements for curstomer address and current date
              // 2.1 Add Name
              OdfSpan spanName = new OdfSpan(content);
              spanName.setTextContent(name);
              // 2.2 Street
              OdfSpan spanStreet = new OdfSpan(content);
              spanStreet.setTextContent(street);
              // 2.2 Zip And City
              OdfSpan spanCity = new OdfSpan(content);
              spanCity.setTextContent(zip + " " + city);
              // 2.3 Current Date
              OdfSpan spanDate = new OdfSpan(content);
              SimpleDateFormat dateFormater = new SimpleDateFormat();
              String dateStr = dateFormater.getDateInstance(DateFormat.FULL).format(new Date());
              spanDate.setTextContent(dateStr);
                            
  14. In order to find the placeholder fields in the template document, we use a xpath. The result is returned in a NodeList. Please note that the namespace context has to be set to the OdfNamespace. Alternatively, we could use

              ////////////////////////////////////////////////////////////////
              // 3. Find all placeholder elements
              XPath xpath = XPathFactory.newInstance().newXPath();
              xpath.setNamespaceContext(new OdfNamespace());
              NodeList placeholder = (NodeList)xpath.evaluate("//text:placeholder", content, XPathConstants.NODESET);
                            
  15. Now we can iterate over the found placeholder fields and replace them with our new spans:

              // 3.1 Apply Address Details and Date
              for(int i=0; i<placeholder.getLength(); i++) {
                  OdfPlaceholder pl = (OdfPlaceholder)placeholder.item(i);
                  OdfParagraph parentParagraph = (OdfParagraph)pl.getParentNode();
                  String placeholderText = pl.getTextContent();
                  if(placeholderText.indexOf("_NAME_") > 0) {
                      parentParagraph.replaceChild(spanName, pl);
                  } else if(placeholderText.indexOf("_STREET_") > 0) {
                      parentParagraph.replaceChild(spanStreet, pl);
                  } else if(placeholderText.indexOf("_ZIPCITY_") > 0) {
                      parentParagraph.replaceChild(spanCity, pl);
                  } else if(placeholderText.indexOf("_CURRENTDATE_") > 0) {
                      parentParagraph.replaceChild(spanDate, pl);
                  }
              }
                            
  16. We also want to fill the table in the template bill with the data from the web front end. So we first need to get access to the table in the template bill content dom:

              ///////////////////////////////////////////////////////
              // 4. Calculate Product Details and add them as Table Rows
              // 4.1 Find the Billing Table
              xpath = XPathFactory.newInstance().newXPath();
              xpath.setNamespaceContext(new OdfNamespace());
              OdfTable table = (OdfTable)xpath.evaluate("//table:table[1]", content, XPathConstants.NODE);
                            

    Since there's only one table in the template document, we can access this directly without using a NodeList.

  17. We append a new table row for each set of data obtained from the web frontend. Each of these rows will consist of 5 table cells containing a paragraph with the following data:

    • the product ID
    • the product name
    • the product quantity
    • the product single price and
    • the total price

    The style for the table cells should be a copied from the respective cells in the first table row. The style for the paragraphs in the table cells should be "Table_20_Contents". This style is a document style of the bill template document. The last row of the table should state the overall price in the last table cell.

              // 4.2 Find the first Row get the style info for table cell
              OdfTableRow firstRow = (OdfTableRow)table.getElementsByTagName("table:table-row").item(0);
              // 4.3 Calculate row sum to insert
              float priceOverall = 0;
              // 4.4 Insert rows for billing items
              for(int i=0; i < productIds.length; i++) {
                  OdfTableRow billingItemRow = new OdfTableRow(content);
                  // 4.4.1 Add cells billing item details
                  for(int j=0; j < 5; j++) {
                      // Create new Cell
                      OdfTableCell cell = new OdfTableCell(content);
                      // Get and set correct style name for this cell
                      cell.setStyleName( ((OdfTableCell)firstRow.getCellAt(j)).getStyleName() );
                      // Create new Paragraph
                      OdfParagraph paragraph = new OdfParagraph(content);
                      // Set correct style name for this cell
                      paragraph.setStyleName( "Table_20_Contents" );
                      // Apply text regarding the current cell index
                      if(j == 0) {
                          paragraph.setTextContent(productIds[i]);
                      } else if(j == 1) {
                          paragraph.setTextContent(productNames[i]);
                      } else if(j == 2) {
                          paragraph.setTextContent(productQuantities[i]);
                      } else if(j == 3) {
                          paragraph.setTextContent(productPrices[i]);
                      } else if(j == 4) {
                          float priceTotal = Float.parseFloat(productPrices[i])*Float.parseFloat(productQuantities[i]);
                          priceOverall += priceTotal;
                          paragraph.setTextContent(String.valueOf(priceTotal));
                      }
                      cell.appendChild(paragraph);
                      billingItemRow.appendCell(cell);
                  }
                  table.appendRow(billingItemRow);
              }
                            
  18. The last row of the table should state the overall price for the customer bill. It shall consist of two cells, the first of which has a column span of 4 and the second contains a new paragraph with the overall price. The paragraph style for this is "SumPrice".

                          
              // 4.5 Add last row showing overall price
              OdfTableRow lastRow = new OdfTableRow(content);
              OdfTableCell cell = new OdfTableCell(content);
              cell.setNumberColumnsSpanned(4);
              lastRow.appendCell(cell);
              cell = new OdfTableCell(content);
              OdfParagraph paragraphFullPrice = new OdfParagraph(content);
              paragraphFullPrice.setStyleName("SumPrice");
              paragraphFullPrice.setTextContent(String.valueOf(priceOverall));
              cell.appendChild(paragraphFullPrice);
              lastRow.appendCell(cell);
              table.appendRow(lastRow);
                            
  19. Finally, we save the file:

          
              ///////////////////////////////////////////////////////
              // 5. Save the resulting file
              // Save to the project directory
              // Edit the path if you are NOT a JavaOne attendee
              document.save("/export/home/lab5569/NetBeansProjects/CustomerInvoice/invoice_final.odt");
                            
  20. The whole "ceateInvoice.jsp" file can be viewed here:

    <%--
        Document   : createInvoice
        Created on : Mar 31, 2009, 01:41:35 PM
        Author     : your name
    --%>
    
                            
  21. You can now run the web application by pressing F6. If the application does not what you expect it to do, you can debug the code by pressing STRG+F5

    When you fill out the web form and click on send, the invoice_final.odt file is generated. Verify this by starting OpenOffice.org and load the invoice. It could look like this:


    The created customer invoice

    Figure-10: The created customer invoice


Challenge

 

The example above produces an exception when not all fields in the table are filled. Fix this.

Hint: Debug the project to verify where the exception actually happens.


Summary

 

In this exercise, you learned how to create a new Netbeans Web Application Project that gathers some data that is written into a document using the ODF Toolkit.

Exercise 3: Build a Web Report from Spreadsheet Data (20 minutes)

 

The goal of this exercise is to create a web page from the data available in a spreadsheet document. For this, the ODFDOM API will be used.


Steps to Follow

 
  1. Create a new Netbeans Web Application Project named 'Report' like in exercise 2.

    Figure-01: Create a web based project.

  2. Add the odfdom.jar and xercesImpl.jar libraries to the project like in exercise 2.

    • <lab_root>/resources/odfdom/jars/xercesImpl.jar
    • <lab_root>/resources/odfdom/jars/odfdom.jar

    For JavaOne attendees, the libraries are here:

    • /export/home/lab5569/odfdom/resources/odfdom/jars/xercesImpl.jar
    • /export/home/lab5569/odfdom/resources/odfdom/jars/odfdom.jar

    Click "OK" afterwards.


    Figure-05: Add required libraries to the project

  3. Add the imports to the 'index.jsp' file like in exercise 2.

     <%@page import="java.text.*,
                     java.util.*,
                     org.w3c.dom.*,
                     org.odftoolkit.odfdom.dom.OdfNamespace,
                     org.odftoolkit.odfdom.doc.OdfDocument,
                     org.odftoolkit.odfdom.doc.OdfFileDom,
                     org.odftoolkit.odfdom.doc.element.text.*,
                     org.odftoolkit.odfdom.doc.element.office.*,
                     org.odftoolkit.odfdom.doc.element.table.*,
                     javax.xml.xpath.*" %>
                            
  4. Change the text of the h2 element to 'Address Data Report' and insert an empty table behind it.

  5. Inside the table tag, enter the java code to open the report.ods spreadsheet document in the <lab_root>/resources folder. Get the content file dom from the document. Note: Make sure to enclose the java code using '<%' and '%>'.

                 ////////////////////////////////////////////////////////////////
                 // Start ODFDOM handling
                 // 1 Load source document
                 OdfDocument document = OdfDocument.loadDocument("/export/home/lab5569/odfdom/resources/report_data.ods");
                 // 2 Get the Content DOM of this source document
                 OdfFileDom content = document.getContentDom();
    
                            
  6. Use a xpath expression to receive a NodeList of all table rows:

                 // 3 Find the data rows
                 XPath xpath = XPathFactory.newInstance().newXPath();
                 xpath.setNamespaceContext(new OdfNamespace());
                 NodeList rows = (NodeList)xpath.evaluate("//table:table[1]//table:table-row", content, XPathConstants.NODESET);
    
                            
  7. Iterator over the table rows and cells. If a new row starts, use 'out.print("<tr>")' to start a new table row. If a row ends, use 'out.print("</tr>")' to close the respective table row tag. If a new cell starts, use 'out.print("<th>")' or 'out.print("<td>")'to start a new table (header) cell. If a cell ends, close the respective cell tag. Inside the cell you have to print the cell contents:

                 // 4 Print data to report table
                 out.print("<table border=\"1\">");
                 for (int i = 0; i < rows.getLength(); ++i){
                    OdfTableRow row = (OdfTableRow)rows.item(i);
                    out.print("<tr>");
                    NodeList tableRowChilds = row.getChildNodes();
                    for (int j = 0; j < tableRowChilds.getLength(); ++j){
                        OdfTableCell cell = (OdfTableCell)tableRowChilds.item(j);
                        out.print(i == 0 ? "<th>" : "<td>");
                        String textContent = cell.getTextContent();
                        out.print(textContent);
                        out.print(i == 0 ? "</th>" : "</td>");
                    }
                    out.print("</tr>");
                 }
                 out.print("</table>");
    
                            
  8. You can now run the web application by pressing F6. If the application does not what you expect it to do, you can debug the code by pressing STRG+F5. The output should look like this:

    Figure-03: The created report


Summary

 

In this exercise, you learned how to use the ODFDOM API to load, manipulate and store documents.

Exercise 4: Export Data from OpenOffice.org as .csv (30 minutes)

 

In this exercise, an export function (with some additions, it could develop into a filter) for OpenOffice.org is written, using the odf toolkit. This exercise shows how data from a spreadsheet can be manipulated with Java inside of the OpenOffice.org process.

For this, an Extension for OpenOffice.org is created. Extensions can bring additional functionalities to OpenOffice.org that are missing from the original program. OpenOffice.org can be customized with Extensions.


Steps to Follow

 
  1. Configure the "OpenOffice.org API plugin for Netbeans". See http://wiki.services.openoffice.org

  2. Create a new OpenOffice.org Add-On project

    Click "Next".


    Figure-01: Create an Add-On project.

  3. Enter the following names in the correct places. Confer to the screenshot, if you are in doubt. Project Name should be "ExportExample". The Main Class Name will change accordingly. Enter "odfdom.example4" as Java package.

    Note: Do not forget to change <lab_root> with something meaningful.

    Finally make sure to have "Create Toolbar" enabled and "Create Menu" disabled. This will produce an extension to OpenOffice.org that brings its own toolbar with it.

    Click "Next" again afterwards.


    Figure-02: Define Project Names

  4. Enter "Export" in the spaces shown in the screenshot.

    Click "Next" again afterwards.


    Figure-03: Define Function Name

  5. Simply enable the "Spreadsheet" context here.

    Click "Finish".


    Figure-04: Enable Spreadsheet

  6. Import the same jar files as described in exercise 2, step 6.

  7. You do not need to add any new files, since everything is generated by NetBeans and the plugin. Open "ExportExample.java". Look for the dispatch(...) method. When someone clicks on the button that was defined during project creation, this method will be executed. Add the following lines:

    try {
        exportFile();
    } catch (Exception ex) {
        Logger.getLogger(ExportExample.class.getName()).log(Level.SEVERE, null, ex);
    }
    

    The complete dispatch(...) method should look like this:

    // com.sun.star.frame.XDispatch:
    public void dispatch(com.sun.star.util.URL aURL,
            com.sun.star.beans.PropertyValue[] aArguments) {
        if (aURL.Protocol.compareTo("odfdom.example4.ExportExample:") == 0) {
            if (aURL.Path.compareTo("Export") == 0) {
                try {
                    exportFile();
                } catch (Exception ex) {
                    Logger.getLogger(ExportExample.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
    

  8. Now implement the exportFile() function. Do not forget to edit the <lab_root> path. In this function lies the challenge of this exercise, but more to that later.

    First store the current file as a temporary document.

        /**
         * Export the csv file
         */
        private void exportFile() throws Exception {
            ////////////////////////////////////////////////////////////////
            // OpenOffice.org API handling
            // 0 save file first
            XModel xModel = m_xFrame.getController().getModel();
            XStorable xStorable = (XStorable) UnoRuntime.queryInterface(XStorable.class, xModel);
            File file = File.createTempFile("temp", ".ods");
            String url = getUrl(file);
            xStorable.storeToURL(url, new PropertyValue[0]);
    

    Then create a file to write the content to.

            ////////////////////////////////////////////////////////////////
            // 1 Create a file for output
            String exportFileName = "output.csv";
            FileWriter exportFileWriter = new FileWriter(exportFileName);
    
            // CHALLENGE!
    
            // close the writer
            exportFileWriter.close();
        }
    

  9. Implement the getUrl() helper function.

        /**
         * Get OpenOffice.org URL from a system file
         * @param file a file on the file system
         * @return the path of the file as OpenOffice.org URL
         */
        private String getUrl(File file) {
            String path = file.getPath().replace('\\', '/');
            if (!path.startsWith("/")) {
                path = "/".concat(path);
            }
            path = "file://".concat(path);
            return path;
        } 

  10. This is the complete "ExportExample.java" file.

    package odfdom.example4;
    
    
  11. Select the "Projects" tab and then click right on the project node and select "Debug in Target OpenOffice.org" to verify that the Add-On works and to debug it.

    Figure-05: Debug the project in OpenOffice.org

  12. To trigger the functionality, open Calc in OpenOffice.org and click on "Export" to execute the new function.
    For the actual functionality, take a look at the challenge.

    Figure-06: Result


Challenge

 

The example until now is meaningless. Use the created variable exportFileWriter to write data to the csv file, and get the data from the stored ods document.


Hint: reuse code from example 3 to collect the data from the spreadsheet (ods file).


Summary

 

In this exercise, the toolkit was used as a part of the OpenOffice.org functionality to add new functions to the OpenOffice.org feature set.

Congratulations! You have successfully completed LAB-5569: ODFDOM - Changing ODF documents using the new opensourced multi-tiered API.

Where to send questions or feedbacks on this lab and public discussion forums: