View Javadoc
1   /*
2    * Copyright 2012 Olivier Godineau
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at http://www.apache.org/licenses/LICENSE-2.0
7    * 
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11   * License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  package olg.csv.base.ods;
15  
16  import java.io.File;
17  import java.io.IOException;
18  import java.io.OutputStream;
19  import java.util.ArrayList;
20  import java.util.List;
21  
22  import olg.csv.base.AbstractSheetWriter;
23  import olg.csv.base.Cell;
24  import olg.csv.base.Row;
25  import olg.csv.base.WriterException;
26  import olg.csv.bean.parser.AbstractParser;
27  import olg.csv.bean.parser.ParseException;
28  
29  import org.odftoolkit.odfdom.doc.OdfSpreadsheetDocument;
30  import org.odftoolkit.odfdom.doc.table.OdfTable;
31  import org.odftoolkit.odfdom.doc.table.OdfTableCell;
32  import org.odftoolkit.odfdom.doc.table.OdfTableRow;
33  import org.odftoolkit.odfdom.incubator.doc.text.OdfTextParagraph;
34  import org.slf4j.Logger;
35  import org.slf4j.LoggerFactory;
36  
37  /**
38   * ODS Writer based on ODFDOM API.
39   * 
40   * @author Olivier Godineau
41   * 
42   */
43  public class ODSWriter extends AbstractSheetWriter {
44  	/**
45  	 * Class logger.
46  	 */
47  	private static final Logger LOGGER = LoggerFactory.getLogger(ODSWriter.class);
48  	/**
49  	 * the ODS Document.
50  	 */
51  	private OdfSpreadsheetDocument outputDocument = null;
52  	/**
53  	 * the table on which write rows.
54  	 */
55  	private OdfTable table = null;
56  	/**
57  	 * the out file.
58  	 */
59  	private File outFile = null;
60  	/**
61  	 * the out stream.
62  	 */
63  	private OutputStream out = null;
64  	/**
65  	 * The cell parser.
66  	 */
67  	private final AbstractParser<List<String>> cellParser;
68  	/**
69  	 * the size of the rows to add in the document.
70  	 */
71  	private int size;
72  	/**
73  	 * the current row index to add.
74  	 */
75  	private int currentLine = 1;
76  
77  	/**
78  	 * Indicates if document cells must be emptied. Useful when cells of a row
79  	 * to add do not follow.
80  	 */
81  	private final boolean emptyingCell;
82  
83  	/**
84  	 * Indicates if document rows must be emptied. useful when the rows to add
85  	 * do not follow.
86  	 */
87  	private final boolean emtpyingRow;
88  
89  	/**
90  	 * Indicates if the document must be overriding. true if the table was not
91  	 * created but opened in an existing document.
92  	 */
93  	private boolean overridding = false;
94  
95  	/**
96  	 * 
97  	 * @param outFile
98  	 *            if outFile exists and contains sheet with specified sheetName,
99  	 *            rewrites rows and cells in it. if sheetname sheet doesn't
100 	 *            exists, creates it and writes on it. If outFile not exists,
101 	 *            creates a new ODS File.
102 	 * @param settings
103 	 *            the ODS Settings
104 	 * @throws IOException
105 	 *             if an errors occurs on file opening.
106 	 */
107 	public ODSWriter(File outFile, ODSSettings settings) throws IOException {
108 		super(settings);
109 		if (outFile == null) {
110 			throw new IllegalArgumentException("ODSWriter constructor File argument must not be null");
111 		}
112 
113 		if (settings.getStringParser() == null) {
114 			throw new IllegalArgumentException(
115 					"ODSWriter constructor ODSSettings argument must have not null StringParser attribut");
116 		}
117 		this.outFile = outFile;
118 		this.cellParser = settings.getStringParser();
119 		this.emptyingCell = settings.isEmptyingCell();
120 		this.emtpyingRow = settings.isEmptyingRow();
121 
122 		if (this.outFile.isFile()) {
123 			loadDocument(sheetName);
124 		} else {
125 			createNewDocument(sheetName);
126 		}
127 
128 	}
129 
130 	/**
131 	 * Constructs ODSWriter with outputStream and Settings. Try to create a new
132 	 * ODS Document which will be saved
133 	 * 
134 	 * @param out
135 	 *            writes a new ODSDocument on it.
136 	 * @param settings
137 	 *            ODS Settings
138 	 * @throws IOException
139 	 *             if unable to create a new ODS Document
140 	 */
141 	public ODSWriter(OutputStream out, ODSSettings settings) throws IOException {
142 		super(settings);
143 		if (out == null) {
144 			throw new IllegalArgumentException("ODSWriter constructor out argument must not be null");
145 		}
146 		if (settings.getStringParser() == null) {
147 			throw new IllegalArgumentException(
148 					"ODSWriter constructor ODSSettings argument must have not null StringParser attribut");
149 		}
150 
151 		this.out = out;
152 		this.cellParser = settings.getStringParser();
153 		this.emptyingCell = settings.isEmptyingCell();
154 		this.emtpyingRow = settings.isEmptyingRow();
155 
156 		try {
157 			createNewDocument(sheetName);
158 		} catch (Exception e) {
159 			LOGGER.debug("Unable to create ODS Document", e);
160 			throw new IOException("Unable to create ODS Document", e);
161 		}
162 	}
163 
164 	/**
165 	 * Close this stream and save the ODS Document to the file/stream and catch
166 	 * and log Exception if necessary. if the parent stream is provided and
167 	 * passed to its constructor by user, user must close it (close what you
168 	 * open!).
169 	 */
170 	public void close() {
171 		if (outputDocument != null) {
172 			try {
173 				if (outFile != null) {
174 					outputDocument.save(outFile);
175 				}
176 				if (out != null) {
177 					outputDocument.save(out);
178 				}
179 			} catch (Exception e) {
180 				LOGGER.info("Error on closing ODSWriter : Unable to save ODS file " + outFile, e);
181 			} finally {
182 				outputDocument.close();
183 			}
184 		}
185 	}
186 
187 	/**
188 	 * {@inheritDoc}
189 	 */
190 	public void addRow(Row row) {
191 		if (row == null) {
192 			throw new IllegalArgumentException("ODSWriter#addRow argument must not be null");
193 		}
194 		if (row.getNum() < currentLine) {
195 			throw new WriterException(String.format("Row num[%s] but %s was expected or superior", row.getNum(),
196 					currentLine));
197 		}
198 		if (size == 0) {
199 			size = row.getSize(); // when this is first line
200 		}
201 		if (size != row.getSize()) {
202 			throw new WriterException(String.format("Row size[%s] differs from the expected size[%s] ", row.getSize(),
203 					size));
204 		}
205 
206 		emptyRows(row.getNum());
207 
208 		OdfTableRow odfRow = table.getRowByIndex(row.getNum() + beginAtRow - 2);
209 
210 		int column = 0;
211 		for (Cell cell : row) {
212 			if (cell.getNum() < column) {
213 				throw new WriterException("ODSWriter#addRow cells must be ordered ");
214 			}
215 
216 			emptyCells(odfRow, column, cell.getNum(), false);
217 
218 			insertCell(odfRow, cell);
219 			column = cell.getNum() + 1;
220 		}
221 
222 		emptyCells(odfRow, column, size, false);
223 
224 		currentLine = row.getNum() + 1;
225 
226 	}
227 
228 	/**
229 	 * {@inheritDoc}
230 	 */
231 	public void addLine(String[] values) {
232 		if (values == null || values.length == 0) {
233 			throw new IllegalArgumentException("ODSWriter#addLine argument must not be null");
234 		}
235 		if (size == 0) {
236 			size = values.length; // when this is first line
237 		}
238 		if (size != values.length) {
239 			throw new WriterException(String.format("Values size[%s] differs from the expected size[%s]",
240 					values.length, size));
241 		}
242 
243 		int column = 0;
244 		OdfTableRow odfRow = table.getRowByIndex(currentLine + beginAtRow - 2);
245 
246 		for (String value : values) {
247 			insertCellAtNum(odfRow, column++, value);
248 
249 		}
250 		currentLine++;
251 
252 	}
253 
254 	/**
255 	 * creates a new empty ODF table in the ODS document.
256 	 * 
257 	 * @param sheetName
258 	 *            the table name.
259 	 * @return the table
260 	 */
261 	private OdfTable createSheet(String sheetName) {
262 		OdfTable table = OdfTable.newTable(outputDocument);
263 		table.removeRowsByIndex(0, 5);
264 		table.removeColumnsByIndex(0, 4);
265 		table.setTableName(sheetName);
266 		return table;
267 	}
268 
269 	/**
270 	 * Create a new ODS Document with a table.
271 	 * 
272 	 * @param sheetName
273 	 *            the name of the table
274 	 * @throws IOException
275 	 *             if unable to create new ODS Document
276 	 */
277 	// TODO voir ecriture sur une feuille désignée par son index
278 	private void createNewDocument(String sheetName) throws IOException {
279 		try {
280 			this.outputDocument = OdfSpreadsheetDocument.newSpreadsheetDocument();
281 		} catch (Exception e) {
282 			LOGGER.debug("Unable to create new ODS Document", e);
283 			throw new IOException("Unable to create new ODS Document", e);
284 		}
285 		for (OdfTable feuille : this.outputDocument.getTableList()) { // TODO
286 			// à
287 			// revoir
288 			if (!sheetName.equals(feuille.getTableName())) {
289 				feuille.remove();
290 			}
291 		}
292 		this.table = createSheet(sheetName);
293 	}
294 
295 	/**
296 	 * Opens the outFile as an ODS Document and opens the table or create the
297 	 * table if not existe in the document.
298 	 * 
299 	 * @param sheetName
300 	 *            the name of the table to open.
301 	 * @throws IOException
302 	 *             if unable to load the document.
303 	 */
304 	private void loadDocument(String sheetName) throws IOException {
305 		try {
306 			this.outputDocument = OdfSpreadsheetDocument.loadDocument(this.outFile);
307 		} catch (Exception e) {
308 			LOGGER.debug("Unable to load ODS Document", e);
309 			throw new IOException("Unable to load ODS Document", e);
310 		}
311 
312 		for (OdfTable feuille : this.outputDocument.getTableList()) { // TODO
313 			// à
314 			// revoir
315 			if (sheetName.equals(feuille.getTableName())) {
316 				this.table = feuille;
317 				this.overridding = true;
318 				break;
319 			}
320 		}
321 		if (this.table == null) {
322 			this.table = createSheet(sheetName);
323 		}
324 
325 	}
326 
327 	/**
328 	 * copy a cell in a row.
329 	 * 
330 	 * @param odfRow
331 	 *            the row
332 	 * @param cell
333 	 *            the cell to insert
334 	 */
335 	private void insertCell(OdfTableRow odfRow, Cell cell) {
336 		OdfTableCell odfCell = odfRow.getCellByIndex(cell.getNum() + beginAtColumn);
337 
338 		if (cell.isEmpty()) {
339 			odfCell.setStringValue(null);
340 		} else {
341 			insertCell(odfCell, cell.getValue());
342 		}
343 	}
344 
345 	/**
346 	 * Insert a cell in a row.
347 	 * 
348 	 * @param odfRow
349 	 *            the row
350 	 * @param num
351 	 *            the column num after beginAtColumn
352 	 * @param value
353 	 *            the cell value
354 	 */
355 	private void insertCellAtNum(OdfTableRow odfRow, int num, String value) {
356 		OdfTableCell odfCell = odfRow.getCellByIndex(num + beginAtColumn);
357 
358 		if (value == null) {
359 			odfCell.setStringValue(null);
360 		} else {
361 			insertCell(odfCell, value);
362 		}
363 	}
364 
365 	/**
366 	 * set a value on a cell. Because line breaks are not correctly interpreted,
367 	 * insert a paragraph for each line identified by the cellParser.
368 	 * 
369 	 * @param odfCell
370 	 *            the cell
371 	 * @param value
372 	 *            the value.
373 	 */
374 	private void insertCell(OdfTableCell odfCell, String value) {
375 		odfCell.removeContent();
376 		for (String line : cellParser.parse(value)) {
377 			OdfTextParagraph paragraph;
378 			try {
379 				paragraph = new OdfTextParagraph(this.outputDocument.getContentDom()); // NOPMD
380 																						// by
381 																						// olivier
382 																						// on
383 																						// 01/02/12
384 																						// 00:04
385 				paragraph.setTextContent(line);
386 				odfCell.getOdfElement().appendChild(paragraph);
387 			} catch (Exception e) {
388 				LOGGER.debug("Error on copy [" + value + "]", e);
389 				throw new WriterException("Error on copy [" + value + "]", e);
390 
391 			}
392 
393 		}
394 	}
395 
396 	/**
397 	 * empties rows. Only if overriding and emptyingRow are true.
398 	 * 
399 	 * @param num
400 	 *            the number of rows to empty. begins at currentLine after
401 	 *            beginAtRow value.
402 	 */
403 	private void emptyRows(int num) {
404 		if (overridding && emtpyingRow) {
405 			for (int i = currentLine; i < num; i++) {
406 				emptyCells(table.getRowByIndex(i + beginAtRow - 2), 0, size, true);
407 			}
408 		}
409 	}
410 
411 	/**
412 	 * empties cells of a row if overriding and emptyingRow are true.
413 	 * 
414 	 * @param row
415 	 *            the row
416 	 * @param begin
417 	 *            first column (include) after beginAtColumn value.
418 	 * @param end
419 	 *            last column (exclude) after beginAtColumn value.
420 	 * @param force
421 	 *            if true bypass overriding and emptyingCell condition.
422 	 */
423 	private void emptyCells(OdfTableRow row, int begin, int end, boolean force) {
424 		if (force || (overridding && emptyingCell)) {
425 			for (int j = begin; j < end; j++) {
426 				row.getCellByIndex(j + beginAtColumn).removeContent();
427 			}
428 		}
429 	}
430 
431 	/**
432 	 * 
433 	 * This parser returns lines extracted from a string by interpreting \r, \n
434 	 * or \r\n as line breaks found in it. Used to convert a string into an ODS
435 	 * Cell content(where line breaks are not correctly interpreted) : For each
436 	 * line returns by this parser, ODSWriter will create a paragraph into the
437 	 * cell
438 	 * 
439 	 * @author Olivier Godineau
440 	 * 
441 	 */
442 	public static class ODSParser extends AbstractParser<List<String>> {
443 
444 		@Override
445 		public List<String> parse(String str) throws ParseException {
446 
447 			ArrayList<String> lines = new ArrayList<String>();
448 			int beginAt = 0;
449 
450 			boolean skipLF = false;
451 			boolean reindex = false;
452 			char[] chars = str.toCharArray();
453 			for (int i = 0; i < chars.length; i++) {
454 				if (reindex) {
455 					beginAt = i;
456 					reindex = false;
457 				}
458 
459 				if (chars[i] == '\r') {
460 					lines.add(String.copyValueOf(chars, beginAt, i - beginAt));
461 					if (chars[i] == '\r') {
462 						skipLF = true;
463 					}
464 					reindex = true;
465 				}
466 
467 				if (chars[i] == '\n') {
468 					if (skipLF) {
469 						skipLF = false;
470 					} else {
471 						lines.add(String.copyValueOf(chars, beginAt, i - beginAt));
472 					}
473 					reindex = true;
474 				}
475 			}
476 
477 			if (reindex) {
478 				lines.add(String.copyValueOf(chars, beginAt, 0));
479 			} else {
480 				lines.add(String.copyValueOf(chars, beginAt, chars.length - beginAt));
481 			}
482 
483 			return lines;
484 
485 		}
486 	}
487 
488 }