ODFDOM for Java: Simplifying programmatic control of documents and their data, Part 2

The second of a three-part series, this article provides details of the code structure of the Open Document Format (ODF) Document Object Model (DOM) for Java™. We introduce the layered architecture of the ODFDOM and the compositions and functions of each layer, including the relationship between the layers.

Wei Hua Wang (weihuaw@cn.ibm.com), Software Engineer, IBM

Wei Hua Wang is a Software Engineer based at the IBM China Development Lab, where she's a member of the Lotus Symphony Team. Mainly focusing on the development of ODF interoperability, she's also an IBM development team member of the ODF Toolkit open source community. She can be reached at weihuaw@cn.ibm.com.



30 March 2010

Also available in Chinese Russian Portuguese

Editor's note: Know a lot about this topic? Want to share your expertise? Participate in the IBM Lotus software wiki program today.

Overview of the ODFDOM layered model

ODFDOM for Java provides a lightweight Java API for developers who want to create, access, and save ODF documents without having to gain detailed knowledge of the full ODF standard specification.

ODFDOM adopts a hierarchical, multilayer structure, with each layer having a specific purpose. The bottom layer has no dependency on the upper layers due to a loose-coupling design. Figure 1 illustrates the hierarchical structure of the ODFDOM layered model.

Figure 1. ODFDOM layered model
ODFDOM layered model

These layers are summarized as follows:

  • Customized ODF document / extendable layer. This layer is hereafter called the customized layer. Although not a part of the ODFDOM package, it is designed as a layer on top of ODFDOM, in which users are able to overwrite or customize the existing ODFDOM API to meet a specified requirement.
  • ODF document / convenient functionality layer. This layer is hereafter called the convenience layer and is the focus of developers because it's based on the DOM layer, thus providing the most rich, usable APIs for users to perform document operations.
  • ODF typed DOM / XML layer. This layer is hereafter referred to as the DOM layer. The ODFDOM specification and grammar (RelaxNG schema) defines all the possible existing ODF XML elements and attributes and their relationship in the standardized ODF XML streams; that is, all the XML files in the ODF package (for example, content.xml, styles.xml).

    The DOM layer provides the necessary information relevant to XML elements and attributes used to build the Document Object Model. Instead of all the classes of this layer being laboriously written, they are generated automatically by the ODF specification, so that this layer can be easily changed when the ODF specification is improved and up-leveled.

  • ODF package / physical layer. This layer is hereafter referred to as the package layer in this article. As the bottom layer of the ODFDOM hierarchical structure, it provides direct access to the physical storage in the ODF package, such as XML streams, pictures, and embedded objects.

In the remainder of this article, we delve into each layer to understand its functions and relationships in more detail.


ODFDOM package layer

The naming of the ODFDOM bottom layer originated from the ODF document structure.

ODF document structure

The ODFDOM document is represented by a bundle of named resources compressed into a package, which is the biggest difference between it and other document file formats.

You can change the extension of ODF documents to .zip; when you extract the package, you get file streams such as content.xml, styles.xml, settings.xml, meta.xml, mimetype, manifest.xml, and an image referred to in the ODF document (see figure 2).

Figure 2. ODF document structure
ODF document structure

All types of ODF documents have this same file structure, and some complex ODF documents might also include OLE objects in the package. In figure 2, all the streams except the Pictures folder and its content are specified by the ODF standard and are defined as follows:

  • Mimetype. Specifies the type of ODF document, the most common of which are text documents, spreadsheet documents, and presentation documents. Even if the file extension of the ODF document is not correct, you can easily identify the document type by extracting this file.
  • content.xml, styles.xml, meta.xml, and settings.xml. Specifies the content of the current document, including the text content, pictures, the styles applied to the content, and document meta information. These four XML file schemas are constrained by the ODF specification, including the elements context, the relationship between elements and attributes, and the value set of the attributes.

    You can easily access these elements through the DOM layer API that is introduced in the next section.

  • META-INF\manifest.xml. Lists all the file entries in the ODF package. Each file entry contains the file name, type, and encryption information. This XML file grammar is also defined by the ODF Manifest schema.

Package layer functions

The Package layer does the following:

  • Manipulates documents in the package layer that deal with the physical storage directly. This manipulation includes getting a certain file input stream from the document package, updating, inserting, or deleting a file stream, and other basic operations.

    This layer provides the foundation for the convenience layer API to open, save, or modify the ODF document and frees up the convenience layer to work with only document-layer operations, rather than the package-layer activities.

  • Handles META-INF\manifest.xml parser and construction. The package layer not only reads and writes the XML streams but also handles the content of META-INF\manifest.xml. According to the ODF manifest schema, this file enlists all the file streams contained in the ODF package.

    The Package Layer is also responsible for parsing and constructing the manifest.xml file and updating this file when a picture has been inserted or other file streams have been changed.

    If an ODF document has been encrypted or digitally signed, the manifest.xml file is not encrypted; it preserves the key and digest for each encrypted file stream. Currently, ODFDOM does not support this function.

  • Performs lazy loading. When importing an ODF document, not all the file streams in the package need to be loaded into memory. For example, if you want to add only a sentence to the ODF text document, two file streams, content.xml and META-INF/manifest.xml, need to be modified, so only these two files are loaded on demand.

    The package layer reads the loaded file stream and, if it is in XML format, the DOM layer parses the stream according to the ODF specification and constructs the DOM tree in memory.

    After a user modifies the document by using the convenience layer API, the save operation is triggered, and the package layer transforms the modified content DOM to an output stream, overwriting the original content.xml and updating the manifest.xml.

The package layer can be used alone, independent of the upper layers, and users can modify the ODF document at the stream or package level.

Listing 1 shows a simple example of a package layer API that inserts a picture into the sample.odt package. Note that this sample code merely inserts the picture in the ODF package, rather than inserting the picture reference into the document content.

Listing 1. Code to insert a picture in the ODF package (package layer API)
import org.odftoolkit.odfdom.pkg.OdfPackage;
[...]

// loads the ODF document package from the path
OdfPackage pkg = OdfPackage.loadPackage("C:\sample.odt");

// loads the image from the URL and inserts the image in the package, 
pkg.insert(new URI("C:\helloWorld.png"), "Pictures/helloWorld.png", "image/png");

// save it including adapting the manifest
pkg.save("C:\sample.odt");

You can get all the source code of the package layer implementation from the ODFDOM Java package named org.odftoolkit.odfdom.pkg.*.


ODFDOM DOM layer

As a standardized document format, ODF specification is developed by the OASIS standards development organization. Applications that correctly implement the ODF specification are able to interoperate and exchange their ODF files with other applications.

All the XML files can be accessed through the W3C DOM API, which builds an in-memory DOM tree composed of all the XML nodes of this file, and with the W3C DOM API users can easily add and remove nodes or modify the attribute of this node in this tree.

Note, however, that in this layer only the ODF standardized XML files can be constructed as the ODFDOM tree.

DOM layer functions

The DOM layer is responsible for parsing and constructing the XML files in the ODF package except the manifest.xml file, which operates in the package layer. By parsing, an in-memory DOM tree can be constructed with the DOM nodes inserted, each node corresponding to a DOM layer class representing the ODF XML element or attribute.

The definition of these classes is specified by the ODF specification. If the XML node is not defined by the ODF specification, the class org.odftoolkit.odfdom.pkg.element.OdfAlienElement/ org.odftoolkit.odfdom.pkg.element.OdfAlienAttribute is constructed.

This class is used to represent the foreign XML nodes, which remain in the document model and aren't neglected. When you modify the ODF DOM tree, the value of the XML node is also defined in each implementation class.

The DOM layer provides a foundation for constructing the DOM tree that is compliant with the ODF standard specification.This compliance ensures good interoperability between ODFDOM and other ODF applications.

The DOM layer API is also independent from the upper layer, so you can use this layer to modify the DOM tree.

DOM layer composition

All sources of the DOM Layer are organized in the org.odftoolkit.odfdom.dom.* package. Currently, the ODFDOM is compliant with the latest ODF 1.2 schema. It contains 599 element classes and 1301 attribute classes, all of which implement the org.w3c.dom.Node interface of W3C DOM API, to ensure that every XML element and attribute can represent the DOM node.

The package path of these element and attribute classes is dependent on the namespace of the XML node, whose naming is composed by use of the namespace, local name, and node type (see listing 2).

Listing 2. Example code snippet of the ODF specification
<define name="table-table">
	<element name="table:table">
		<ref name="table-table-attlist"/>
		<optional>
			<ref name="table-title"/>
		</optional>
		<optional>
			<ref name="table-desc"/>
		</optional>
		<optional>
			<ref name="table-table-source"/>
		</optional>
		<optional>
			<ref name="office-dde-source"/>
		</optional>
		<optional>
			<ref name="table-scenario"/>
		</optional>
		<optional>
			<ref name="office-forms"/>
		</optional>
		<optional>
			<ref name="table-shapes"/>
		</optional>
		<ref name="table-columns-and-groups"/>
		<ref name="table-rows-and-groups"/>
	</element>
</define>
…………
<define name="table-table-attlist" combine="interleave">
	<optional>
		<attribute name="table:name">
			<ref name="string"/>
		</attribute>
	</optional>
</define>

Listing 2 extracts a snippet of ODF specification that defines an element called <table:table>, so the class path for this element is org.odftoolkit.odfdom.dom.table.TableTableElement.

The reference table-table-attrlist defines the attribute table:name of this element <table:table>, so the attribute class path is org.odftoolkit.odfdom.dom.TableNameAttribute.

Each element class is an abstract class derived from org.odftoolkit.odfdom.OdfElement, and each class provides methods for getting and setting its own attributes and creating its allowed child elements that are all defined by the ODF specification.

Listing 3 shows a portion of the methods of the org.odftoolkit.odfdom.dom.table.TableTableElement class.

Listing 3. org.odftoolkit.odfdom.dom.table.TableTableElement code snippet
/**
* Receives the value of the ODFDOM attribute representation 
<code>TableNameAttribute</code> , See {@odf.attribute table:name}
*
 * @return - the <code>String</code> , the value or <code>null
 </code>, if the attribute is not set and no default value defined.
*/
public String getTableNameAttribute()
{
TableNameAttribute attr = (TableNameAttribute) getOdfAttribute
(OdfName.get
(OdfNamespace.get(OdfNamespaceNames.TABLE), "name" ) );
if( attr != null ){
return String.valueOf( attr.getValue() );
}
return null;
}

/**
 * Sets the value of ODFDOM attribute representation <code>
 TableNameAttribute</code> , See {@odf.attribute table:name}
*
 * @param tableNameValue   The type is <code>String</code>
 */
public void setTableNameAttribute( String tableNameValue )
{
TableNameAttribute attr =  new TableNameAttribute( 
(OdfFileDom)this.ownerDocument );
setOdfAttribute( attr );
attr.setValue( tableNameValue );
}
…………
/**
 * Create child element {@odf.element table:table-row}.
 *
 * @return   return  the element {@odf.element table:table-row}
 * DifferentQName 
 */
public TableTableRowElement newTableTableRowElement()
{
TableTableRowElement  tableTableRow = 
((OdfFileDom)this.ownerDocument).newOdfElement
(TableTableRowElement.class);
this.appendChild( tableTableRow);
return  tableTableRow;
}

The parent of an attribute class is org.odftoolkit.odfdom.OdfAttribute, and every attribute class specifies the value type and default value for its elements. The value type of some attributes references the data type defined by W3C, so by implementing these data types in the org.odftoolkit.odfdom.type.*package, we can easily check the attribute value.

DOM layer code generation

ODF 1.2 is the latest version of the standard. The ODF Technical Committee ensures that the ODF schema is maintained and regularly updated and improved, requiring that the DOM layer classes are also updated when the schema changes.

Due to the ODF specification's large size, it is impossible to update the DOM layer classes manually, so we generate them with the ODF schema instead. This automatic generation guarantees complete and accurate coverage of the ODF specification and easy updates to future ODF versions.

Users can run the mvn -P codegen command in the root directory of the ODFDOM project to generate all the DOM layer source code within 20 seconds.

At present, code generation is a stand-alone project that is separate from the ODFDOM project. You can see the ODFDOM Wiki Page to download the source code of ODFDOM and the code-generation project.

http://odftoolkit.org/projects/odfdom/pages/Home

Running the code-generation execution file requires four parameters, which are all configured in the pom.xml file of the ODFDOM project (see listing 4).

Listing 4. Parameter configuration of code generation
<configuration>
<sourceRoot>${basedir}/src/main/java</sourceRoot>
<schemaFile>${basedir}/src/codegen/resources/dom/
OpenDocument-schema-v1.2-cd02-rev02.rng</schemaFile>
<configFile>${basedir}/src/codegen/resources/dom/config.xml</configFile>
<templateFile>${basedir}/src/codegen/resources/dom/
javacodetemplate.xml</templateFile>
</configuration>

where:

  • Parameter 1: sourceRoot points to the destination of the generated source code.
  • Parameter 2: schemaFile specifies the location of ODF schema. Users can replace any version of the schema file to get the corresponding DOM layer code.
  • Parameter 3: configFile lists the base class and family of some XML elements, the default value of attributes (because the parameter 2 schema file does not contain the default value but is defined in ODF specification), and the class implementation of data type defined by W3C.
  • Parameter 4: templateFile defines the template of each generated element and attribute class.

ODFDOM convenience layer

From previous sections, we know that the package layer can obtain a stream from the ODF package, the DOM layer can then construct and operate the DOM tree freely, and then the modified stream can be saved back to the package.

Operating on the ODF-specified elements and attributes to manipulate the documents can be a complex process for developers, especially if they are not familiar with the ODF specification and XML parser.

Convenience layer goals

The convenience layer API, a higher usability programming interface, is designed for frequently used document-operation scenarios. Without manipulating the DOM tree, users can write a minimum amount of source code to accomplish a series of document operations, such as inserting a table, updating a chart in a document, and even copying and pasting text between different documents.

Furthermore, users do not need to care about the specific content of the file stream in the ODF package. The convenience layer API implements a suite of APIs for each document feature, and one feature might cover multiple XML elements.

You can imagine how convenient it is that we need only three lines to create a table: open the document, insert a table in some position, and then save it.

Convenience layer functions

Here we introduce some key functions of the convenience layer:

  • Document-level operation. Unlike the package level, the document level can create and save different types of ODF documents (.odt, .odp, .ods files), and also embed a document into another document.
  • Convenience Layer and DOM Layer mapping relationship. Usually a class of the convenience layer is derived from the corresponding DOM layer element class to inherit the DOM functionality. The name of the convenience class is similar to the parent class, but with the new prefix “Odf” and the suffix “Element” omitted.

    For example, in the case of the <draw:frame> element, the DOM layer class name is org.odftoolkit.odfdom.dom.element.draw.DrawFrameElement, and the convenience layer class is named org.odftoolkit.odfdom.doc.draw.OdfDrawFrame.

    But this design seems to conflict with the convenience layer goal, which is to focus on the feature, where one feature might control multiple XML elements. As a result, we might opt to refactor the convenience layer API by using a delegation design pattern instead of inheritance.

    In other words, each feature in the convenience layer delegates and organizes related element classes of the DOM Layer to implement the convenience layer API.

    For example, in the Table feature, which is used to insert, delete, or modify the table of the document, the 1:1 inheritance relationships between convenience layer classes and DOM layer classes have been broken up, so composite relationships are used instead.

    Thus, the table feature class takes the DOM element class named org.odftoolkit.odfdom.dom.element.table.TableTableElement as the class member.

  • Convenience APIs. ODFDOM version 0.8 was released on 19 February 2010. It adds many exciting convenience layer APIs, including the text navigation API, with which users are able to manipulate the text, image, table features and easily search and replace text content.

    In addition, an incubator package was added for navigating through the ODF text document, providing a function that searches text by text pattern or styles and performs operations on the selected text.

All sources of the convenience layer are organized in the package org.odftoolkit.odfdom.doc.*.


Conclusion

The ODFDOM API employs a layered approach to access documents. This article has introduced the compositions and functions of the layers and the relationship between adjacent layers. Users have the flexibility to work on different layers according to their requirements.


Download

DescriptionNameSize
Code samplePart2-ODFDOM-Architecture.zip10KB

Resources

Learn

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into IBM collaboration and social software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Lotus
ArticleID=476621
ArticleTitle=ODFDOM for Java: Simplifying programmatic control of documents and their data, Part 2
publish-date=03302010