Hi all,
Just finished battling with the bugs in POI HWPF component. When searching forums, I found more questions than answers so I wanted to save others a significant effort until POI guys implement all the fixes.
Basically, the synopsis is, all the delete() methods are broken and replacing a string with a string of a different size corrupts the document. But insertAfter and insertBefore methods are mostly working.
I did not want to add a method to their class, because it will probably be overwritten. On the other hand, it is unknown when it will be fixed. Therefore, I had to go via reflection.
Here's the method, just attach it to your class and it's ready to use:
/**
* Replaces text in the paragraph by a specified new text.
* @param r A paragraph to replace.
* @param newText A new text.
* @throws Exception if anything goes wrong
*/
protected void setParagraphText(Paragraph r, String newText) throws Exception {
int length = r.text().length() - 1;
Class clRange = Range.class;
Field fldText = clRange.getDeclaredField("_text");
fldText.setAccessible(true);
Field fldTextEnd = clRange.getDeclaredField("_textEnd");
fldTextEnd.setAccessible(true);
Field fldStart = clRange.getDeclaredField("_start");
fldStart.setAccessible(true);
List textPieces = (List)fldText.get(r);
int _textEnd = fldTextEnd.getInt(r);
TextPiece t = (TextPiece)textPieces.get(_textEnd - 1);
StringBuffer sb = t.getStringBuffer();
int offset = fldStart.getInt(r);
int diff = newText.length() - length;
if (diff <= 0) {
// delete doesn't work properly yet, corrupting the documents.
// Therefore a quick and ugly workaround is to pad the new text with spaces
for (int i = 0; i < -diff; i++)
newText += " ";
sb.replace(offset, offset + length, newText);
} else {
// when the new text is longer, the old one must be replaced
// character by character, and the difference is added using
// insertAfter method
if (r.isInTable()) {
// more obstacles when working with tables though.
// Not only the regular insertAfter does not work,
// but also insertAfter called from a cell overruns cell delimiters.
// Needless to say, getTable(r) does not return the required table.
// Fortunately, there's a workaround
TableIterator ti = new TableIterator(range);
TableCell tc;
while (ti.hasNext()) {
Table tbl = ti.next();
for (int i = 0; i < tbl.numRows(); i++) {
TableRow tr = tbl.getRow(i);
for (int j = 0; j < tr.numCells(); j++) {
tc = tr.getCell(j);
if (tc.text().startsWith(sb.substring(offset, offset + length))) {
sb.replace(offset, offset + length,
newText.substring(newText.length() - length));
// only like this, otherwise cell delimiter will be run over
tc.insertBefore(newText.substring(0, newText.length() - length));
return;
}
}
}
}
sb.replace(offset, offset + length, newText.substring(0, length));
} else {
sb.replace(offset, offset + length, newText.substring(0, length));
r.insertAfter(newText.substring(length));
}
}
}