Skip to content

Tutorial

Julien Férard edited this page Jun 20, 2019 · 19 revisions

This tutorial is extracted from https://github.com/jferard/fastods/tree/master/fastods-examples/src/main/java/com/github/jferard/fastods/examples. See the classes in this directory for the full code.

Writing a full documentation would be a considerable work, because every time you change the library, you have to rewrite the doc.

My idea is to provide a set of examples of the features of FastODS.

Before you start, add the following dependency to your POM (Maven users):

 <dependency>
     <groupId>com.github.jferard</groupId>
     <artifactId>fastods</artifactId>
     <version>0.6.1</version>
 </dependency>

For other build tools, look at: https://search.maven.org/artifact/com.github.jferard/fastods/0.6.1/jar

Hello, world!

Let's start with the famous "Hello, World!" example.

As stated in the javadoc, "An OdsFactory is the entry point for creating ODS documents." Every time you want to create an ODS document, you'll start with something like that:

    final OdsFactory odsFactory = OdsFactory.create(Logger.getLogger("hello-world"), Locale.US);

Now, you can create an ODS writer. You have the choice: either you give the filename now, or you keep it for the end. Let's create an anonymous writer: the content will live in memory until we save it to a file:

    final AnonymousOdsFileWriter writer = odsFactory.createWriter();

The writer owns a document that we need to create a spreadsheet:

    final OdsDocument document = writer.document();

Okay, let's go. We create a new table (a new sheet indeed) named "hello-world":

    final Table table = document.addTable("hello-world");

We get the first row:

    final TableRow row = table.getRow(0);

And the first cell of the first row:

    final TableCell cell = row.getOrCreateCell(0);

Note that we could have chained the calls: TableCell cell = document.addTable("hello-world").getRow(0).getOrCreateCell(0) Finally, we put the famous sentence in this cell A1

    cell.setStringValue("Hello, world!");

And save the file.

    writer.saveAs(new File("generated_files", "a_hello_world_example.ods"));

With a mvn clean verify at the root of the project, you can check the result in the fastods-examples/generated-files directory.

Accessing Tables, Rows and Cells

Now, we want to write values in other cells that the A1 cell.

We start with the (now) usual boilerplate code to get a document:

    final OdsFactory odsFactory = OdsFactory.create(Logger.getLogger("accessing"), Locale.US);
    final AnonymousOdsFileWriter writer = odsFactory.createWriter();
    final OdsDocument document = writer.document();

Direct Access to Cells

You know how to create a table:

    final Table table1 = document.addTable("direct-access");

Get the first row and the first cell of the first row:

    TableRow row = table1.getRow(0);
    TableCell cell = row.getOrCreateCell(0);

And set a value.

    cell.setStringValue("A1");

Note that the access is a write only access. You can't read the content of a cell. You can have direct access to any cell, but it has a cost. If the row doesn't exist yet in the list of rows, it will create all rows between the last row and the actual row.

    row = table1.getRow(6);

You can have direct access to cells

    cell = row.getOrCreateCell(1);
    cell.setStringValue("B7");

With a TableHelper, direct access might be easier to write...

    final TableHelper tableHelper = TableHelper.create();

...but note that the cell is referred by row then column (as matrices in maths). To access the cell "F2", you'll use:

    cell = tableHelper.getCell(table1, 1, 5);
    cell.setStringValue("F2");

You can use an address, but there is the cost of parsing that address and a risk of malformed address:

    try {
        cell = tableHelper.getCell(table1, "E3");
        cell.setStringValue("E3");
    } catch (final ParseException e) {
        /* this won't happen here! */
    }

To be (almost) complete, there is another way to write a value to a cell:

    try {
        tableHelper.setCellValue(table1, "D4", CellValue.fromObject("D4"));
    } catch (final ParseException e) {
        /* this won't happen here! */
    }

Relative Access

Direct access may be useful, but FastODS was designed for a relative access Create a new table:

    final Table table2 = document.addTable("relative-access");

We want ten rows of data

    for (int r = 0; r < 10; r++) {

The Table object has an internal row index (that is updated by the getRow method). Just call nextRow to make the index advance by one (you have to call nextRow before you write data, to get the first row):

        final TableRow tableRow = table2.nextRow();

And then create a "walker" for this row:

        final TableCellWalker cellWalker = tableRow.getWalker();

Now, we want nine columns for each row:

        for (int c = 0; c < 9; c++) {

Add the value to each cell

            cellWalker.setStringValue(String.valueOf((char) (c + 'A')) + String.valueOf(r + 1));

And then push one cell right.

            cellWalker.next();
        }
    }

And save the file.

    writer.saveAs(new File("generated_files", "b_accessing_example.ods"));

Note 1: There is a slight inconsistency between table.newtRow (before using the row) and cellWalker.next (after using the cell). Maybe I'll fix it before version 1.0...

Note 2: We will see how to merge cells in the Advanced part of this tutorial.

Value, Type, Style and Data Style

Four elements define the content of a cell:

  • the value
  • the type (string, float, boolean, ...)
  • the style (font, background color, border, ...)
  • the format, or data style (number of digits for a float, date format for a date, ...)

So far, we just created cells of type string, with neither style nor data style. Now, we will create cells with a more varied content.

Types

Now, we will create cells of different types. First, a table:

    Table table = document.addTable("types");

We add a header:

    TableRow tableRow = table.nextRow();
    TableCellWalker cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("Type");
    cellWalker.next();
    cellWalker.setStringValue("Example");

The first row contains a boolean:

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("Boolean");
    cellWalker.next();
    cellWalker.setBooleanValue(true);

The second row contains a currency:

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("Currency");
    cellWalker.next();
    cellWalker.setCurrencyValue(10.5, "USD");

The third row contains a date:

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("Date");
    cellWalker.next();
    cellWalker.setDateValue(new GregorianCalendar(2014, 9, 17, 9, 0, 0));

The fourth row contains a float:

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("Float");
    cellWalker.next();
    cellWalker.setFloatValue(3.14159);

The fifth row contains a percentage:

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("Percentage");
    cellWalker.next();
    cellWalker.setPercentageValue(0.545);

The sixth row contains...

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("String");
    cellWalker.next();
    cellWalker.setStringValue("A String");

The seventh row contains a time (that mean a duration):

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("Time");
    cellWalker.next();
    cellWalker.setTimeValue(3600);

The eighth row contains nothing

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("Void");
    cellWalker.next();
    cellWalker.setVoidValue();

Type Guess

FastODS can guess types, based on Java object types. It's useful when we try to auto import typed data, e.g. from a ResultSet. We can use this ability to reduce the boilerplate code.

Let's define two lists:

    final List<String> A = Arrays
            .asList("Type", "Boolean", "Currency", "Date", "Float", "Percentage", "String",
                    "Void");
    final List<Object> B = Arrays.<Object>asList("Type guess example", true,
            new CurrencyValue(10.5f, "USD"), new GregorianCalendar(2014, 9, 17, 9, 0, 0),
            3.14159, new PercentageValue(0.545f), "A String", null);

As you can see, some types are not guessable: is 0.545 a float or a percentage? For FastODS, it is a float. What Java type will map a currency value? We have to use specific types...

We skip a row for readability:

    table.nextRow();

Now, we can use setValue to take advantage of the type guess:

    for (int r = 0; r < A.size(); r++) {
        tableRow = table.nextRow();
        cellWalker = tableRow.getWalker();
        cellWalker.setStringValue(A.get(r));
        cellWalker.next();
        cellWalker.setCellValue(CellValue.fromObject(B.get(r)));
    }

Styles

Let's try to add some shapes and colors. First, we have to create a style for the header:

    final TableCellStyle grayStyle = TableCellStyle.builder("gray")
            .backgroundColor(SimpleColor.GRAY64).fontWeightBold().build();

The functions calls are chained in a fluent style.

We create a table and get the first cell:

    table = document.addTable("styles");
    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();

Now, we add a value and set the style

    cellWalker.setStringValue("A1");
    cellWalker.setStyle(grayStyle);

Common Styles and Automatic Styles

In LO, you'll see a new style named "gray" in the "Styles" window. That's because TableCellStyles are visible by default. We can make a style hidden by adding a hidden() call:

    final TableCellStyle hiddenGrayStyle = TableCellStyle.builder("hiddenGray")
            .backgroundColor(SimpleColor.GRAY64).fontWeightBold().hidden().build();
    cellWalker.next();
    cellWalker.setStringValue("A2");
    cellWalker.setStyle(grayStyle);

The "gray2" style is not present in the Style window of LO. This distinction between "visible" and "hidden" styles matches the distinction between common and automatic styles in the OpenDocument specification (3.15.3):

Note: Common and automatic styles behave differently in OpenDocument editing consumers. Common styles are presented to the user as a named set of formatting properties. The formatting properties of an automatic style are presented to a user as properties of the object to which the style is applied.

This distinction is sometimes hard to handle. FastODS tries to make things easy: each style is, by default, either visible or hidden (depending on the kind of the style), but you can always override the default choice. For instance, TableCellStyles, are visible by default, but TableRowStyles and data styles are hidden by default. In most of the cases, you can simply ignore the distinction.

Let's continue with a new style:

    final TableCellStyle rotateStyle = TableCellStyle.builder("rotate")
            .fontColor(SimpleColor.RED).textRotating(Angle.deg(37)).build();
    cellWalker.next();
    cellWalker.setStringValue("A3");
    cellWalker.setStyle(rotateStyle);

You can explore the TableCellStyle to create the style you need. A last example:

    final TableCellStyle borderStyle = TableCellStyle.builder("border").fontName(LOFonts.DEJAVU_SANS).fontSize(SimpleLength.pt(24))
            .borderAll(SimpleLength.mm(2), SimpleColor.BLUE, BorderAttribute.Style.OUTSET).build();
    cellWalker.next();
    cellWalker.setStringValue("A4");
    cellWalker.setStyle(borderStyle);

I think you get it now.

Rows and Columns Styles

What do we see? Yes, the last cell is ugly. But it is also partially hidden because the height of the row was not adapted. You have to adapt it yourself. Let's try with another row:

    final TableRowStyle tallRowStyle = TableRowStyle.builder("tall-row").rowHeight(SimpleLength.cm(3)).
            build();
    tableRow = table.nextRow();
    tableRow.setStyle(tallRowStyle);
    cellWalker = tableRow.getWalker();
    cellWalker.setStringValue("B1");
    cellWalker.setStyle(borderStyle);

You have to set the height of the row manually. There's nothing like an Optimal height/width in the OpenDocument specification, and FastODS won't provide those features. (Maybe one day I'll write a tool to compute the width/height of a text.) You can also add a column style:

    final TableColumnStyle wideColumn = TableColumnStyle.builder("wide-col")
            .columnWidth(SimpleLength.cm(9)).build();
    table.setColumnStyle(0, wideColumn);

Data Styles

Data styles are what we call "formats": is your date in plain text or in US format? how many digits have your number? are the negative numbers in red?

A cell may have a style, and this style may have a data style. The OpenDocument specification states that data style can't be attached directly to a cell, but must be embedded in a cell. That's not easy to handle for FastODS, but should not be too complicated for you. As usual, we create a table and get the first cell:

    table = document.addTable("data styles");

We'll place a float with the standard format, and a float with a custom format side by side

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();

Standard format:

    cellWalker.setFloatValue(123456.789);

And now create a custom data style:

    final DataStyle floatDataStyle = new FloatStyleBuilder("float-datastyle", Locale.US)
            .decimalPlaces(8).groupThousands(true).build();
    cellWalker.next();
    cellWalker.setFloatValue(123456.789);
    cellWalker.setDataStyle(floatDataStyle);

We can do the same with dates:

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();

A date with the standard format:

    final Calendar cal = new GregorianCalendar(2018, 1, 1, 0, 0, 0);
    cellWalker.setDateValue(cal);

And a custom format:

    final DataStyle dateDataStyle = new DateStyleBuilder("date-datastyle", Locale.US)
            .dateFormat(new DateStyleFormat(DateStyleFormat.DAY, DateStyleFormat.DOT,
                    DateStyleFormat.MONTH, DateStyleFormat.DOT, DateStyleFormat.YEAR)).visible()
            .build();
    cellWalker.next();
    cellWalker.setDateValue(cal);
    cellWalker.setDataStyle(dateDataStyle);

A last try with a time (duration):

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setTimeValue(10000000);

And:

    final DataStyle timeDataStyle = new TimeStyleBuilder("time-datastyle", Locale.US)
            .timeFormat(new DateStyleFormat(DateStyleFormat.text("Hour: "),
                    DateStyleFormat.LONG_HOURS)).visible().build();
    cellWalker.next();
    cellWalker.setTimeValue(10000000);
    cellWalker.setDataStyle(timeDataStyle);

Obvioulsy, you can combine a style and a data style:

    tableRow = table.nextRow();
    cellWalker = tableRow.getWalker();
    cellWalker.setTimeValue(10000000);
    cellWalker.setStyle(rotateStyle);
    cellWalker.setDataStyle(timeDataStyle);

Changing the Default Data Styles

Setting the data style for every cell may become cumbersome. Happily, you can decide of the default data styles at the creation of the document.

First, create a DataStyles object (note the "s") with a builder:

    final DataStylesBuilder dsb = DataStylesBuilder.create(Locale.US);
    dsb.floatStyleBuilder().decimalPlaces(0);
    dsb.dateStyleBuilder().dateFormat(
            new DateStyleFormat(DateStyleFormat.LONG_DAY, DateStyleFormat.SLASH,
                    DateStyleFormat.LONG_MONTH, DateStyleFormat.SLASH,
                    DateStyleFormat.LONG_YEAR));

You can use the other data style builders if you want, and then build all the data style in one shot:

    final DataStyles ds = dsb.build();

Now, create the factory

    final OdsFactory odsFactory = OdsFactory.create(Logger.getLogger("cells2"), Locale.US);

and pass the created "data styles" to the factory:

    odsFactory.dataStyles(ds);

We can continue as usual:

    final AnonymousOdsFileWriter writer = odsFactory.dataStyles(ds).createWriter();
    final OdsDocument document = writer.document();

And create the same cells as above:

    final Table table = document.addTable("data styles");
    final TableRow tableRow = table.nextRow();
    final TableCellWalker cellWalker = tableRow.getWalker();
    cellWalker.setFloatValue(123456.789);
    final Calendar cal = new GregorianCalendar(2018, 1, 1, 0, 0, 0);
    cellWalker.next();
    cellWalker.setDateValue(cal);

And save the file.

    writer.saveAs(new File("generated_files", "c_value_type2.ods"));

Page Format

We know how to access to cells and how to format those cells. We still have to format the pages. Let's start with a new table:

    Table table = document.addTable("format-page");
    TableRow row = table.getRow(0);
    TableCellWalker walker = row.getWalker();
    walker.setStringValue("Text");

We will add a footer and a header.

Header

First, we build the three parts of a simple header:

    final Text leftHeaderContent = Text.content("left header");
    final Text centerHeaderContent = Text.builder().par().span("center header, page ")
            .span(Text.TEXT_PAGE_NUMBER).build();
    final Text rightHeaderContent = Text.content("right header");

Then we build the header itself:

    final Header header = PageSection.regionBuilder().region(PageSectionContent.Region.LEFT)
            .text(leftHeaderContent).region(PageSectionContent.Region.CENTER)
            .text(centerHeaderContent).region(PageSectionContent.Region.RIGHT)
            .text(rightHeaderContent).allMargins(SimpleLength.cm(2))
            .minHeight(SimpleLength.cm(5)).buildHeader();

Footer

For the footer, let's use the one part format:

    final Footer footer = PageSection.simpleBuilder().text(Text.content("footer"))
            .buildFooter();

We now insert the header and the footer in a page style:

    PageStyle pageStyle = PageStyle.builder("page-style").header(header).footer(footer)
            .build();

And add the page style into the table style:

    TableStyle tableStyle = TableStyle.builder("table-style").pageStyle(pageStyle)
            .build();

And set this table style:

    table.setStyle(tableStyle);

Styles

Create another table:

    table = document.addTable("format-page2");
    row = table.getRow(0);
    walker = row.getWalker();
    walker.setStringValue("Text");

We can create a very simple header:

    final Header minimalHeader = PageSection.simpleBuilder().content("minimal header").buildHeader();

Or a complex footer:

    final TextBuilder textBuilder = Text.builder();

par() means a new paragraph, span a new portion of text:

    textBuilder.par().span("complex");

Both can be used in one call:

    textBuilder.parContent("footer");

Text can be styled:

    textBuilder.par().styledSpan("date is:",
            TextProperties.builder().fontWeightBold().buildHiddenStyle("footer1"));

In one call:

    textBuilder.parStyledContent(Text.TEXT_DATE,
            TextProperties.builder().fontSize(SimpleLength.pt(25)).fontWeightBold().buildHiddenStyle("footer2")
    );

And build the text:

    final Text footerContent = textBuilder.build();
    final Footer complexFooter = PageSection.simpleBuilder().text(footerContent).buildFooter();

As above:

    pageStyle = PageStyle.builder("page-style2").header(minimalHeader).footer(complexFooter).build();
    tableStyle = TableStyle.builder("table-style2").pageStyle(pageStyle)
            .build();
    table.setStyle(tableStyle);

LO features

If you know what you are doing, you can play with LO settings, for instance:

    table.setSettings("View1", "ZoomValue", "150");

For more doc, see:

Advanced Features

Let's start with a new page:

    final Table table = document.addTable("advanced");
    final TableRow row = table.getRow(0);
    final TableCellWalker walker = row.getWalker();

Merging Cells

Links

Some Tools

A Named Writer

And save the file.

    writer.saveAs(new File("generated_files", "a_hello_world_example.ods"));
Clone this wiki locally