From 05ddf6a51e86296a05c6f6d42d9c66b20fcafffc Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Fri, 11 Jun 2010 13:29:44 +0000 Subject: [PATCH] Fix for bug #48245 - tweak HWPF table cell detection to work across more files git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@953694 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../apache/poi/hwpf/usermodel/TableRow.java | 12 +- .../poi/hwpf/usermodel/TestProblems.java | 514 ++++++++++++------ test-data/document/simple-table2.doc | Bin 0 -> 27136 bytes 4 files changed, 361 insertions(+), 166 deletions(-) create mode 100644 test-data/document/simple-table2.doc diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index e0cdba7af..92474f0aa 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 48245 - tweak HWPF table cell detection to work across more files 48996 - initial support for External Name References in HSSF formula evaluation 46664 - fix up Tab IDs when adding new sheets, so that print areas don't end up invalid 45269 - improve replaceText on HWPF ranges diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java index 857a92cfb..a2a8d4676 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/TableRow.java @@ -17,6 +17,7 @@ package org.apache.poi.hwpf.usermodel; +import org.apache.poi.hwpf.model.PropertyNode; import org.apache.poi.hwpf.sprm.TableSprmUncompressor; public final class TableRow @@ -57,10 +58,19 @@ public final class TableRow p = getParagraph(end); s = p.text(); } - _cells[cellIndex] = new TableCell(start, end+1, this, levelNum, + + // Create it for the correct paragraph range + _cells[cellIndex] = new TableCell(start, end, this, levelNum, _tprops.getRgtc()[cellIndex], _tprops.getRgdxaCenter()[cellIndex], _tprops.getRgdxaCenter()[cellIndex+1]-_tprops.getRgdxaCenter()[cellIndex]); + // Now we've decided where everything is, tweak the + // record of the paragraph end so that the + // paragraph level counts work + // This is a bit hacky, we really need a better fix... + _cells[cellIndex]._parEnd++; + + // Next! end++; start = end; } diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java index 94b66f894..7e3857cae 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestProblems.java @@ -30,172 +30,356 @@ import org.apache.poi.hwpf.model.StyleSheet; */ public final class TestProblems extends HWPFTestCase { - /** - * ListEntry passed no ListTable - */ - public void testListEntryNoListTable() { - HWPFDocument doc = HWPFTestDataSamples.openSampleFile("ListEntryNoListTable.doc"); + /** + * ListEntry passed no ListTable + */ + public void testListEntryNoListTable() { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("ListEntryNoListTable.doc"); - Range r = doc.getRange(); - StyleSheet styleSheet = doc.getStyleSheet(); - for (int x = 0; x < r.numSections(); x++) { - Section s = r.getSection(x); - for (int y = 0; y < s.numParagraphs(); y++) { - Paragraph paragraph = s.getParagraph(y); - // System.out.println(paragraph.getCharacterRun(0).text()); - } - } - } - - /** - * AIOOB for TableSprmUncompressor.unCompressTAPOperation - */ - public void testSprmAIOOB() { - HWPFDocument doc = HWPFTestDataSamples.openSampleFile("AIOOB-Tap.doc"); - - Range r = doc.getRange(); - StyleSheet styleSheet = doc.getStyleSheet(); - for (int x = 0; x < r.numSections(); x++) { - Section s = r.getSection(x); - for (int y = 0; y < s.numParagraphs(); y++) { - Paragraph paragraph = s.getParagraph(y); - // System.out.println(paragraph.getCharacterRun(0).text()); - } - } - } - - /** - * Test for TableCell not skipping the last paragraph. Bugs #45062 and - * #44292 - */ - public void testTableCellLastParagraph() { - HWPFDocument doc = HWPFTestDataSamples.openSampleFile("Bug44292.doc"); - Range r = doc.getRange(); - assertEquals(6, r.numParagraphs()); - assertEquals(0, r.getStartOffset()); - assertEquals(87, r.getEndOffset()); - - // Paragraph with table - Paragraph p = r.getParagraph(0); - assertEquals(0, p.getStartOffset()); - assertEquals(20, p.getEndOffset()); - - // Get the table - Table t = r.getTable(p); - - // get the only row - assertEquals(1, t.numRows()); - TableRow row = t.getRow(0); - - // get the first cell - TableCell cell = row.getCell(0); - // First cell should have one paragraph - assertEquals(1, cell.numParagraphs()); - assertEquals("One paragraph is ok\7", cell.getParagraph(0).text()); - - // get the second - cell = row.getCell(1); - // Second cell should be detected as having two paragraphs - assertEquals(2, cell.numParagraphs()); - assertEquals("First para is ok\r", cell.getParagraph(0).text()); - assertEquals("Second paragraph is skipped\7", cell.getParagraph(1).text()); - - // get the last cell - cell = row.getCell(2); - // Last cell should have one paragraph - assertEquals(1, cell.numParagraphs()); - assertEquals("One paragraph is ok\7", cell.getParagraph(0).text()); - } - - public void testRangeDelete() { - HWPFDocument doc = HWPFTestDataSamples.openSampleFile("Bug28627.doc"); - - Range range = doc.getRange(); - int numParagraphs = range.numParagraphs(); - - int totalLength = 0, deletedLength = 0; - - for (int i = 0; i < numParagraphs; i++) { - Paragraph para = range.getParagraph(i); - String text = para.text(); - - totalLength += text.length(); - if (text.indexOf("{delete me}") > -1) { - para.delete(); - deletedLength = text.length(); - } - } - - // check the text length after deletion - int newLength = 0; - range = doc.getRange(); - numParagraphs = range.numParagraphs(); - - for (int i = 0; i < numParagraphs; i++) { - Paragraph para = range.getParagraph(i); - String text = para.text(); - - newLength += text.length(); - } - - assertEquals(newLength, totalLength - deletedLength); - } - - /** - * With an encrypted file, we should give a suitable exception, and not OOM - */ - public void testEncryptedFile() { - try { - HWPFTestDataSamples.openSampleFile("PasswordProtected.doc"); - fail(); - } catch (EncryptedDocumentException e) { - // Good - } - } - - public void testWriteProperties() { - HWPFDocument doc = HWPFTestDataSamples.openSampleFile("SampleDoc.doc"); - assertEquals("Nick Burch", doc.getSummaryInformation().getAuthor()); - - // Write and read - HWPFDocument doc2 = writeOutAndRead(doc); - assertEquals("Nick Burch", doc2.getSummaryInformation().getAuthor()); - } - - /** - * Test for reading paragraphs from Range after replacing some - * text in this Range. - * Bug #45269 - */ - public void testReadParagraphsAfterReplaceText()throws Exception{ - HWPFDocument doc = HWPFTestDataSamples.openSampleFile("Bug45269.doc"); - Range range = doc.getRange(); - - String toFind = "campo1"; - String longer = " foi porraaaaa "; - String shorter = " foi "; - - //check replace with longer text - for (int x = 0; x < range.numParagraphs(); x++) { - Paragraph para = range.getParagraph(x); - int offset = para.text().indexOf(toFind); - if (offset >= 0) { - para.replaceText(toFind, longer, offset); - assertEquals(offset, para.text().indexOf(longer)); + Range r = doc.getRange(); + StyleSheet styleSheet = doc.getStyleSheet(); + for (int x = 0; x < r.numSections(); x++) { + Section s = r.getSection(x); + for (int y = 0; y < s.numParagraphs(); y++) { + Paragraph paragraph = s.getParagraph(y); + // System.out.println(paragraph.getCharacterRun(0).text()); + } } - } - - doc = HWPFTestDataSamples.openSampleFile("Bug45269.doc"); - range = doc.getRange(); - - //check replace with shorter text - for (int x = 0; x < range.numParagraphs(); x++) { - Paragraph para = range.getParagraph(x); - int offset = para.text().indexOf(toFind); - if (offset >= 0) { - para.replaceText(toFind, shorter, offset); - assertEquals(offset, para.text().indexOf(shorter)); + } + + /** + * AIOOB for TableSprmUncompressor.unCompressTAPOperation + */ + public void testSprmAIOOB() { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("AIOOB-Tap.doc"); + + Range r = doc.getRange(); + StyleSheet styleSheet = doc.getStyleSheet(); + for (int x = 0; x < r.numSections(); x++) { + Section s = r.getSection(x); + for (int y = 0; y < s.numParagraphs(); y++) { + Paragraph paragraph = s.getParagraph(y); + // System.out.println(paragraph.getCharacterRun(0).text()); + } } - } - } + } + + /** + * Test for TableCell not skipping the last paragraph. Bugs #45062 and + * #44292 + */ + public void testTableCellLastParagraph() { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("Bug44292.doc"); + Range r = doc.getRange(); + assertEquals(6, r.numParagraphs()); + assertEquals(0, r.getStartOffset()); + assertEquals(87, r.getEndOffset()); + + // Paragraph with table + Paragraph p = r.getParagraph(0); + assertEquals(0, p.getStartOffset()); + assertEquals(20, p.getEndOffset()); + + // Check a few bits of the table directly + assertEquals("One paragraph is ok\7", r.getParagraph(0).text()); + assertEquals("First para is ok\r", r.getParagraph(1).text()); + assertEquals("Second paragraph is skipped\7", r.getParagraph(2).text()); + assertEquals("One paragraph is ok\7", r.getParagraph(3).text()); + assertEquals("\7", r.getParagraph(4).text()); + assertEquals("\r", r.getParagraph(5).text()); + for(int i=0; i<=5; i++) { + assertFalse(r.getParagraph(i).usesUnicode()); + } + + + // Get the table + Table t = r.getTable(p); + + // get the only row + assertEquals(1, t.numRows()); + TableRow row = t.getRow(0); + + // sanity check our row + assertEquals(5, row.numParagraphs()); + assertEquals(0, row._parStart); + assertEquals(5, row._parEnd); + assertEquals(0, row.getStartOffset()); + assertEquals(87, row.getEndOffset()); + + + // get the first cell + TableCell cell = row.getCell(0); + // First cell should have one paragraph + assertEquals(1, cell.numParagraphs()); + assertEquals("One paragraph is ok\7", cell.getParagraph(0).text()); + assertEquals(0, cell._parStart); + assertEquals(1, cell._parEnd); + assertEquals(0, cell.getStartOffset()); + assertEquals(20, cell.getEndOffset()); + + + // get the second + cell = row.getCell(1); + // Second cell should be detected as having two paragraphs + assertEquals(2, cell.numParagraphs()); + assertEquals("First para is ok\r", cell.getParagraph(0).text()); + assertEquals("Second paragraph is skipped\7", cell.getParagraph(1).text()); + assertEquals(1, cell._parStart); + assertEquals(3, cell._parEnd); + assertEquals(20, cell.getStartOffset()); + assertEquals(65, cell.getEndOffset()); + + + // get the last cell + cell = row.getCell(2); + // Last cell should have one paragraph + assertEquals(1, cell.numParagraphs()); + assertEquals("One paragraph is ok\7", cell.getParagraph(0).text()); + assertEquals(3, cell._parStart); + assertEquals(4, cell._parEnd); + assertEquals(65, cell.getStartOffset()); + assertEquals(85, cell.getEndOffset()); + } + + public void testRangeDelete() { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("Bug28627.doc"); + + Range range = doc.getRange(); + int numParagraphs = range.numParagraphs(); + + int totalLength = 0, deletedLength = 0; + + for (int i = 0; i < numParagraphs; i++) { + Paragraph para = range.getParagraph(i); + String text = para.text(); + + totalLength += text.length(); + if (text.indexOf("{delete me}") > -1) { + para.delete(); + deletedLength = text.length(); + } + } + + // check the text length after deletion + int newLength = 0; + range = doc.getRange(); + numParagraphs = range.numParagraphs(); + + for (int i = 0; i < numParagraphs; i++) { + Paragraph para = range.getParagraph(i); + String text = para.text(); + + newLength += text.length(); + } + + assertEquals(newLength, totalLength - deletedLength); + } + + /** + * With an encrypted file, we should give a suitable exception, and not OOM + */ + public void testEncryptedFile() { + try { + HWPFTestDataSamples.openSampleFile("PasswordProtected.doc"); + fail(); + } catch (EncryptedDocumentException e) { + // Good + } + } + + public void testWriteProperties() { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("SampleDoc.doc"); + assertEquals("Nick Burch", doc.getSummaryInformation().getAuthor()); + + // Write and read + HWPFDocument doc2 = writeOutAndRead(doc); + assertEquals("Nick Burch", doc2.getSummaryInformation().getAuthor()); + } + + /** + * Test for reading paragraphs from Range after replacing some + * text in this Range. + * Bug #45269 + */ + public void testReadParagraphsAfterReplaceText()throws Exception{ + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("Bug45269.doc"); + Range range = doc.getRange(); + + String toFind = "campo1"; + String longer = " foi porraaaaa "; + String shorter = " foi "; + + //check replace with longer text + for (int x = 0; x < range.numParagraphs(); x++) { + Paragraph para = range.getParagraph(x); + int offset = para.text().indexOf(toFind); + if (offset >= 0) { + para.replaceText(toFind, longer, offset); + assertEquals(offset, para.text().indexOf(longer)); + } + } + + doc = HWPFTestDataSamples.openSampleFile("Bug45269.doc"); + range = doc.getRange(); + + //check replace with shorter text + for (int x = 0; x < range.numParagraphs(); x++) { + Paragraph para = range.getParagraph(x); + int offset = para.text().indexOf(toFind); + if (offset >= 0) { + para.replaceText(toFind, shorter, offset); + assertEquals(offset, para.text().indexOf(shorter)); + } + } + } + + /** + * Bug #48245 - don't include the text from the + * next cell in the current one + */ + public void testTableIterator() throws Exception { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile("simple-table2.doc"); + Range r = doc.getRange(); + + // Check the text is as we'd expect + assertEquals(13, r.numParagraphs()); + assertEquals("Row 1/Cell 1\u0007", r.getParagraph(0).text()); + assertEquals("Row 1/Cell 2\u0007", r.getParagraph(1).text()); + assertEquals("Row 1/Cell 3\u0007", r.getParagraph(2).text()); + assertEquals("\u0007", r.getParagraph(3).text()); + assertEquals("Row 2/Cell 1\u0007", r.getParagraph(4).text()); + assertEquals("Row 2/Cell 2\u0007", r.getParagraph(5).text()); + assertEquals("Row 2/Cell 3\u0007", r.getParagraph(6).text()); + assertEquals("\u0007", r.getParagraph(7).text()); + assertEquals("Row 3/Cell 1\u0007", r.getParagraph(8).text()); + assertEquals("Row 3/Cell 2\u0007", r.getParagraph(9).text()); + assertEquals("Row 3/Cell 3\u0007", r.getParagraph(10).text()); + assertEquals("\u0007", r.getParagraph(11).text()); + assertEquals("\r", r.getParagraph(12).text()); + for(int i=0; i<=12; i++) { + assertFalse(r.getParagraph(i).usesUnicode()); + } + + Paragraph p; + + // Take a look in detail at the first couple of + // paragraphs + p = r.getParagraph(0); + assertEquals(1, p.numParagraphs()); + assertEquals(0, p.getStartOffset()); + assertEquals(13, p.getEndOffset()); + assertEquals(0, p._parStart); + assertEquals(1, p._parEnd); + + p = r.getParagraph(1); + assertEquals(1, p.numParagraphs()); + assertEquals(13, p.getStartOffset()); + assertEquals(26, p.getEndOffset()); + assertEquals(1, p._parStart); + assertEquals(2, p._parEnd); + + p = r.getParagraph(2); + assertEquals(1, p.numParagraphs()); + assertEquals(26, p.getStartOffset()); + assertEquals(39, p.getEndOffset()); + assertEquals(2, p._parStart); + assertEquals(3, p._parEnd); + + + // Now look at the table + Table table = r.getTable(r.getParagraph(0)); + assertEquals(3, table.numRows()); + + TableRow row; + TableCell cell; + + + row = table.getRow(0); + assertEquals(0, row._parStart); + assertEquals(4, row._parEnd); + + cell = row.getCell(0); + assertEquals(1, cell.numParagraphs()); + assertEquals(0, cell._parStart); + assertEquals(1, cell._parEnd); + assertEquals(0, cell.getStartOffset()); + assertEquals(13, cell.getEndOffset()); + assertEquals("Row 1/Cell 1\u0007", cell.text()); + + cell = row.getCell(1); + assertEquals(1, cell.numParagraphs()); + assertEquals(1, cell._parStart); + assertEquals(2, cell._parEnd); + assertEquals(13, cell.getStartOffset()); + assertEquals(26, cell.getEndOffset()); + assertEquals("Row 1/Cell 2\u0007", cell.text()); + + cell = row.getCell(2); + assertEquals(1, cell.numParagraphs()); + assertEquals(2, cell._parStart); + assertEquals(3, cell._parEnd); + assertEquals(26, cell.getStartOffset()); + assertEquals(39, cell.getEndOffset()); + assertEquals("Row 1/Cell 3\u0007", cell.text()); + + + // Onto row #2 + row = table.getRow(1); + assertEquals(4, row._parStart); + assertEquals(8, row._parEnd); + + cell = row.getCell(0); + assertEquals(1, cell.numParagraphs()); + assertEquals(4, cell._parStart); + assertEquals(5, cell._parEnd); + assertEquals(40, cell.getStartOffset()); + assertEquals(53, cell.getEndOffset()); + assertEquals("Row 2/Cell 1\u0007", cell.text()); + + cell = row.getCell(1); + assertEquals(1, cell.numParagraphs()); + assertEquals(5, cell._parStart); + assertEquals(6, cell._parEnd); + assertEquals(53, cell.getStartOffset()); + assertEquals(66, cell.getEndOffset()); + assertEquals("Row 2/Cell 2\u0007", cell.text()); + + cell = row.getCell(2); + assertEquals(1, cell.numParagraphs()); + assertEquals(6, cell._parStart); + assertEquals(7, cell._parEnd); + assertEquals(66, cell.getStartOffset()); + assertEquals(79, cell.getEndOffset()); + assertEquals("Row 2/Cell 3\u0007", cell.text()); + + + // Finally row 3 + row = table.getRow(2); + assertEquals(8, row._parStart); + assertEquals(12, row._parEnd); + + cell = row.getCell(0); + assertEquals(1, cell.numParagraphs()); + assertEquals(8, cell._parStart); + assertEquals(9, cell._parEnd); + assertEquals(80, cell.getStartOffset()); + assertEquals(93, cell.getEndOffset()); + assertEquals("Row 3/Cell 1\u0007", cell.text()); + + cell = row.getCell(1); + assertEquals(1, cell.numParagraphs()); + assertEquals(9, cell._parStart); + assertEquals(10, cell._parEnd); + assertEquals(93, cell.getStartOffset()); + assertEquals(106, cell.getEndOffset()); + assertEquals("Row 3/Cell 2\u0007", cell.text()); + + cell = row.getCell(2); + assertEquals(1, cell.numParagraphs()); + assertEquals(10, cell._parStart); + assertEquals(11, cell._parEnd); + assertEquals(106, cell.getStartOffset()); + assertEquals(119, cell.getEndOffset()); + assertEquals("Row 3/Cell 3\u0007", cell.text()); + } } diff --git a/test-data/document/simple-table2.doc b/test-data/document/simple-table2.doc new file mode 100644 index 0000000000000000000000000000000000000000..1a5ff5f7109fcc8352530ace3d8c0b2c3bcd75aa GIT binary patch literal 27136 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 literal 0 HcmV?d00001