Skip to content

Commit

Permalink
ready for version 1.0 of ods-reader
Browse files Browse the repository at this point in the history
  • Loading branch information
jze committed Nov 30, 2024
1 parent 207d17a commit 88f7348
Show file tree
Hide file tree
Showing 13 changed files with 174 additions and 67 deletions.
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# ods-reader
You want to import very large OpenDocument tables (ODS file) and worry about memory usage? In that case the opendocument-reader might be interesting for you. Instead of loading the entire document, it is a pull-based reader.
You want to import very large OpenDocument tables (ODS file) and worry about memory usage? In that case the ods-reader might be interesting for you. Instead of loading the entire document, it is a pull-based reader. It therefore has a very low memory consumption, even with huge documents.

## Usage
Here is an example how to use the opendocument-reader:
Here is an example how to use the ods-reader:
```java
Document doc = new Document(new File("myTable.ods"));
Table table = doc.nextTable();
Expand All @@ -15,3 +15,27 @@ Here is an example how to use the opendocument-reader:
}

```

## Cell types

ODS distinguishes between the value that is displayed for humans and the machine-readable value. The value that is displayed for humans depends on the language selected for the document. The method `getContent()` is used to get the language depending human-readable value.

The machine-reable value can be read using specialized methods that return suitable Java objects. Here is an example:

```java
if ("date".equals(cell.getValueType())) {
if( cell.isDateTime() ) {
LocalDateTime dateTime = cell.asDateTime();
} else {
LocalDate date = cell.asDate();
}
} else if ("boolean".equals(cell.getValueType())) {
boolean b = cell.asBoolean();
} else if ("time".equals(cell.getValueType())) {
LocalTime time = cell.asTime();
} else if (cell.getValue() != null) {
result = cell.getValue();
} else {
result = cell.getContent();
}
```
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<groupId>de.zedlitz</groupId>
<name>OpenDocument reader</name>
<artifactId>ods-reader</artifactId>
<version>1.1</version>
<version>1.0</version>
<description>streaming reader for OpenDocument files</description>
<url>https://github.com/jze/opendocument-reader</url>
<properties>
Expand Down
69 changes: 64 additions & 5 deletions src/main/java/de/zedlitz/opendocument/Cell.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,34 @@ private static String getColumnName(int columnIndex) {
return columnName.toString();
}

/**
* Get the raw value of the <code>currency</code> attribute. It is only present for cells with the type "currency".
* @return the raw currency value or <code>null</code> if not present.
*/
public String getCurrency() {
return currency;
}

/**
* Get the raw value of the <code>time-value</code> attribute. It is only present for cells with the type "time".
* @return the raw time value or <code>null</code> if not present.
*/
public String getTimeValue() {
return timeValue;
}

/**
* Get the raw value of the <code>boolean-value</code> attribute. It is only present for cells with the type "boolean".
* @return the raw boolean value or <code>null</code> if not present.
*/
public String getBooleanValue() {
return booleanValue;
}

/**
* Get the raw value of the <code>date-value</code> attribute. It is only present for cells with the type "date".
* @return the raw date value or <code>null</code> if not present.
*/
public String getDateValue() {
return dateValue;
}
Expand All @@ -123,7 +139,8 @@ private void skipNote(final XMLStreamReader parser)
}

/**
* @return Returns the valueType.
* Get the raw value of the <code>value-type</code> attribute. It should be present for every cell.
* @return Returns the valueType or <code>null</code> if not present.
*/
public String getValueType() {
return this.valueType;
Expand All @@ -133,7 +150,7 @@ public String getValueType() {
* Returns the content of the cell formatted for the locale of the file. For example in a German ods file the
* boolean value <code>true</code> will be returned as <code>WAHR</code>. In a French ods file it will be
* <code>VRAI</code>. And in an English ods file it will be <code>TRUE</code>.
* <p/>
* <p>
* If you are looking for a language independent value you can use the getValue method.
*/
public String getContent() {
Expand All @@ -152,14 +169,29 @@ public String toString() {
return String.format("[%s \"%s\"]", getValueType(), getContent());
}

public Boolean asBoolean() {
/**
* Return the boolean value of the cell. This works only for cells with the type "boolean".
* Check for <code>"boolean".equals(cell.getValueType())</code> before invoking this method.
*
* @return a {@link LocalDate} object with the value of the cell.
* @throws OdsReaderException is the cell is not a boolean cell
*/
public boolean asBoolean() {
if ("boolean".equals(valueType) && StringUtils.isNotEmpty(booleanValue)) {
return Boolean.valueOf(booleanValue);
}

throw new OdsReaderException("Wrong cell type " + valueType + " for boolean value");
}

/**
* Return a {@link LocalDate} object with the value of the cell. This works only for cells with the type "date" and
* do not have a time component.
* Check for <code>"date".equals(cell.getValueType())</code> before invoking this method.
*
* @return a {@link LocalDate} object with the value of the cell.
* @throws OdsReaderException is the cell is not a date cell
*/
public LocalDate asDate() {
if ("date".equals(valueType) && StringUtils.isNotEmpty(dateValue)) {
return LocalDate.parse(dateValue);
Expand All @@ -168,20 +200,42 @@ public LocalDate asDate() {
throw new OdsReaderException("Wrong cell type " + valueType + " for date value");
}

/**
* Return a {@link LocalDateTime} object with the value of the cell. This works only for cells with the type "date".
* Check for <code>"date".equals(cell.getValueType())</code> before invoking this method.
* If the cell contains only a date and no time the time <code>00:00:00</code> will be used as the time component.
*
* @return a {@link LocalDateTime} object with the value of the cell.
* @throws OdsReaderException is the cell is not a date cell
*/
public LocalDateTime asDateTime() {
if ("date".equals(valueType) && StringUtils.isNotEmpty(dateValue)) {
if (dateValue.contains("T")) {
// date and time
return LocalDateTime.parse(dateValue, DateTimeFormatter.ISO_DATE_TIME);
} else {
// only date
return LocalDateTime.parse(dateValue+"T00:00:00", DateTimeFormatter.ISO_DATE_TIME);
return LocalDateTime.parse(dateValue + "T00:00:00", DateTimeFormatter.ISO_DATE_TIME);
}
}

throw new OdsReaderException("Wrong cell type " + valueType + " for date value");
}

/**
* Does the cell contain a value that consists of a date and a time?
*/
public boolean isDateTime() {
return "date".equals(valueType) && StringUtils.contains(dateValue, "T");
}

/**
* Return a {@link LocalTime} object with the value of the cell. This works only for cells with the type "time".
* Check for <code>"time".equals(cell.getValueType())</code> before invoking this method.
*
* @return a {@link LocalTime} object with the value of the cell.
* @throws OdsReaderException is the cell is not a time cell
*/
public LocalTime asTime() {
if ("time".equals(valueType) && StringUtils.isNotEmpty(timeValue)) {
Duration duration = Duration.parse(timeValue);
Expand All @@ -200,7 +254,12 @@ public String getAddress() {
}

/**
* Returns the language independent value of a cell.
* Returns the language independent value of a cell. This is only present for cell with the type float, currency,
* and percentage. It is stored in the <code>value</code> attribute of the cell element.
* <p>
* If getValue() is <code>null</code> you can use getContent() to get the text value of the cell.
*
* @return the language independent value of a cell or <code>null</code> if not value is present.
*/
public String getValue() {
return value;
Expand Down
13 changes: 0 additions & 13 deletions src/main/java/de/zedlitz/opendocument/CellType.java

This file was deleted.

6 changes: 5 additions & 1 deletion src/main/java/de/zedlitz/opendocument/Document.java
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public void eachTable(final Consumer<Table> c) {
}
}

public Optional<Sheet> getSheet(int i) {
public Optional<Table> getTable(int i) {
int count = 0;
Table nextTable = this.nextTable();
while (nextTable != null) {
Expand All @@ -120,4 +120,8 @@ public Optional<Sheet> getSheet(int i) {
}
return Optional.empty();
}

public Optional<Sheet> getSheet(int i) {
return getTable(i).map(it -> it);
}
}
15 changes: 0 additions & 15 deletions src/main/java/de/zedlitz/opendocument/ReadableWorkbook.java

This file was deleted.

20 changes: 0 additions & 20 deletions src/main/java/de/zedlitz/opendocument/ReadingOptions.java

This file was deleted.

4 changes: 2 additions & 2 deletions src/main/java/de/zedlitz/opendocument/Table.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
public class Table implements Sheet, Iterable<Row> {
static final QName ELEMENT_TABLE = new QName(Document.NS_TABLE, "table");

private static final String ATTRIBUTE_NAME = "name";
private final XMLStreamReader xpp;
private String name;
Expand All @@ -35,8 +36,7 @@ private boolean isTableEndElement(int eventType) {

private boolean isRowStartElement(int eventType) {
return eventType == XMLStreamConstants.START_ELEMENT
&& Row.ELEMENT_ROW.equals(xpp.getName())
&& Document.NS_TABLE.equals(xpp.getNamespaceURI());
&& Row.ELEMENT_ROW.equals(xpp.getName());
}

public final Row nextRow() {
Expand Down
42 changes: 42 additions & 0 deletions src/test/java/de/zedlitz/opendocument/CellTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,24 @@ public class CellTest extends AbstractBaseTest {
private static final String CONTENT_EMPTY_CELL =
"<table:table-cell xmlns:table='urn:oasis:names:tc:opendocument:xmlns:table:1.0'></table:table-cell>";

/**
* A date cell without a date-value attribute. I don't think this situation can happen in real live.
*/
private static final String CONTENT_MISSING_DATE_VALUE =
"<table:table-cell office:value-type=\"date\" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:table='urn:oasis:names:tc:opendocument:xmlns:table:1.0'></table:table-cell>";

/**
* A time cell without a time-value attribute. I don't think this situation can happen in real live.
*/
private static final String CONTENT_MISSING_TIME_VALUE =
"<table:table-cell office:value-type=\"time\" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:table='urn:oasis:names:tc:opendocument:xmlns:table:1.0'></table:table-cell>";

/**
* A boolean cell without a time-value attribute. I don't think this situation can happen in real live.
*/
private static final String CONTENT_MISSING_BOOLEAN_VALUE =
"<table:table-cell office:value-type=\"boolean\" xmlns:office=\"urn:oasis:names:tc:opendocument:xmlns:office:1.0\" xmlns:table='urn:oasis:names:tc:opendocument:xmlns:table:1.0'></table:table-cell>";

private static final String BROKEN_XML_CONTENT =
"<table:table-cell xmlns:table='urn:oasis:names:tc:opendocument:xmlns:table:1.0'><WRONG></table:table-cell>";

Expand Down Expand Up @@ -131,6 +149,16 @@ public void testGetDateValue() throws Exception {
assertEquals("1999-12-31T07:35:02", cells.get(22).getDateValue());
assertEquals("1899-12-30T13:37:46", cells.get(28).getDateValue());
}

@Test
public void testIsDateTime() throws Exception {
List<Cell> cells = getCellsFromDemoFile("/formats_german.ods");
assertFalse(cells.get(0).isDateTime());
assertFalse(cells.get(11).isDateTime());
assertTrue(cells.get(22).isDateTime());
assertTrue(cells.get(28).isDateTime());
}

@Test
public void testAsBoolean() throws Exception {
List<Cell> cells = getCellsFromDemoFile("/formats_french.ods");
Expand Down Expand Up @@ -176,4 +204,18 @@ public void testGetValue() throws Exception {
assertEquals("0.1295", cells.get(7).getValue());
assertEquals("120.5", cells.get(8).getValue());
}

@Test
public void invalidCells() throws XMLStreamException {
Cell cell = new Cell(advanceToStartTag(createParser(CONTENT_MISSING_DATE_VALUE)), new DummyRow(), 0);
assertThrows(OdsReaderException.class, cell::asDate);
assertThrows(OdsReaderException.class, cell::asDateTime);

cell = new Cell(advanceToStartTag(createParser(CONTENT_MISSING_TIME_VALUE)), new DummyRow(), 0);
assertThrows(OdsReaderException.class, cell::asTime);

cell = new Cell(advanceToStartTag(createParser(CONTENT_MISSING_BOOLEAN_VALUE)), new DummyRow(), 0);
assertThrows(OdsReaderException.class, cell::asBoolean);

}
}
14 changes: 8 additions & 6 deletions src/test/java/de/zedlitz/opendocument/DocumentTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ public void testEmptyTable() throws Exception {

@Test
public void testEmptyTableCheckRows() throws Exception {
final Document doc = new Document(this
.createParser(CONTENT_ONE_EMPTY_TABLE));
final Document doc = new Document(this.createParser(CONTENT_ONE_EMPTY_TABLE));
final Table tab = doc.nextTable();
assertNotNull(tab, "one table");
assertNull(tab.nextRow(), "no row");
Expand All @@ -63,8 +62,7 @@ public void testEmptyTableCheckRows() throws Exception {

@Test
public void testSkipRows() throws Exception {
final Document doc = new Document(this
.createParser(CONTENT_ONE_TWO_ROWS));
final Document doc = new Document(this.createParser(CONTENT_ONE_TWO_ROWS));

final Table tab1 = doc.nextTable();
assertNotNull(tab1, "1st table");
Expand All @@ -77,8 +75,7 @@ public void testSkipRows() throws Exception {

@Test
public void testReadRows() throws Exception {
final Document doc = new Document(this
.createParser(CONTENT_ONE_TWO_ROWS));
final Document doc = new Document(this .createParser(CONTENT_ONE_TWO_ROWS));

final Table tab1 = doc.nextTable();
assertNotNull(tab1, "1st table");
Expand Down Expand Up @@ -213,4 +210,9 @@ public void getSheet_notExisting() throws XMLStreamException, IOException {
assertFalse(sheet.isPresent());
}

@Test
public void constructor_emptyZipFile() throws XMLStreamException, IOException {
Document doc = new Document(getClass().getResourceAsStream("/empty.zip"));
}

}
Loading

0 comments on commit 88f7348

Please sign in to comment.