Creating Presentation Documents Using ODFDOM
The input for the program in this tutorial is a text file
with these contents:
// Each line contains:
// slide title, country name, city name, image path
Rathaus,Germany,Hamburg,../src/images/rathaus.jpg
Namdaemun (남대문),South Korea,Seoul (서을),../src/images/namdaemun.jpg
The program’s output will be
an OpenDocument presentation file.
Here is a
slightly edited screenshot of what the first slide will look like.
The area at the left that looks like a table is not an
OdfTable like the one
in the previous tutorial. Instead,
it is just a set of four rectangles that have text labels.
The program is written to be run from the command line program; you invoke
it with a command like this:
java -jar simple_odp.jar inputfile.txt outputfile.ods
The Structure of a Presentation Document
In order for the following code to make sense, you need to know how a
presentation document is represented in OpenDocument.
An ODF presentation’s
content.xml file
contains an <office:presentation> element.
Each of its <draw:page> children represents one slide.
within the document.
Geometric shapes such as rectangles and circles are drawn directly on to the page. Text and images
are placed inside a “frame” that contains the position and dimensions of
that particular object; in ODFDOM, these are represented by the OdfDrawFrame class.
Input Variables
The name of the input file will come straight from args[0]
rather than be stored in a separate variable.
In order to make it easier to manipulate the input file, the program will create
an object of an auxiliary class SlideInfo for each line in the
file. This class has the following members
and constructors:
class SlideInfo
{
String country;
String city;
String imagePath;
String title;
public SlideInfo(String title, String country, String city,
String imagePath)
{
this.title = title.trim();
this.country = country.trim();
this.city = city.trim();
this.imagePath = imagePath.trim();
}
public SlideInfo(String[] info)
{
this(info[0], info[1], info[2], info[3]);
}
// getter/setter methods for members omitted to save space.
}
The main class declares an ArrayList to hold the
input data:
ArrayList<SlideInfo> slideList;
Output Variables
In this program, the name of the
output file will also come straight from args[1]
rather than being stored in a separate variable.
However, as in the previous tutorials,
we do need variables for the presentation document and
its “access points.”
OdfPresentationDocument outputDocument;
OdfOfficePresentation officePresentation;
OdfFileDom contentDom; // the document object model for content.xml
OdfFileDom stylesDom; // the document object model for styles.xml
OdfOfficeAutomaticStyles contentAutoStyles;
The presentation needs to create automatic styles in the
content.xml only, so we will dispense with variables
for accessing the styles.xml named and automatic
styles.
Once again, because the ODFDOM toolkit automatically generates unique
style names for you, the program must keep track of their names
in order to apply those styles
to elements.
String slideTitleStyleName;
String imageStyleName;
String fauxTableRectangleStyleName;
String fauxTableTextStyleName;
These styles are used for the slide title, the image, the rectangles that make
up the fake (faux) table and the text inside those rectangles.
This having been done, here is the main() method,
which creates an application and runs it via the run()
method:
public static void main(String[] args) {
SimpleOdp app = new SimpleOdp();
if (args.length == 2) {
app.run(args);
}
else {
System.err.println("Usage: java -jar simple_odp.jar infile outfile");
}
}
void run(String[] args) {
slideList = new ArrayList<SlideInfo>();
processInput(args[0]);
setupOutputDocument();
if (slideList != null && outputDocument != null) {
setupOutputDocument();
cleanOutDocument();
newStyles();
createPages();
saveOutputDocument(args[1]);
}
}
Processing the Input Document
This function parses the input file. It does not use any calls
to ODFDOM, but it is given here for the sake of completeness. If
the parsing goes well, the slideList is filled;
otherwise it is set to null.
private void processInput(String inputFileName) {
BufferedReader dataFile;
String data;
try {
dataFile = new BufferedReader(new FileReader(inputFileName));
data = dataFile.readLine();
while (data != null) {
if (!(data.startsWith("//") || data.matches("^\\s*$"))) {
slideList.add(new SlideInfo(data.split("\\s*,\\s*")));
}
data = dataFile.readLine();
}
dataFile.close();
} catch (FileNotFoundException e) {
System.err.println("Unable to open file" + inputFileName);
slideList = null;
} catch (IOException e) {
System.err.println("Error reading file " + inputFileName);
slideList = null;
}
}
Creating the Output Document
The setupOutputDocument() method starts by calling
newPresentationDocument() to create an ODF presentation
document from a template that is built into the library. Once you
have the document, the method gets the the Document Object Model (a
subclass of Document) for the content.xml and
the <office:presentation> element that is the content
root. All of the data that make up the
document’s content will be children of this element.
void setupOutputDocument() {
try {
outputDocument =
OdfSpreadsheetDocument.newSpreadsheetDocument();
contentDom = outputDocument.getContentDom();
contentAutoStyles = contentDom.getOrCreateAutomaticStyles();
officePresentation = outputDocument.getContentRoot();
} catch (Exception e) {
System.err.println("Unable to create output file.");
System.err.println(e.getMessage());
outputDocument = null;
}
}
Clearing Content from the Output Document
The templates included in the ODFDOM toolkit have content in them; a
newly-created presentation document contains an empty page. The
cleanOutDocument() method gets rid of any existing
pages, and is built along the same lines as
described in the
first tutorial, so we won’t duplicate the code here.
Adding Styles
Here is the code for adding the four styles that this
program needs. The OdfStyleGraphicProperties.Clip
property is a rectangle that tells how much of the image to clip.
The values are for the top, right, bottom, and left side of the
image (clockwise from 12:00). In this case, you want the entire
picture, so all four values are set to zero.
void addAutomaticStyles() {
OdfStyle style;
/* The style for the slide title */
style = contentAutoStyles.newStyle(OdfStyleFamily.Presentation);
slideTitleStyleName = style.getStyleNameAttribute();
style.setProperty(OdfStyleGraphicProperties.AutoGrowHeight, "true");
style.setProperty(OdfStyleGraphicProperties.MinHeight, "3cm");
style.setProperty(OdfStyleTextProperties.FontFamily, "Helvetica");
style.setProperty(OdfStyleTextProperties.FontSize, "44pt");
/* Style for the image element */
style = contentAutoStyles.newStyle(OdfStyleFamily.Graphic);
imageStyleName = style.getStyleNameAttribute();
style.setProperty(OdfStyleGraphicProperties.MinHeight, "10cm");
style.setProperty(OdfStyleGraphicProperties.Clip,
"rect(0cm, 0cm, 0cm, 0cm)");
/* Graphic style for the table rectangles */
style = contentAutoStyles.newStyle(OdfStyleFamily.Graphic);
fauxTableRectangleStyleName = style.getStyleNameAttribute();
style.setProperty(OdfStyleGraphicProperties.FillColor,
"#ffffff");
style.setProperty(OdfStyleGraphicProperties.TextareaVerticalAlign,
"middle");
style.setProperty(OdfStyleGraphicProperties.PaddingLeft, "8pt");
/* Style for the text in the table */
style = contentAutoStyles.newStyle(OdfStyleFamily.Paragraph);
fauxTableTextStyleName = style.getStyleNameAttribute();
style.setProperty(OdfStyleTextProperties.FontFamily, "Helvetica");
style.setProperty(OdfStyleTextProperties.FontSize, "16pt");
}
Creating the Slides
The beginning of the createPages() method establishes the
variables. The counter variable lets you give
consecutive style names to each of the slides.
void createPages() {
OdfDrawPage page;
OdfDrawFrame frame;
OdfDrawImage image;
OdfDrawTextBox textBox;
OdfTextParagraph para;
int counter = 0;
Now iterate through the slide information. The constructor for a new
OdfDrawPage requires the name of a “master page,”
but you may safely leave this as the empty string or null.
Since the title is text rather than a graphic primitive such as a line, it
must be placed inside a OdfDrawFrame.
The title’s position and dimensions are set in the frame as the
the x, y, width and
height. In the XML, these attributes are borrowed from
the SVG (Scalable Vector Graphics) namespace, hence the Svg
prefix.
/* Each slide goes on its own page */
for (SlideInfo info : slideList) {
page = (OdfDrawPage) officePresentation.newDrawPageElement("");
page.setDrawNameAttribute("pg" + Integer.toString(++counter));
/* Create the title in a <draw:frame> element */
frame = (OdfDrawFrame) page.newDrawFrameElement();
frame.setPresentationStyleNameAttribute(slideTitleStyleName);
frame.setPresentationClassAttribute(
PresentationClassAttribute.Value.TITLE.toString());
frame.setSvgXAttribute("1.4cm");
frame.setSvgYAttribute("0.85cm");
frame.setSvgWidthAttribute("25cm");
frame.setSvgHeightAttribute("3.5cm");
/* Which contains a <draw:text-box> that has a
<text:p> inside it */
textBox = (OdfDrawTextBox) frame.newDrawTextBoxElement();
para = (OdfTextParagraph) textBox.newTextPElement();
para.addContent(info.getTitle());
Next, the page needs the image. It also is inside a frame.
Inserting the image with the insertImage method
does all the work for you; it brings the file into the
Pictures directory of the ODF document structure.
If the image can’t be found or there’s some other
type of error, the program does nothing rather than throw
an exception.
Because we want all the images to have the same width (12cm),
we must read the iamge again with ImageIO.read
and calculate the correct height for the image.
try {
frame = (OdfDrawFrame) page.newDrawFrameElement();
frame.setPresentationStyleNameAttribute(imageStyleName);
frame.setPresentationClassAttribute(
PresentationClassAttribute.Value.GRAPHIC.toString());
image = (OdfDrawImage) frame.newDrawImageElement();
image.insertImage(new URI(info.getImagePath()));
BufferedImage buffer = ImageIO.read(new File(
info.getImagePath()));
frame.setSvgXAttribute("14cm");
frame.setSvgYAttribute("6cm");
/*
* Always make the picture 12cm wide, and adjust
* the height in proportion to the width
*/
frame.setSvgWidthAttribute("12cm");
frame.setSvgHeightAttribute(Double.toString(
(12.0 * buffer.getHeight()) / buffer.getWidth())
+ "cm");
/* Put in an empty paragraph--not necessary, but doesn't hurt
anything */
para = (OdfTextParagraph) image.newTextPElement();
} catch (Exception e) {
// do nothing; no picture will be added.
}
Finally, each page has to have the rectangles with the country and city names.
This task is delegated to a separate method:
page.appendChild(createDrawnTable(info));
}
}
Labelled Rectangles
The code to create the “table” creates an
OdfDrawGroup of the four rectangles and
returns that group to be inserted into the page. If you open the
resulting file in OpenOffice.org and click the table, you will be
able to ungroup it into its component rectangles.
The createDrawnTable in turn calls the helper
method createTextRectangle, whose parameters are as
described in the javadoc.
private OdfDrawGroup createDrawnTable(SlideInfo info) {
OdfDrawGroup group = new OdfDrawGroup(contentDom);
group.appendChild(createTextRectangle(
"Country", "1.5cm", "6cm", "4.7cm", "1cm"));
group.appendChild(createTextRectangle(
info.getCountry(), "6.2cm", "6cm", "7cm", "1cm"));
group.appendChild(createTextRectangle(
"City", "1.5cm", "7cm", "4.7cm", "1cm"));
group.appendChild(createTextRectangle(
info.getCity(), "6.2cm", "7cm", "7cm", "1cm"));
return group;
}
/*
* Create a rectangle that contains text.
* @param textValue the text to display inside the rectangle
* @param x coordinate of left corner of rectangle
* @param y coordinate of upper corner of rectangle
* @param width width of rectangle
* @param height height of rectangle
*/
private OdfDrawRect createTextRectangle(String textValue, String x, String y,
String width, String height) {
OdfDrawRect r = new OdfDrawRect(contentDom);
OdfTextParagraph p;
r.setSvgXAttribute(x);
r.setSvgYAttribute(y);
r.setSvgWidthAttribute(width);
r.setSvgHeightAttribute(height);
r.setDrawStyleNameAttribute(fauxTableRectangleStyleName);
p = new OdfTextParagraph(contentDom);
p.setStyleName(fauxTableTextStyleName);
p.appendChild(contentDom.createTextNode(textValue));
r.appendChild(p);
return r;
}
Saving the Output Document
This is the easiest part of the program: only one line of actual code,
surrounded by error handling.
void saveOutputDocument()
{
try
{
outputDocument.save(outputFileName);
}
catch (Exception e)
{
System.err.println("Unable to save document.");
System.err.println(e.getMessage());
}
}
The Entire Program
You may download the src directory
for this program. This directory comes from a
NetBeans project.