LibreOffice SDK Guide: Chapter 51. Simple ODF

    From The Document Foundation Wiki
    Note pin.svg

    Note:
    Simple API for ODF was deprecated since 0.9 and eventually got removed in 0.10 release.

    An OpenDocument Format (ODF) document is a zipped folder containing an assortment of XML files, images, and other resources. This makes it possible to manipulate using zip/unzip and XML features, but that isn't a good idea due to the complexity of the formats. This chapter looks at a few of these lower-level techniques, but is mainly about the Simple API for ODF, a sub-project of the ODF Toolkit. It's a small Java API for creating, modifying and extracting data from ODF documents, built on top of the ODFDOM library. Rather surprisingly, its support for the concatenation of documents is better than that in the Office API.

    1. The OpenDocument Format

    The contents of an ODF document (i.e. the zipped folder) depend on its type (e.g.

    does it contain a spreadsheet, presentation, or text?), but several files always appear inside the folder:

    • content.xml: the document's textual contents, but not binary data such as images;
    • meta.xml: the document's meta information, such as the author's name and the last modification date. The data is most easily viewed through Office's File > Properties menu item;
    • styles.xml: the styles used for the document's pages, paragraphs, text formats, and others, which are usually set via the Styles and Formatting dialogs in Office;
    • settings.xml: information specific to the application and document's display, such as the window's size/position, printer settings, and whether headers and footers are visible;
    • manifest.xml: this lists the content of the zipped folder, and is stored in the META-INF/ subdirectory;
    • mimetype: a one-line file listing the document's MIME-type.

    The easiest way of viewing these files and folders is to unzip the ODF file using a utility such as 7-Zip, as in Figure 1.

    SDK 51-Simple ODF-1.png

    Figure 1. An Unzipped View of algs.odp.

    Figure 1's Configurations2/ folder stores localization information for the Office GUI; Pictures/ stores the images used in the document; Thumbnails/ holds a small 128x128 picture of the document in thumbnail.png.

    The Wikipedia page about ODF is quite informative, as is its entry for the OpenDocument technical specification. There's a quick summary of the OpenDocument Format (ODF) here.

    The ODF standard was developed by the Organization for the Advancement of Structured Information Standards (OASIS) consortium, whose website hosts a lot of information. A related community site is opendocument.xml.org.

    The most complete textbook on ODF is:

    OASIS OpenDocument Essentials J. David Eisenberg, 2005

    The associated website has a free draft of the book, and all the examples and support code. The site often seems to be offline, but the book can be found at other locations.

    A recent version can be purchased at Lulu.

    2. Document Information

    There's little need to directly manipulate a document's XML files since the Office API offers high-level techniques for accessing most of their information.

    For example, part of meta.xml for algs.odp is:

    <office:meta>
      <dc:title>PowerPoint Presentation</dc:title>
      <dc:date>2017-01-06T16:53:58.639000000</dc:date>
      <meta:editing-cycles>4</meta:editing-cycles>
      <meta:editing-duration>PT1M52S</meta:editing-duration>
      <meta:generator>LibreOffice/5.1.0.3$
          Windows_x86LibreOffice_project/
          5e3e00a007d9b3b6efb6797a8b8e57b51ab1f737</meta:generator>
      <meta:document-statistic meta:object-count="92"/>
      <meta:user-defined meta:name="Secret">Made in
                                Thailand</meta:user-defined>
    </office:meta>

    This data is displayed in the General tab of the File, Properties window in Figure 2.

    SDK 51-Simple ODF-2.png

    Figure 2. File, Properties Window.

    Rather than attempting to extract and parse meta.xml, this information is reachable through the XDocumentProperties interface which I described back in Chapter 3, section 2.

    The following DocInfo example uses Info.printDocProperties() to print document details:

    // in DocInfo.java
    public class DocInfo
    {
      public static void main(String args[])
      {
        if (args.length < 1) {
          System.out.println("Usage: run DocInfo <fnm>");
          return;
        }
    
        XComponentLoader loader = Lo.loadOffice();
        XComponent doc = Lo.openDoc(args[0], loader);
        if (doc == null) {
          System.out.println("Could not open " + args[0]);
          Lo.closeOffice();
          return;
        }
    
        System.out.println();
        Props.showObjProps("Document", doc);
        Info.printDocProperties(doc);
    
        Lo.closeDoc(doc);
        Lo.closeOffice();
      }  // end of main()
    
    }  // end of DocInfo class

    When DocInfo is passed algs.odp, some of the output is:

    Document Properties Info
      Author:
      Title: PowerPoint Presentation
      Subject:
      Description:
      Generator: LibreOffice/5.1.0.3$Windows_x86 LibreOffice_project/
                         5e3e00a007d9b3b6efb6797a8b8e57b51ab1f737
         :
      Modification Date: Jan 06, 2017 16:53
         :
      Secret == Made in Thailand

    Info.printDocProperties() calls many XDocumentProperties.getXXX() methods to obtain these details:

    // in the Info class
    public static void printDocProperties(Object doc)
    {
      XDocumentPropertiesSupplier docPropsSupp =
               Lo.qi(XDocumentPropertiesSupplier.class, doc);
      XDocumentProperties dps = docPropsSupp.getDocumentProperties();
      printDocProps(dps);
    
      XPropertyContainer udProps = dps.getUserDefinedProperties();
      Props.showObjProps("UserDefined Info", udProps);
    }  // end of printDocProperties()
    
    
    public static void printDocProps(XDocumentProperties dps)
    {
      System.out.println("Document Properties Info");
    
      System.out.println("  Author: " + dps.getAuthor());
      System.out.println("  Title: " + dps.getTitle());
      System.out.println("  Subject: " + dps.getSubject());
         :  // many more prints
    }  // end of printDocProps()

    XDocumentProperties replaces the deprecated XDocumentInfo interface, which has been removed from LibreOffice (although it's still in OpenOffice). The two classes manipulate almost the same information, but the older XDocumentInfo can retrieve the document's MIME-type, which is absent from XDocumentProperties. One way of obtaining this would be to access the mimetype file zipped inside the document, as shown next.

    3. Unzipping an ODF Document

    The DocUnzip example shows how it's possible to list the zipped contents of a document, access its MIME-type, and extract a zipped file:

    // in DocUnzip.java
    public class DocUnzip
    {
      public static void main(String args[])
      {
        if ((args.length < 1) || (args.length > 2)) {
          System.out.println("Usage: run DocUnzip <fnm> [<ExtractFnm>]");
          return;
        }
        XComponentLoader loader = Lo.loadOffice();
    
        FileIO.zipList(args[0]);
        // FileIO.zipListUno(args[0]);   // only names listed
    
        // get zip access to the document
        XZipFileAccess zfa = FileIO.zipAccess(args[0]);
    
        String mimeType = FileIO.getMimeType(zfa);
        System.out.println("MIME type: " + mimeType);
        System.out.println("Other MIME type approach: " +
                                    Info.getMIMEType(args[0]));
    
        // convert MIME-type too other forms
        int docType = Info.mimeDocType(mimeType);
        System.out.println("Doc Type: " + docType + "; " +
                                     Lo.docTypeStr(docType));
    
        if (args.length == 2)   // extract the named file
          FileIO.unzipFile(zfa, args[1]);
    
        Lo.closeOffice();
      }  // end of main()
    }  // end of DocUnzip class

    The program can be called like so:

    run DocUnzip algs.odp content.xml

    content.xml will be unzipped from algs.odp and saved as contentCopy.xml in the local directory.

    The rest of the output is:

    Listing of algs.odp:
    Raw Size    Size     Date        Time         Name
    --------  -------  -------      -------      --------
    47        47       Jan 6, 2017  9:54:00 AM   mimetype
    0         0        Jan 6, 2017  9:54:00 AM   Configurations2/popupmenu/
    0         0        Jan 6, 2017  9:54:00 AM   Configurations2/floater/
    0         0        Jan 6, 2017  9:54:00 AM   Configurations2/images/Bitmaps/
    0         2        Jan 6, 2017  9:54:00 AM
    Configurations2/accelerator/current.xml
    0         0        Jan 6, 2017  9:54:00 AM   Configurations2/menubar/
    0         0        Jan 6, 2017  9:54:00 AM   Configurations2/progressbar/
    0         0        Jan 6, 2017  9:54:00 AM   Configurations2/toolbar/
    0         0        Jan 6, 2017  9:54:00 AM   Configurations2/statusbar/
    0         0        Jan 6, 2017  9:54:00 AM   Configurations2/toolpanel/
    83608     6501     Jan 6, 2017  9:54:00 AM   styles.xml
    4364      4364     Jan 6, 2017  9:54:00 AM
    Pictures/100000000000004600000035E6F1CB2181A9ACF1.png
    16977     16977    Jan 6, 2017  9:54:00 AM
    Pictures/10000000000003080000002A0A348B9039C3652D.png
    20480     20480    Jan 6, 2017  9:54:00 AM
    Pictures/10000000000001F4000001F4E2E69E1D.jpg
    45056     45056    Jan 6, 2017  9:54:00 AM
    Pictures/10000000000000C8000000EEB0A3D2D2.jpg
    8192      8192     Jan 6, 2017  9:54:00 AM
    Pictures/10000000000000E1000000E1B343DD04.jpg
    40960     40960    Jan 6, 2017  9:54:00 AM
    Pictures/10000000000000E60000015E784CAA37.jpg
    69632     69632    Jan 6, 2017  9:54:00 AM
    Pictures/10000000000000DC000000FF9A43DBBF.jpg
    28672     28672    Jan 6, 2017  9:54:00 AM
    Pictures/100000000000011800000160792BAA16.jpg
    12288     12288    Jan 6, 2017  9:54:00 AM
    Pictures/10000000000000DC0000014B7CFE8C49.jpg
    11901     11901    Jan 6, 2017  9:54:00 AM
    Pictures/10000000000000BC0000010DD7ECB1F7.jpg
    18238     18238    Jan 6, 2017  9:54:02 AM   Thumbnails/thumbnail.png
    86313     8040     Jan 6, 2017  9:54:02 AM   content.xml
    6037      913      Jan 6, 2017  9:54:02 AM   settings.xml
    1141      491      Jan 6, 2017  9:54:02 AM   meta.xml
    2248      452      Jan 6, 2017  9:54:02 AM   META-INF/manifest.xml
    
    
    MIME type: application/vnd.oasis.opendocument.presentation
    Other MIME type approach: application/vnd.oasis.opendocument.presentation
    Doc Type: 5; simpress
    
    Extracting content.xml
    Saving to contentCopy.xml

    Most of the output is a detailed listing of the zipped contents of algs.odp.

    3.1. Listing the Contents of a Zipped Folder

    There are two "zipList" functions in the FileIO utility class. FileIO.zipListUno() is simpler since only file and folder names are printed:

    // in the FileIO class
    public static void zipListUno(String fnm)
    {
      XZipFileAccess zfa = zipAccess(fnm);
      XNameAccess nmAccess = Lo.qi(XNameAccess.class, zfa);
      String[] names = nmAccess.getElementNames();
    
      System.out.println("\nZipped Contents of " + fnm);
      Lo.printNames(names, 1);
    }  // end of zipListUno()
    
    
    public static XZipFileAccess zipAccess(String fnm)
    // get zip access to the document using Office API
    {
      return Lo.createInstanceMCF(XZipFileAccess.class,
                             "com.sun.star.packages.zip.ZipFileAccess",
                             new Object[]{ fnmToURL(fnm) });
    }

    XZipFileAccess is created by instantiating the ZipFileAccess service with the document's filename. The interface is cast to XNameAccess which allows the names of the zipped files to be retrieved as an array of strings. Figure 3 shows the relationships between the service and interfaces.

    SDK 51-Simple ODF-3.png

    Figure 3. The ZipFileAccess Service.

    The ZipFileAccess service and interfaces are in the com.sun.star.packages.zip module, which includes a ZipEntry class for holding information about each zipped file (e.g. its compressed size). I was unable to find a way of creating ZipEntry objects, but Java contains a complete zip API. By using Java rather than Office, I was able to implement a more fancy "zipList":

    // in the FileIO class
    public static void zipList(String fnm)
    // using the Java API
    {
      DateFormat df= DateFormat.getDateInstance();   // date format
      DateFormat tf= DateFormat.getTimeInstance();   // time format
      tf.setTimeZone( TimeZone.getDefault() );
      try {
        ZipFile zfile = new ZipFile(fnm);
        System.out.println("Listing of " + zfile.getName() + ":");
        System.out.println("Raw  Size  Size  Date   Time   Name");
        System.out.println("---  ----  ----  ----   ----   ----");
        Enumeration<? extends java.util.zip.ZipEntry> zfs =
                                                zfile.entries();
        while (zfs.hasMoreElements()) {
          java.util.zip.ZipEntry entry =
                  (java.util.zip.ZipEntry) zfs.nextElement();
          Date d = new Date(entry.getTime());
          System.out.print( padSpaces(entry.getSize(), 9) + " ");
          System.out.print( padSpaces(entry.getCompressedSize(),7)+ " ");
          System.out.print(" " + df.format(d) + " ");
          System.out.print(" " + tf.format(d) + "  ");
          System.out.println(" " + entry.getName());
        }
        System.out.println();
      }
      catch (java.io.IOException e)
      {  System.out.println(e); }
    }  // end of zipList()

    Another advantage of switching to Java are the large number of online examples of zip manipulation; the zipList() function is closely based on one at https://drdobbs.com/jvm/java-and-the-zip-file-format/184410339/.

    3.2. Extracting a MIME-type

    FileIO.getMimeType() employs XZipFileAccess.getStreamByPattern() to access the zipped mimetype file as an input stream.

    // in the FileIO class
    public static String getMimeType(XZipFileAccess zfa)
    {
      try {
        XInputStream inStream = zfa.getStreamByPattern("mimetype");
        String[] lines = FileIO.readLines(inStream);
        if (lines != null)
          return lines[0].trim();
      }
      catch (com.sun.star.uno.Exception e)
      {  System.out.println(e);  }
    
      System.out.println("No mimetype found");
      return null;
    }  // end of getMimeType()
    
    There's an alternative approach which looks up the file's MIME-type  using the Java
    API class MimetypesFileTypeMap:
    
    // in the Info class
    // global
    private static final String MIME_FNM = "mime.types";
    
    
    public static String getMIMEType(String fnm)
    {
      try {
        MimetypesFileTypeMap mftMap = new MimetypesFileTypeMap(
                             FileIO.getUtilsFolder() + MIME_FNM);
        return mftMap.getContentType(new File(fnm));
      }
      catch(java.lang.Exception e)
      {  System.out.println("Could not find " + MIME_FNM);
         return "application/octet-stream";   // better than nothing
      }
    }  // end of getMIMEType()
    
    The MimetypesFileTypeMap() constructor examines a list of MIME-types loaded
    from the utility classes folder.
    
    Most of the utility functions use document 'types' coded as integers rather than as
    MIME-type strings; the values are defined at the start of the Lo utility class:
    
    // in the Lo class
    public static final int UNKNOWN = 0;
    public static final int WRITER = 1;
    public static final int BASE = 2;
    public static final int CALC = 3;
    public static final int DRAW = 4;
    public static final int IMPRESS = 5;
    public static final int MATH = 6;
    
    Info.mimeDocType() maps ODF MIME-type strings to one of these integers:
    
    // in the Info class
    public static int mimeDocType(String mimeType)
    {
      if (mimeType == null)
        return Lo.UNKNOWN;
    
      if (mimeType.contains("vnd.oasis.opendocument.text"))
        return Lo.WRITER;
      else if (mimeType.contains("vnd.oasis.opendocument.base"))
        return Lo.BASE;
      else if (mimeType.contains("vnd.oasis.opendocument.spreadsheet"))
        return Lo.CALC;
      else if (mimeType.contains("vnd.oasis.opendocument.graphics") ||
               mimeType.contains("vnd.oasis.opendocument.image") ||
               mimeType.contains("vnd.oasis.opendocument.chart"))
        return Lo.DRAW;
      else if (mimeType.contains("vnd.oasis.opendocument.presentation"))
        return Lo.IMPRESS;
      else if (mimeType.contains("vnd.oasis.opendocument.formula"))
        return Lo.MATH;
      else return Lo.UNKNOWN;
    }  // end of mimeDocType()

    Some of the functions also utilize short document type strings, which are also defined in the Lo class:

    // in the Lo class
    // docType strings
    public static final String UNKNOWN_STR = "unknown";
    public static final String WRITER_STR = "swriter";
    public static final String BASE_STR = "sbase";
    public static final String CALC_STR = "scalc";
    public static final String DRAW_STR = "sdraw";
    public static final String IMPRESS_STR = "simpress";
    public static final String MATH_STR = "smath";

    Lo.docTypeStr() maps document type integers to these strings.

    3.3. Extracting a Zipped File

    The XZipFileAccess.getStreamByPattern() method used in FileIO.getMimeType() is also employed by FileIO.unzipFile() to extract a zipped file:

    // in FileIO class
    public static void unzipFile(XZipFileAccess zfa, String fnm)
    {
      String fileName = Info.getName(fnm);
      String ext = Info.getExt(fnm);
      try {
        System.out.println("Extracting " + fnm);
        XInputStream inStream = zfa.getStreamByPattern("*" + fnm);
    
        XSimpleFileAccess3 fileAcc =
              Lo.createInstanceMCF(XSimpleFileAccess3.class,
                        "com.sun.star.ucb.SimpleFileAccess");
    
        String copyFnm =  (ext == null) ? (fileName + "Copy") :
                                          (fileName + "Copy." + ext);
        System.out.println("Saving to " + copyFnm);
        fileAcc.writeFile(FileIO.fnmToURL(copyFnm), inStream);
      }
      catch (com.sun.star.uno.Exception e)
      {  System.out.println(e);  }
    }  // end of unzipFile()

    The XZipFileAccess.getStreamByPattern() call includes the wildcard character "*" so the filename will be found even if prefixed by a directory path, as in the case of an ODF document's manifest.xml which is stored in "META-INF/manifest.xml".

    I utilize Office's IO interface, XSimpleFileAccess3, which supports writeFile() to directly connect an XInputStream to a file. The filename of the extracted data is constructed from the zipped filename with the addition of "Copy".

    4. Higher-level Manipulation of ODF

    Manipulating an ODF document as zipped files and XML is a recipe for highly complex (and probably bug-ridden) code. And why bother when the Office API offers all the necessary functionality in a higher-level framework?

    "Maslow's hammer" maxim comes to mind; it states: "if all you have is a hammer, then everything looks like a nail". In other words, it's useful to know about ODF APIs other than Office's since they might offer better, easier ways of doing some tasks.

    As a consequence, we are going to spend the rest of the chapter looking at the Simple Java API for ODF, a sub-project of ODF Toolkit. It's a relatively small Java API for creating, modifying and extracting data from ODF documents, built on top of the ODFDOM library.

    Its best features are the cookbook pages, the Getting Started Guide, and its demo pages.

    The cookbook contains examples for manipulating text documents, presentations, tables in text documents and spreadsheets, charts, style handling, text navigation, text extraction, text fields, text forms, and document metadata. The API documentation is here, and a mail archive here.

    By far the worst feature of the API is the large number of libraries that must be installed before even a lowly "Hello World" example will work. Fortunately, Copies of those libraries are included alongside the examples code.

    The Simple API doesn't have the depth of features of Office, so it's best suited for the creation of simple text documents, spreadsheets, and presentations. However, it has better support for combining documents (e.g. concatenating two slide decks into a single one), and can move/copy slides in a presentation much more easily than Office; look back at Chapter 17, sections 4 and 5 for the complaints about that.

    4.1. Building Simple Documents

    This subsection looks at how the Simple API can be used to create text, spreadsheet, and slide documents.

    Make a Text Document the MakeTextDoc example creates a text document containing an image, some text, a list, and a table.; the result is shown in Figure 4.

    SDK 51-Simple ODF-4.png

    Figure 4. The Output of MakeTextDoc.

    This isn't quite what the output looks like in the "Getting Started Guide", where the program is called HelloWorld – in the guide the first paragraph is beneath the image rather than to one side.

    The MakeTextDoc code:

    public class MakeTextDoc
    {
      public static void main(String[] args)
      {
        try {
          TextDocument doc = TextDocument.newTextDocument();
          doc.newImage(new URI("odf-logo.png"));
    
          // add paragraphs and list
          doc.addParagraph("Hello World, Hello Simple ODF!");
          doc.addParagraph("The following is a list.");
          List list = doc.addList();
          String[] items = {"item1", "item2", "item3"};
          list.addItems(items);
    
          // add table
          Table table = doc.addTable(2, 2);
          Cell cell = table.getCellByPosition(0, 0);
          cell.setStringValue("Hello World!");
    
          System.out.println("Creating MakeTextDoc.odt");
          doc.save("MakeTextDoc.odt");
        }
        catch (Exception e) {
          System.out.println(e);
        }
      }  // end of main()
    
    } // end of MakeTextDoc class

    I was unable to find a way of changing the image's text wrap property to affect the paragraph's position. It's probably do-able using the lower-level ODFDOM library, but the programming information for that part of ODF Toolkit is mostly limited to its API documentation. However, there's a good introductory tutorial here.

    Make a Spreadsheet

    The MakeSheet example creates a spreadsheet with a single sheet, and a few cells of data, as in Figure 5.

    SDK 51-Simple ODF-5.png

    Figure 5. The Output of MakeSheet.

    The program:

    public class MakeSheet
    {
      public static void main(String[] args)
      {
        try {
          SpreadsheetDocument doc =
                 SpreadsheetDocument.newSpreadsheetDocument();
          Table sheet = doc.getSheetByIndex(0);
          sheet.getCellByPosition(0, 0).setStringValue("Hello");
          for (int row = 0; row < 5; row++)
            sheet.getCellByPosition(1, row).setDoubleValue(row*2.0);
    
          System.out.println("Saving document to makeSheet.ods");
          doc.save("makeSheet.ods");
        }
        catch (Exception e)
        {  System.out.println(e); }
      }  // end of main()
    
    }  // end of MakeSheet class

    Make a Slide Deck

    The MakeSlides example creates a slide deck. The first slide contains a title, the second some bulleted text and a picture, and the third slide is blank, as in Figure 6.

    SDK 51-Simple ODF-6.png

    Figure 6. The Output of MakeSlides.

    The program:

    public class MakeSlides
    {
      public static void main(String[] args)
      {
        try {
          PresentationDocument doc =
                    PresentationDocument.newPresentationDocument();
    
          // a title slide
          Slide slide1 = doc.newSlide(0, "slide1",
                                       SlideLayout.TITLE_ONLY);
          Textbox titleBox = slide1.getTextboxByUsage(
                                 PresentationClass.TITLE).get(0);
          titleBox.setTextContent("Important Slide Presentation");
    
          // a slide with text bullets and a picture
          Slide slide2 = doc.newSlide(1, "slide2",
                                     SlideLayout.TITLE_OUTLINE);
          titleBox = slide2.getTextboxByUsage(
                                 PresentationClass.TITLE).get(0);
          titleBox.setTextContent("Overview");
    
          Textbox outline = slide2.getTextboxByUsage(
                                 PresentationClass.OUTLINE).get(0);
          List txtList = outline.addList();   // two bullets
          txtList.addItem("Item 1");
          txtList.addItem("Item 2");
    
          Image image = Image.newImage(slide2, new URI("skinner.png"));
          FrameRectangle rect = image.getRectangle();
          rect.setX(8);     // position the image
          rect.setY(4);
          image.setRectangle(rect);
    
          System.out.println("Saving document to makeSlides.odp");
          doc.save("makeSlides.odp");
        }
        catch (Exception e)
        { System.out.println(e);  }
      }  // end of main()
    
    }  // end of MakeSlides class

    Programming with this part of the API is a littlie tricky since slide elements are represented by different types of boxes. I also found it hard to determine the position of the image, except by trial-and-error.

    4.2. Moving a Slide

    Back in Chapter 17, section 4, I had to use copy and paste dispatch commands in the slide-sorter view to rearrange a deck. Moving a slide in the Simple API is much easier, as shown in MoveSlide example, which moves the first slide of the deck to its end:

    public class MoveSlide
    {
      public static void main(String[] args)
      {
        try {
          PresentationDocument doc =
                 PresentationDocument.loadDocument("algs.odp");
          int numSlides = doc.getSlideCount();
          System.out.println("Moving first slide to the end");
          doc.moveSlide(0, numSlides);   // why not numSlides-1?
    
          System.out.println("Saving document to algsMoved.odp");
          doc.save("algsMoved.odp");
          doc.close();
        }
        catch (Exception e)
        {  System.out.println(e); }
      }  // end of main()
    
    }  // end of MoveSlide class

    PresentationDocument.moveSlide() works without a hitch, although it is confusing why we need to supply numSlides as the second numerical argument rather than numSlides-1.

    There's also a PresentationDocument.copySlide() method for copying.

    4.3. Combining Two Documents

    This subsection presents three short programs that combine two text documents, appends the sheets of two spreadsheets, and concatenates two slide decks. These examples are much simpler to program than equivalent ones using the Office API.

    Combining Text Documents

    CombineTexts example adds the contents of doc2.odt to the end of the contents of doc1.odt, separating them with a page break. The result is saved to combined.odt:

    public class CombineTexts
    {
      public static void main(String[] args)
      {
        try {
          TextDocument doc1 = TextDocument.loadDocument("doc1.odt");
          TextDocument doc2 = TextDocument.loadDocument("doc2.odt");
    
          doc1.addPageBreak();
          Paragraph lastPara = doc1.getParagraphByReverseIndex(0, false);
    
          // insert contents at end and copy styles
          doc1.insertContentFromDocumentAfter(doc2, lastPara, true);
    
          System.out.println("Saving combination to combined.odt");
          doc1.save("combined.odt");
          doc1.close();
          doc2.close();
        }
        catch (Exception e)
        {  System.out.println(e); }
      }  // end of main()
    
    }  // end of CombineTexts class

    The crucial method is TextDocument.insertContentFromDocumentAfter().

    Combining Spreadsheets

    CombineSheets example places the sheets in ss2.odt after the sheets in ss1.odt, and saves the result in combined.ods:

    public class CombineSheets
    {
      public static void main(String[] args)
      {
        try {
          SpreadsheetDocument doc1 =
                  SpreadsheetDocument.loadDocument("ss1.ods");
          SpreadsheetDocument doc2 =
                  SpreadsheetDocument.loadDocument("ss2.ods");
          int numSheets2 = doc2.getSheetCount();
    
          // add sheets of second document to end of first doc
          for(int i=0; i < numSheets2; i++) {
            Table t = doc2.getSheetByIndex(i);
            doc1.appendSheet(t, t.getTableName());
          }
          System.out.println("Saving combination to combined.ods");
          doc1.save("combined.ods");
          doc1.close();
          doc2.close();
        }
        catch (Exception e)
        {  System.out.println(e); }
      }  // end of main()
    
    }  // end of CombineSheets class

    Sadly there isn't a single method that appends tables. Instead we have used a loop to append each sheet from ss2.ods after the sheets in ss1.ods.

    Combining Slide Decks

    CombineDecks example adds the slides in deck2.odp after the slides in deck1.odp, saving the result in combined.odp:

    public class CombineDecks
    {
      public static void main(String[] args)
      {
        try {
          PresentationDocument doc1 =
                  PresentationDocument.loadDocument("deck1.odp");
          PresentationDocument doc2 =
                  PresentationDocument.loadDocument("deck2.odp");
          doc1.appendPresentation(doc2);
          System.out.println("Saving combination to combined.odp");
          doc1.save("combined.odp");
          doc1.close();
          doc2.close();
        }
        catch (Exception e)
        {  System.out.println(e); }
      }  // end of main()
    
    }  // end of CombineDecks class

    The PresentationDocument.appendPresentation() method does all the work, and should be compared to the hacky Office solution in Chapter 17, section 5.

    Cc-by new white.svgCc-sa white.svg

    This work is licensed under the Attribution Share-Alike 4.0 Unported License

    Topics: The OpenDocument Format; Doc Information; Unzipping an ODF Doc; the Simple Java API for ODF (Apache ODF Toolkit): making docs (text, sheet, and slides), slide movement, doc concatenation (text, sheet, and slides)

    Example folders: odftoolkit and Utils