Browse Source

Patch from Andrew Shirley from bug #49185 - Support for HSSFNames where the comment is stored in a NameCommentRecord

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@964845 13f79535-47bb-0310-9956-ffa450edef68
tags/REL_3_7_BETA2
Nick Burch 14 years ago
parent
commit
aa253ea74a

+ 1
- 0
src/documentation/content/xdocs/status.xml View File



<changes> <changes>
<release version="3.7-beta2" date="2010-??-??"> <release version="3.7-beta2" date="2010-??-??">
<action dev="POI-DEVELOPERS" type="add">49185 - Support for HSSFNames where the comment is stored in a NameCommentRecord</action>
<action dev="POI-DEVELOPERS" type="fix">49599 - Correct writing of NoteRecord author text when switching between ASCII and Unicode</action> <action dev="POI-DEVELOPERS" type="fix">49599 - Correct writing of NoteRecord author text when switching between ASCII and Unicode</action>
<action dev="POI-DEVELOPERS" type="fix">HWPF: Improve reading of auto-saved ("complex") documents</action> <action dev="POI-DEVELOPERS" type="fix">HWPF: Improve reading of auto-saved ("complex") documents</action>
<action dev="POI-DEVELOPERS" type="add">Paragraph level as well as whole-file text extraction for Word 6/95 files through HWPF</action> <action dev="POI-DEVELOPERS" type="add">Paragraph level as well as whole-file text extraction for Word 6/95 files through HWPF</action>

+ 1
- 0
src/java/org/apache/poi/hssf/dev/BiffViewer.java View File

case MulBlankRecord.sid: return new MulBlankRecord(in); case MulBlankRecord.sid: return new MulBlankRecord(in);
case MulRKRecord.sid: return new MulRKRecord(in); case MulRKRecord.sid: return new MulRKRecord(in);
case NameRecord.sid: return new NameRecord(in); case NameRecord.sid: return new NameRecord(in);
case NameCommentRecord.sid: return new NameCommentRecord(in);
case NoteRecord.sid: return new NoteRecord(in); case NoteRecord.sid: return new NoteRecord(in);
case NumberRecord.sid: return new NumberRecord(in); case NumberRecord.sid: return new NumberRecord(in);
case ObjRecord.sid: return new ObjRecord(in); case ObjRecord.sid: return new ObjRecord(in);

+ 42
- 1
src/java/org/apache/poi/hssf/model/InternalWorkbook.java View File

import java.security.AccessControlException; import java.security.AccessControlException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;


import org.apache.poi.ddf.EscherBSERecord; import org.apache.poi.ddf.EscherBSERecord;
import org.apache.poi.ddf.EscherBoolProperty; import org.apache.poi.ddf.EscherBoolProperty;
import org.apache.poi.hssf.record.InterfaceEndRecord; import org.apache.poi.hssf.record.InterfaceEndRecord;
import org.apache.poi.hssf.record.InterfaceHdrRecord; import org.apache.poi.hssf.record.InterfaceHdrRecord;
import org.apache.poi.hssf.record.MMSRecord; import org.apache.poi.hssf.record.MMSRecord;
import org.apache.poi.hssf.record.NameCommentRecord;
import org.apache.poi.hssf.record.NameRecord; import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.PaletteRecord; import org.apache.poi.hssf.record.PaletteRecord;
import org.apache.poi.hssf.record.PasswordRecord; import org.apache.poi.hssf.record.PasswordRecord;
private WriteAccessRecord writeAccess; private WriteAccessRecord writeAccess;
private WriteProtectRecord writeProtect; private WriteProtectRecord writeProtect;


/**
* Hold the {@link NameCommentRecord}s indexed by the name of the {@link NameRecord} to which they apply.
*/
private final Map<String, NameCommentRecord> commentRecords;

private InternalWorkbook() { private InternalWorkbook() {
records = new WorkbookRecordList(); records = new WorkbookRecordList();


maxformatid = -1; maxformatid = -1;
uses1904datewindowing = false; uses1904datewindowing = false;
escherBSERecords = new ArrayList<EscherBSERecord>(); escherBSERecords = new ArrayList<EscherBSERecord>();
commentRecords = new LinkedHashMap<String, NameCommentRecord>();
} }


/** /**
// LinkTable can start with either of these // LinkTable can start with either of these
if (log.check( POILogger.DEBUG )) if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found SupBook record at " + k); log.log(DEBUG, "found SupBook record at " + k);
retval.linkTable = new LinkTable(recs, k, retval.records);
retval.linkTable = new LinkTable(recs, k, retval.records, retval.commentRecords);
k+=retval.linkTable.getRecordCount() - 1; k+=retval.linkTable.getRecordCount() - 1;
continue; continue;
case FormatRecord.sid : case FormatRecord.sid :
if (log.check( POILogger.DEBUG )) if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found FileSharing at " + k); log.log(DEBUG, "found FileSharing at " + k);
retval.fileShare = (FileSharingRecord) rec; retval.fileShare = (FileSharingRecord) rec;
break;

case NameCommentRecord.sid:
final NameCommentRecord ncr = (NameCommentRecord) rec;
if (log.check( POILogger.DEBUG ))
log.log(DEBUG, "found NameComment at " + k);
retval.commentRecords.put(ncr.getNameText(), ncr);
default : default :
} }
records.add(rec); records.add(rec);
return linkTable.getNameRecord(index); return linkTable.getNameRecord(index);
} }


/** gets the name comment record
* @param nameRecord name record who's comment is required.
* @return name comment record or <code>null</code> if there isn't one for the given name.
*/
public NameCommentRecord getNameCommentRecord(final NameRecord nameRecord){
return commentRecords.get(nameRecord.getNameText());
}

/** creates new name /** creates new name
* @return new name record * @return new name record
*/ */
} }
} }


/**
* If a {@link NameCommentRecord} is added or the name it references
* is renamed, then this will update the lookup cache for it.
*/
public void updateNameCommentRecordCache(final NameCommentRecord commentRecord) {
if(commentRecords.containsValue(commentRecord)) {
for(Entry<String,NameCommentRecord> entry : commentRecords.entrySet()) {
if(entry.getValue().equals(commentRecord)) {
commentRecords.remove(entry.getKey());
break;
}
}
}
commentRecords.put(commentRecord.getNameText(), commentRecord);
}

/** /**
* Returns a format index that matches the passed in format. It does not tie into HSSFDataFormat. * Returns a format index that matches the passed in format. It does not tie into HSSFDataFormat.
* @param format the format string * @param format the format string

+ 18
- 5
src/java/org/apache/poi/hssf/model/LinkTable.java View File

import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;


import org.apache.poi.hssf.record.CRNCountRecord; import org.apache.poi.hssf.record.CRNCountRecord;
import org.apache.poi.hssf.record.CRNRecord; import org.apache.poi.hssf.record.CRNRecord;
import org.apache.poi.hssf.record.CountryRecord; import org.apache.poi.hssf.record.CountryRecord;
import org.apache.poi.hssf.record.ExternSheetRecord; import org.apache.poi.hssf.record.ExternSheetRecord;
import org.apache.poi.hssf.record.ExternalNameRecord; import org.apache.poi.hssf.record.ExternalNameRecord;
import org.apache.poi.hssf.record.NameCommentRecord;
import org.apache.poi.hssf.record.NameRecord; import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SupBookRecord; import org.apache.poi.hssf.record.SupBookRecord;
private final int _recordCount; private final int _recordCount;
private final WorkbookRecordList _workbookRecordList; // TODO - would be nice to remove this private final WorkbookRecordList _workbookRecordList; // TODO - would be nice to remove this


public LinkTable(List inputList, int startIndex, WorkbookRecordList workbookRecordList) {
public LinkTable(List inputList, int startIndex, WorkbookRecordList workbookRecordList, Map<String, NameCommentRecord> commentRecords) {


_workbookRecordList = workbookRecordList; _workbookRecordList = workbookRecordList;
RecordStream rs = new RecordStream(inputList, startIndex); RecordStream rs = new RecordStream(inputList, startIndex);
} }


_definedNames = new ArrayList<NameRecord>(); _definedNames = new ArrayList<NameRecord>();
// collect zero or more DEFINEDNAMEs id=0x18
while(rs.peekNextClass() == NameRecord.class) {
NameRecord nr = (NameRecord)rs.getNext();
_definedNames.add(nr);
// collect zero or more DEFINEDNAMEs id=0x18,
// with their comments if present
while(true) {
Class nextClass = rs.peekNextClass();
if (nextClass == NameRecord.class) {
NameRecord nr = (NameRecord)rs.getNext();
_definedNames.add(nr);
}
else if (nextClass == NameCommentRecord.class) {
NameCommentRecord ncr = (NameCommentRecord)rs.getNext();
commentRecords.put(ncr.getNameText(), ncr);
}
else {
break;
}
} }


_recordCount = rs.getCountRead(); _recordCount = rs.getCountRead();

+ 150
- 0
src/java/org/apache/poi/hssf/record/NameCommentRecord.java View File

/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hssf.record;

import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
import org.apache.poi.util.StringUtil;

/**
* Title: NAMECMT Record (0x0894)
* <p/>
* Description: Defines a comment associated with a specified name.
* <P>
* REFERENCE:
* <P>
*
* @author Andrew Shirley (aks at corefiling.co.uk)
*/
public final class NameCommentRecord extends StandardRecord {
public final static short sid = 0x0894;

private final short field_1_record_type;
private final short field_2_frt_cell_ref_flag;
private final long field_3_reserved;
//private short field_4_name_length;
//private short field_5_comment_length;
private String field_6_name_text;
private String field_7_comment_text;

public NameCommentRecord(final String name, final String comment) {
field_1_record_type = 0;
field_2_frt_cell_ref_flag = 0;
field_3_reserved = 0;
field_6_name_text = name;
field_7_comment_text = comment;
}

@Override
public void serialize(final LittleEndianOutput out) {
final int field_4_name_length = field_6_name_text.length();
final int field_5_comment_length = field_7_comment_text.length();

out.writeShort(field_1_record_type);
out.writeShort(field_2_frt_cell_ref_flag);
out.writeLong(field_3_reserved);
out.writeShort(field_4_name_length);
out.writeShort(field_5_comment_length);

out.writeByte(0);
out.write(field_6_name_text.getBytes());
out.writeByte(0);
out.write(field_7_comment_text.getBytes());
}

@Override
protected int getDataSize() {
return 18 // 4 shorts + 1 long + 2 spurious 'nul's
+ field_6_name_text.length()
+ field_7_comment_text.length();
}

/**
* @param ris the RecordInputstream to read the record from
*/
public NameCommentRecord(final RecordInputStream ris) {
final LittleEndianInput in = ris;
field_1_record_type = in.readShort();
field_2_frt_cell_ref_flag = in.readShort();
field_3_reserved = in.readLong();
final int field_4_name_length = in.readShort();
final int field_5_comment_length = in.readShort();

in.readByte(); //spurious NUL
field_6_name_text = StringUtil.readCompressedUnicode(in, field_4_name_length);
in.readByte(); //spurious NUL
field_7_comment_text = StringUtil.readCompressedUnicode(in, field_5_comment_length);
}

/**
* return the non static version of the id for this record.
*/
@Override
public short getSid() {
return sid;
}

@Override
public String toString() {
final StringBuffer sb = new StringBuffer();

sb.append("[NAMECMT]\n");
sb.append(" .record type = ").append(HexDump.shortToHex(field_1_record_type)).append("\n");
sb.append(" .frt cell ref flag = ").append(HexDump.byteToHex(field_2_frt_cell_ref_flag)).append("\n");
sb.append(" .reserved = ").append(field_3_reserved).append("\n");
sb.append(" .name length = ").append(field_6_name_text.length()).append("\n");
sb.append(" .comment length = ").append(field_7_comment_text.length()).append("\n");
sb.append(" .name = ").append(field_6_name_text).append("\n");
sb.append(" .comment = ").append(field_7_comment_text).append("\n");
sb.append("[/NAMECMT]\n");

return sb.toString();
}

/**
* @return the name of the NameRecord to which this comment applies.
*/
public String getNameText() {
return field_6_name_text;
}
/**
* Updates the name we're associated with, normally used
* when renaming that Name
*/
public void setNameText(String newName) {
field_6_name_text = newName;
}

/**
* @return the text of the comment.
*/
public String getCommentText() {
return field_7_comment_text;
}
public void setCommentText(String comment) {
field_7_comment_text = comment;
}

public short getRecordType() {
return field_1_record_type;
}

}

+ 1
- 0
src/java/org/apache/poi/hssf/record/RecordFactory.java View File

MulBlankRecord.class, MulBlankRecord.class,
MulRKRecord.class, MulRKRecord.class,
NameRecord.class, NameRecord.class,
NameCommentRecord.class,
NoteRecord.class, NoteRecord.class,
NumberRecord.class, NumberRecord.class,
ObjectProtectRecord.class, ObjectProtectRecord.class,

+ 37
- 2
src/java/org/apache/poi/hssf/usermodel/HSSFName.java View File



import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.model.InternalWorkbook; import org.apache.poi.hssf.model.InternalWorkbook;
import org.apache.poi.hssf.record.NameCommentRecord;
import org.apache.poi.hssf.record.NameRecord; import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.formula.Ptg; import org.apache.poi.hssf.record.formula.Ptg;
import org.apache.poi.ss.formula.FormulaType; import org.apache.poi.ss.formula.FormulaType;
public final class HSSFName implements Name { public final class HSSFName implements Name {
private HSSFWorkbook _book; private HSSFWorkbook _book;
private NameRecord _definedNameRec; private NameRecord _definedNameRec;
private NameCommentRecord _commentRec;


/** Creates new HSSFName - called by HSSFWorkbook to create a sheet from
/**
* Creates new HSSFName - called by HSSFWorkbook to create a name from
* scratch. * scratch.
* *
* @see org.apache.poi.hssf.usermodel.HSSFWorkbook#createName() * @see org.apache.poi.hssf.usermodel.HSSFWorkbook#createName()
* @param book workbook object associated with the sheet. * @param book workbook object associated with the sheet.
*/ */
/* package */ HSSFName(HSSFWorkbook book, NameRecord name) { /* package */ HSSFName(HSSFWorkbook book, NameRecord name) {
this(book, name, null);
}
/**
* Creates new HSSFName - called by HSSFWorkbook to create a name from
* scratch.
*
* @see org.apache.poi.hssf.usermodel.HSSFWorkbook#createName()
* @param name the Name Record
* @param comment the Name Comment Record, optional.
* @param book workbook object associated with the sheet.
*/
/* package */ HSSFName(final HSSFWorkbook book, final NameRecord name, final NameCommentRecord comment) {
_book = book; _book = book;
_definedNameRec = name; _definedNameRec = name;
_commentRec = comment;
} }


/** Get the sheets name which this named range is referenced to /** Get the sheets name which this named range is referenced to
} }
} }
} }
// Update our comment, if there is one
if(_commentRec != null) {
String oldName = _commentRec.getNameText();
_commentRec.setNameText(nameName);
_book.getWorkbook().updateNameCommentRecordCache(_commentRec);
}
} }


private static void validateName(String name){ private static void validateName(String name){
* *
* @return the user comment for this named range * @return the user comment for this named range
*/ */
public String getComment(){
public String getComment() {
if(_commentRec != null) {
// Prefer the comment record if it has text in it
if(_commentRec.getCommentText() != null &&
_commentRec.getCommentText().length() > 0) {
return _commentRec.getCommentText();
}
}
return _definedNameRec.getDescriptionText(); return _definedNameRec.getDescriptionText();
} }


* @param comment the user comment for this named range * @param comment the user comment for this named range
*/ */
public void setComment(String comment){ public void setComment(String comment){
// Update the main record
_definedNameRec.setDescriptionText(comment); _definedNameRec.setDescriptionText(comment);
// If we have a comment record too, update that as well
if(_commentRec != null) {
_commentRec.setCommentText(comment);
}
} }


/** /**

+ 3
- 3
src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java View File

import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.CreationHelper; import org.apache.poi.ss.usermodel.CreationHelper;
import org.apache.poi.ss.usermodel.PictureData;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.formula.FormulaType; import org.apache.poi.ss.formula.FormulaType;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
} }


for (int i = 0 ; i < workbook.getNumNames() ; ++i){ for (int i = 0 ; i < workbook.getNumNames() ; ++i){
HSSFName name = new HSSFName(this, workbook.getNameRecord(i));
NameRecord nameRecord = workbook.getNameRecord(i);
HSSFName name = new HSSFName(this, nameRecord, workbook.getNameCommentRecord(nameRecord));
names.add(name); names.add(name);
} }
} }


if (isNewRecord) if (isNewRecord)
{ {
HSSFName newName = new HSSFName(this, nameRecord);
HSSFName newName = new HSSFName(this, nameRecord, nameRecord.isBuiltInName() ? null : workbook.getNameCommentRecord(nameRecord));
names.add(newName); names.add(newName);
} }



+ 32
- 1
src/testcases/org/apache/poi/hssf/model/TestLinkTable.java View File

package org.apache.poi.hssf.model; package org.apache.poi.hssf.model;


import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;


import junit.framework.AssertionFailedError; import junit.framework.AssertionFailedError;
import junit.framework.TestCase; import junit.framework.TestCase;


import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.hssf.record.NameCommentRecord;
import org.apache.poi.hssf.record.NameRecord;
import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.Record;
import org.apache.poi.hssf.record.SSTRecord; import org.apache.poi.hssf.record.SSTRecord;
import org.apache.poi.hssf.record.SupBookRecord; import org.apache.poi.hssf.record.SupBookRecord;
LinkTable lt; LinkTable lt;
try { try {
lt = new LinkTable(recList, 0, wrl);
lt = new LinkTable(recList, 0, wrl, Collections.<String, NameCommentRecord>emptyMap());
} catch (RuntimeException e) { } catch (RuntimeException e) {
if (e.getMessage().equals("Expected an EXTERNSHEET record but got (org.apache.poi.hssf.record.SSTRecord)")) { if (e.getMessage().equals("Expected an EXTERNSHEET record but got (org.apache.poi.hssf.record.SSTRecord)")) {
throw new AssertionFailedError("Identified bug 47001b"); throw new AssertionFailedError("Identified bug 47001b");
} }
assertNotNull(lt); assertNotNull(lt);
} }

/**
*
*/
public void testNameCommentRecordBetweenNameRecords() {

final Record[] recs = {
new NameRecord(),
new NameCommentRecord("name1", "comment1"),
new NameRecord(),
new NameCommentRecord("name2", "comment2"),

};
final List<Record> recList = Arrays.asList(recs);
final WorkbookRecordList wrl = new WorkbookRecordList();
final Map<String, NameCommentRecord> commentRecords = new LinkedHashMap<String, NameCommentRecord>();

final LinkTable lt = new LinkTable(recList, 0, wrl, commentRecords);
assertNotNull(lt);

assertEquals(2, commentRecords.size());
assertTrue(recs[1] == commentRecords.get("name1")); //== is intentionally not .equals()!
assertTrue(recs[3] == commentRecords.get("name2")); //== is intentionally not .equals()!

assertEquals(2, lt.getNumNames());
}
} }

+ 42
- 0
src/testcases/org/apache/poi/hssf/record/TestNameCommentRecord.java View File

/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */

package org.apache.poi.hssf.record;

import junit.framework.TestCase;

import org.apache.poi.util.HexRead;

/**
* Tests the NameCommentRecord serializes/deserializes correctly
*
* @author Andrew Shirley (aks at corefiling.co.uk)
*/
public final class TestNameCommentRecord extends TestCase {
public void testReserialize() {
final byte[] data = HexRead
.readFromString(""
+ "94 08 00 00 00 00 00 00 00 00 00 00 04 00 07 00 00 6E 61 6D 65 00 63 6F 6D 6D 65 6E 74]");
final RecordInputStream in = TestcaseRecordInputStream.create(NameCommentRecord.sid, data);
final NameCommentRecord ncr = new NameCommentRecord(in);
assertEquals(0x0894, ncr.getRecordType());
assertEquals("name", ncr.getNameText());
assertEquals("comment", ncr.getCommentText());
final byte[] data2 = ncr.serialize();
TestcaseRecordInputStream.confirmRecordEncoding(NameCommentRecord.sid, data, data2);
}
}

+ 27
- 0
src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java View File

assertEquals(234.0, row.getCell(1).getNumericCellValue()); assertEquals(234.0, row.getCell(1).getNumericCellValue());
} }
} }
/**
* Test for a file with NameRecord with NameCommentRecord comments
*/
public void test49185() throws Exception {
HSSFWorkbook wb = openSample("49185.xls");
Name name = wb.getName("foobarName");
assertEquals("This is a comment", name.getComment());
// Rename the name, comment comes with it
name.setNameName("ChangedName");
assertEquals("This is a comment", name.getComment());
// Save and re-check
wb = writeOutAndReadBack(wb);
name = wb.getName("ChangedName");
assertEquals("This is a comment", name.getComment());
// Now try to change it
name.setComment("Changed Comment");
assertEquals("Changed Comment", name.getComment());
// Save and re-check
wb = writeOutAndReadBack(wb);
name = wb.getName("ChangedName");
assertEquals("Changed Comment", name.getComment());
}
} }

BIN
test-data/spreadsheet/49185.xls View File


Loading…
Cancel
Save