diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 158f096d0..4411ea6f6 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 47229 - Fixed ExternalNameRecord to handle DDE links 46287 - Control of header and footer extraction in ExcelExtractor / XSSFExcelExtractor 46554 - New ant target "jar-examples" 46161 - Support in XSSF for setGroupColumnCollapsed and setGroupRowCollapsed diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 84f815edc..b46e086a6 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 47229 - Fixed ExternalNameRecord to handle DDE links 46287 - Control of header and footer extraction in ExcelExtractor / XSSFExcelExtractor 46554 - New ant target "jar-examples" 46161 - Support in XSSF for setGroupColumnCollapsed and setGroupRowCollapsed diff --git a/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java index 1233ab442..0ac34880d 100755 --- a/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java +++ b/src/java/org/apache/poi/hssf/record/ExternalNameRecord.java @@ -17,14 +17,14 @@ package org.apache.poi.hssf.record; +import org.apache.poi.hssf.record.constant.ConstantValueParser; import org.apache.poi.ss.formula.Formula; -import org.apache.poi.util.LittleEndianByteArrayOutputStream; import org.apache.poi.util.LittleEndianOutput; import org.apache.poi.util.StringUtil; /** * EXTERNALNAME (0x0023)

- * + * * @author Josh Micich */ public final class ExternalNameRecord extends StandardRecord { @@ -32,7 +32,7 @@ public final class ExternalNameRecord extends StandardRecord { public final static short sid = 0x0023; // as per BIFF8. (some old versions used 0x223) private static final int OPT_BUILTIN_NAME = 0x0001; - private static final int OPT_AUTOMATIC_LINK = 0x0002; // m$ doc calls this fWantAdvise + private static final int OPT_AUTOMATIC_LINK = 0x0002; // m$ doc calls this fWantAdvise private static final int OPT_PICTURE_LINK = 0x0004; private static final int OPT_STD_DOCUMENT_NAME = 0x0008; private static final int OPT_OLE_LINK = 0x0010; @@ -46,6 +46,21 @@ public final class ExternalNameRecord extends StandardRecord { private String field_4_name; private Formula field_5_name_definition; + /** + * 'rgoper' / 'Last received results of the DDE link' + * (seems to be only applicable to DDE links)
+ * Logically this is a 2-D array, which has been flattened into 1-D array here. + */ + private Object[] _ddeValues; + /** + * (logical) number of columns in the {@link #_ddeValues} array + */ + private int _nColumns; + /** + * (logical) number of rows in the {@link #_ddeValues} array + */ + private int _nRows; + /** * Convenience Function to determine if the name is a built-in name */ @@ -88,6 +103,11 @@ public final class ExternalNameRecord extends StandardRecord { + 2 + field_4_name.length(); // nameLen and name if(hasFormula()) { result += field_5_name_definition.getEncodedSize(); + } else { + if (_ddeValues != null) { + result += 3; // byte, short + result += ConstantValueParser.getEncodedSize(_ddeValues); + } } return result; } @@ -101,6 +121,12 @@ public final class ExternalNameRecord extends StandardRecord { StringUtil.putCompressedUnicode(field_4_name, out); if (hasFormula()) { field_5_name_definition.serialize(out); + } else { + if (_ddeValues != null) { + out.writeByte(_nColumns-1); + out.writeShort(_nRows-1); + ConstantValueParser.encode(out, _ddeValues); + } } } @@ -112,6 +138,16 @@ public final class ExternalNameRecord extends StandardRecord { short nameLength = in.readShort(); field_4_name = in.readCompressedUnicode(nameLength); if(!hasFormula()) { + if (!isStdDocumentNameIdentifier() && !isOLELink() && isAutomaticLink()) { + // both need to be incremented + int nColumns = in.readUByte() + 1; + int nRows = in.readShort() + 1; + + int totalCount = nRows * nColumns; + _ddeValues = ConstantValueParser.parse(in, totalCount); + _nColumns = nColumns; + _nRows = nRows; + } if(in.remaining() > 0) { throw readFail("Some unread data (is formula present?)"); } @@ -127,11 +163,11 @@ public final class ExternalNameRecord extends StandardRecord { field_5_name_definition = Formula.read(formulaLen, in, nBytesRemaining); } /* - * Makes better error messages (while hasFormula() is not reliable) + * Makes better error messages (while hasFormula() is not reliable) * Remove this when hasFormula() is stable. */ private RuntimeException readFail(String msg) { - String fullMsg = msg + " fields: (option=" + field_1_option_flag + " index=" + field_2_index + String fullMsg = msg + " fields: (option=" + field_1_option_flag + " index=" + field_2_index + " not_used=" + field_3_not_used + " name='" + field_4_name + "')"; return new RecordFormatException(fullMsg); } diff --git a/src/testcases/org/apache/poi/hssf/record/TestExternalNameRecord.java b/src/testcases/org/apache/poi/hssf/record/TestExternalNameRecord.java index 8ce8a3d5b..5c693d36c 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestExternalNameRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestExternalNameRecord.java @@ -17,6 +17,8 @@ package org.apache.poi.hssf.record; +import org.apache.poi.util.HexRead; + import junit.framework.AssertionFailedError; import junit.framework.TestCase; /** @@ -102,4 +104,35 @@ public final class TestExternalNameRecord extends TestCase { TestcaseRecordInputStream.confirmRecordEncoding(0x0023, dataPlainName, enr.serialize()); } + + public void testDDELink_bug47229() { + /** + * Hex dump read directly from text of bugzilla 47229 + */ + final byte[] dataDDE = HexRead.readFromString( + "E2 7F 00 00 00 00 " + + "37 00 " + // text len + // 010672AT0 MUNI,[RTG_MOODY_UNDERLYING,RTG_SP_UNDERLYING] + "30 31 30 36 37 32 41 54 30 20 4D 55 4E 49 2C " + + "5B 52 54 47 5F 4D 4F 4F 44 59 5F 55 4E 44 45 52 4C 59 49 4E 47 2C " + + "52 54 47 5F 53 50 5F 55 4E 44 45 52 4C 59 49 4E 47 5D " + + // constant array { { "#N/A N.A.", "#N/A N.A.", }, } + " 01 00 00 " + + "02 09 00 00 23 4E 2F 41 20 4E 2E 41 2E " + + "02 09 00 00 23 4E 2F 41 20 4E 2E 41 2E"); + ExternalNameRecord enr; + try { + enr = createSimpleENR(dataDDE); + } catch (RecordFormatException e) { + // actual msg reported in bugzilla 47229 is different + // because that seems to be using a version from before svn r646666 + if (e.getMessage().startsWith("Some unread data (is formula present?)")) { + throw new AssertionFailedError("Identified bug 47229 - failed to read ENR with OLE/DDE result data"); + } + throw e; + } + assertEquals("010672AT0 MUNI,[RTG_MOODY_UNDERLYING,RTG_SP_UNDERLYING]", enr.getText()); + + TestcaseRecordInputStream.confirmRecordEncoding(0x0023, dataDDE, enr.serialize()); + } }