From e423cc45ed5259e6627c06817361150ee520fef4 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Mon, 1 Jan 2007 21:02:22 +0000 Subject: [PATCH] Comment support from bug 41198, patch from Yegor git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@491629 13f79535-47bb-0310-9956-ffa450edef68 --- .../content/xdocs/hssf/quick-guide.xml | 82 +++++- .../hssf/usermodel/examples/CellComments.java | 99 +++++++ .../org/apache/poi/hssf/dev/BiffViewer.java | 3 + .../hssf/eventmodel/EventRecordFactory.java | 4 +- .../apache/poi/hssf/model/AbstractShape.java | 6 +- .../apache/poi/hssf/model/CommentShape.java | 133 ++++++++++ .../poi/hssf/record/EscherAggregate.java | 28 +- .../apache/poi/hssf/record/NoteRecord.java | 246 ++++++++++++++++++ .../hssf/record/NoteStructureSubRecord.java | 130 +++++++++ .../apache/poi/hssf/record/RecordFactory.java | 3 +- .../org/apache/poi/hssf/record/SubRecord.java | 3 + .../apache/poi/hssf/usermodel/HSSFCell.java | 61 ++++- .../poi/hssf/usermodel/HSSFComment.java | 143 ++++++++++ .../poi/hssf/usermodel/HSSFPatriarch.java | 15 ++ .../poi/hssf/usermodel/HSSFSimpleShape.java | 4 +- .../org/apache/poi/hssf/HSSFTests.java | 4 +- .../poi/hssf/data/SimpleWithComments.xls | Bin 0 -> 13824 bytes .../poi/hssf/record/TestNoteRecord.java | 82 ++++++ .../record/TestNoteStructureSubRecord.java | 68 +++++ .../poi/hssf/usermodel/TestHSSFComment.java | 120 +++++++++ 20 files changed, 1225 insertions(+), 9 deletions(-) create mode 100644 src/examples/src/org/apache/poi/hssf/usermodel/examples/CellComments.java create mode 100644 src/java/org/apache/poi/hssf/model/CommentShape.java create mode 100644 src/java/org/apache/poi/hssf/record/NoteRecord.java create mode 100644 src/java/org/apache/poi/hssf/record/NoteStructureSubRecord.java create mode 100644 src/java/org/apache/poi/hssf/usermodel/HSSFComment.java create mode 100644 src/testcases/org/apache/poi/hssf/data/SimpleWithComments.xls create mode 100644 src/testcases/org/apache/poi/hssf/record/TestNoteRecord.java create mode 100644 src/testcases/org/apache/poi/hssf/record/TestNoteStructureSubRecord.java create mode 100644 src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java diff --git a/src/documentation/content/xdocs/hssf/quick-guide.xml b/src/documentation/content/xdocs/hssf/quick-guide.xml index 59a27dc65..8bc58d6ba 100644 --- a/src/documentation/content/xdocs/hssf/quick-guide.xml +++ b/src/documentation/content/xdocs/hssf/quick-guide.xml @@ -47,6 +47,7 @@
  • Outlining
  • Images
  • Named Ranges and Named Cells
  • +
  • How to set cell comments
  • Features @@ -1034,6 +1035,85 @@
    - + +
    Cell Comments +

    + In Excel a comment is a kind of a text shape, + so inserting a comment is very similar to placing a text box in a worksheet: +

    + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Cell comments in POI HSSF"); + + // Create the drawing patriarch. This is the top level container for all shapes including cell comments. + HSSFPatriarch patr = sheet.createDrawingPatriarch(); + + //create a cell in row 3 + HSSFCell cell1 = sheet.createRow(3).createCell((short)1); + cell1.setCellValue(new HSSFRichTextString("Hello, World")); + + //anchor defines size and position of the comment in worksheet + HSSFComment comment1 = patr.createComment(new HSSFClientAnchor(0, 0, 0, 0, (short)4, 2, (short) 6, 5)); + + // set text in the comment + comment1.setString(new HSSFRichTextString("We can set comments in POI")); + + //set comment author. + //you can see it in the status bar when moving mouse over the commented cell + comment1.setAuthor("Apache Software Foundation"); + + // The first way to assign comment to a cell is via HSSFCell.setCellComment method + cell1.setCellComment(comment1); + + //create another cell in row 6 + HSSFCell cell2 = sheet.createRow(6).createCell((short)1); + cell2.setCellValue(36.6); + + + HSSFComment comment2 = patr.createComment(new HSSFClientAnchor(0, 0, 0, 0, (short)4, 8, (short) 6, 11)); + //modify background color of the comment + comment2.setFillColor(204, 236, 255); + + HSSFRichTextString string = new HSSFRichTextString("Normal body temperature"); + + //apply custom font to the text in the comment + HSSFFont font = wb.createFont(); + font.setFontName("Arial"); + font.setFontHeightInPoints((short)10); + font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); + font.setColor(HSSFColor.RED.index); + string.applyFont(font); + + comment2.setString(string); + //by default comments are hidden. This one is always visible. + comment2.setVisible(true); + + comment2.setAuthor("Bill Gates"); + + /** + * The second way to assign comment to a cell is to implicitly specify its row and column. + * Note, it is possible to set row and column of a non-existing cell. + * It works, the commnet is visible. + */ + comment2.setRow(6); + comment2.setColumn((short)1); + + FileOutputStream out = new FileOutputStream("poi_comment.xls"); + wb.write(out); + out.close(); + +

    + Reading cell comments +

    + + HSSFCell cell = sheet.get(3).getColumn((short)1); + HSSFComment comment = cell.getCellComment(); + if (comment != null) { + HSSFRichTextString str = comment.getString(); + String author = comment.getAuthor(); + } + +
    + diff --git a/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellComments.java b/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellComments.java new file mode 100644 index 000000000..7ec606fea --- /dev/null +++ b/src/examples/src/org/apache/poi/hssf/usermodel/examples/CellComments.java @@ -0,0 +1,99 @@ +/* ==================================================================== + 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.usermodel.examples; + +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.hssf.util.HSSFColor; + +import java.io.*; + +/** + * Demonstrates how to work with excel cell comments. + * + *

    + * Excel comment is a kind of a text shape, + * so inserting a comment is very similar to placing a text box in a worksheet + *

    + * + * @author Yegor Kozlov + */ +public class CellComments { + + public static void main(String[] args) throws IOException { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Cell comments in POI HSSF"); + + // Create the drawing patriarch. This is the top level container for all shapes including cell comments. + HSSFPatriarch patr = sheet.createDrawingPatriarch(); + + //create a cell in row 3 + HSSFCell cell1 = sheet.createRow(3).createCell((short)1); + cell1.setCellValue(new HSSFRichTextString("Hello, World")); + + //anchor defines size and position of the comment in worksheet + HSSFComment comment1 = patr.createComment(new HSSFClientAnchor(0, 0, 0, 0, (short)4, 2, (short) 6, 5)); + + // set text in the comment + comment1.setString(new HSSFRichTextString("We can set comments in POI")); + + //set comment author. + //you can see it in the status bar when moving mouse over the commented cell + comment1.setAuthor("Apache Software Foundation"); + + // The first way to assign comment to a cell is via HSSFCell.setCellComment method + cell1.setCellComment(comment1); + + //create another cell in row 6 + HSSFCell cell2 = sheet.createRow(6).createCell((short)1); + cell2.setCellValue(36.6); + + + HSSFComment comment2 = patr.createComment(new HSSFClientAnchor(0, 0, 0, 0, (short)4, 8, (short) 6, 11)); + //modify background color of the comment + comment2.setFillColor(204, 236, 255); + + HSSFRichTextString string = new HSSFRichTextString("Normal body temperature"); + + //apply custom font to the text in the comment + HSSFFont font = wb.createFont(); + font.setFontName("Arial"); + font.setFontHeightInPoints((short)10); + font.setBoldweight(HSSFFont.BOLDWEIGHT_BOLD); + font.setColor(HSSFColor.RED.index); + string.applyFont(font); + + comment2.setString(string); + comment2.setVisible(true); //by default comments are hidden. This one is always visible. + + comment2.setAuthor("Bill Gates"); + + /** + * The second way to assign comment to a cell is to implicitly specify its row and column. + * Note, it is possible to set row and column of a non-existing cell. + * It works, the commnet is visible. + */ + comment2.setRow(6); + comment2.setColumn((short)1); + + FileOutputStream out = new FileOutputStream("poi_comment.xls"); + wb.write(out); + out.close(); + + + } +} diff --git a/src/java/org/apache/poi/hssf/dev/BiffViewer.java b/src/java/org/apache/poi/hssf/dev/BiffViewer.java index 6cf4e3825..2d5f02beb 100644 --- a/src/java/org/apache/poi/hssf/dev/BiffViewer.java +++ b/src/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -509,6 +509,9 @@ public class BiffViewer { case FilePassRecord.sid: retval = new FilePassRecord(in); break; + case NoteRecord.sid: + retval = new NoteRecord( in ); + break; default: retval = new UnknownRecord( in ); } diff --git a/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java b/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java index daa0ba08a..04660c568 100644 --- a/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java +++ b/src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java @@ -107,6 +107,7 @@ import org.apache.poi.hssf.record.WindowTwoRecord; import org.apache.poi.hssf.record.WriteAccessRecord; import org.apache.poi.hssf.record.WriteProtectRecord; import org.apache.poi.hssf.record.FilePassRecord; +import org.apache.poi.hssf.record.NoteRecord; /** @@ -158,7 +159,8 @@ public class EventRecordFactory LeftMarginRecord.class, RightMarginRecord.class, TopMarginRecord.class, BottomMarginRecord.class, PaletteRecord.class, StringRecord.class, SharedFormulaRecord.class, - WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class + WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, + NoteRecord.class }; } diff --git a/src/java/org/apache/poi/hssf/model/AbstractShape.java b/src/java/org/apache/poi/hssf/model/AbstractShape.java index 609f78c96..19878db79 100644 --- a/src/java/org/apache/poi/hssf/model/AbstractShape.java +++ b/src/java/org/apache/poi/hssf/model/AbstractShape.java @@ -36,7 +36,11 @@ public abstract class AbstractShape public static AbstractShape createShape( HSSFShape hssfShape, int shapeId ) { AbstractShape shape; - if (hssfShape instanceof HSSFTextbox) + if (hssfShape instanceof HSSFComment) + { + shape = new CommentShape( (HSSFComment)hssfShape, shapeId ); + } + else if (hssfShape instanceof HSSFTextbox) { shape = new TextboxShape( (HSSFTextbox)hssfShape, shapeId ); } diff --git a/src/java/org/apache/poi/hssf/model/CommentShape.java b/src/java/org/apache/poi/hssf/model/CommentShape.java new file mode 100644 index 000000000..9be3fa325 --- /dev/null +++ b/src/java/org/apache/poi/hssf/model/CommentShape.java @@ -0,0 +1,133 @@ +/* ==================================================================== + 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.model; + +import org.apache.poi.hssf.record.*; +import org.apache.poi.hssf.usermodel.HSSFComment; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.ddf.*; + +import java.util.List; +import java.util.Iterator; + +/** + * Represents a cell comment. + * This class converts highlevel model data from HSSFComment + * to low-level records. + * + * @author Yegor Kozlov + */ +public class CommentShape extends TextboxShape { + + private NoteRecord note; + + /** + * Creates the low-level records for a comment. + * + * @param hssfShape The highlevel shape. + * @param shapeId The shape id to use for this shape. + */ + public CommentShape( HSSFComment hssfShape, int shapeId ) + { + super(hssfShape, shapeId); + + note = createNoteRecord(hssfShape, shapeId); + + ObjRecord obj = getObjRecord(); + List records = obj.getSubRecords(); + int cmoIdx = 0; + for (int i = 0; i < records.size(); i++) { + Object r = records.get(i); + + if (r instanceof CommonObjectDataSubRecord){ + //modify autofill attribute inherited from TextObjectRecord + CommonObjectDataSubRecord cmo = (CommonObjectDataSubRecord)r; + cmo.setAutofill(false); + cmoIdx = i; + } + } + //add NoteStructure sub record + //we don't know it's format, for now the record data is empty + NoteStructureSubRecord u = new NoteStructureSubRecord(); + obj.addSubRecord(cmoIdx+1, u); + } + + /** + * Creates the low level NoteRecord + * which holds the comment attributes. + */ + private NoteRecord createNoteRecord( HSSFComment shape, int shapeId ) + { + NoteRecord note = new NoteRecord(); + note.setColumn(shape.getColumn()); + note.setRow((short)shape.getRow()); + note.setFlags(shape.isVisible() ? NoteRecord.NOTE_VISIBLE : NoteRecord.NOTE_HIDDEN); + note.setShapeId((short)shapeId); + note.setAuthor(shape.getAuthor() == null ? "" : shape.getAuthor()); + return note; + } + + /** + * Sets standard escher options for a comment. + * This method is responsible for setting default background, + * shading and other comment properties. + * + * @param shape The highlevel shape. + * @param opt The escher records holding the proerties + * @return number of escher options added + */ + protected int addStandardOptions( HSSFShape shape, EscherOptRecord opt ) + { + super.addStandardOptions(shape, opt); + + //remove unnecessary properties inherited from TextboxShape + java.util.List props = opt.getEscherProperties(); + for ( Iterator iterator = props.iterator(); iterator.hasNext(); ) { + EscherProperty prop = (EscherProperty) iterator.next(); + switch (prop.getId()){ + case EscherProperties.TEXT__TEXTLEFT: + case EscherProperties.TEXT__TEXTRIGHT: + case EscherProperties.TEXT__TEXTTOP: + case EscherProperties.TEXT__TEXTBOTTOM: + case EscherProperties.GROUPSHAPE__PRINT: + case EscherProperties.FILL__FILLBACKCOLOR: + case EscherProperties.LINESTYLE__COLOR: + iterator.remove(); + break; + } + } + + HSSFComment comment = (HSSFComment)shape; + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.GROUPSHAPE__PRINT, comment.isVisible() ? 0x000A0000 : 0x000A0002) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.SHADOWSTYLE__SHADOWOBSURED, 0x00030003 ) ); + opt.addEscherProperty( new EscherSimpleProperty( EscherProperties.SHADOWSTYLE__COLOR, 0x00000000 ) ); + opt.sortProperties(); + return opt.getEscherProperties().size(); // # options added + } + + /** + * Return the NoteRecord holding the comment attributes + * + * @return NoteRecord holding the comment attributes + */ + public NoteRecord getNoteRecord() + { + return note; + } + +} diff --git a/src/java/org/apache/poi/hssf/record/EscherAggregate.java b/src/java/org/apache/poi/hssf/record/EscherAggregate.java index 3e69d6141..28a717682 100644 --- a/src/java/org/apache/poi/hssf/record/EscherAggregate.java +++ b/src/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -23,6 +23,7 @@ import org.apache.poi.hssf.model.AbstractShape; import org.apache.poi.hssf.model.TextboxShape; import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.model.ConvertAnchor; +import org.apache.poi.hssf.model.CommentShape; import java.util.*; @@ -260,6 +261,11 @@ public class EscherAggregate extends AbstractEscherHolderRecord private DrawingManager2 drawingManager; private short drawingGroupId; + /** + * list of "tail" records that need to be serialized after all drawing group records + */ + private List tailRec = new ArrayList(); + public EscherAggregate( DrawingManager2 drawingManager ) { this.drawingManager = drawingManager; @@ -450,6 +456,13 @@ public class EscherAggregate extends AbstractEscherHolderRecord } + // write records that need to be serialized after all drawing group records + for ( int i = 0; i < tailRec.size(); i++ ) + { + Record rec = (Record)tailRec.get(i); + pos += rec.serialize( pos, data ); + } + int bytesWritten = pos - offset; if ( bytesWritten != getRecordSize() ) throw new RecordFormatException( bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize() ); @@ -484,7 +497,13 @@ public class EscherAggregate extends AbstractEscherHolderRecord Record r = (Record) iterator.next(); objRecordSize += r.getRecordSize(); } - return drawingRecordSize + objRecordSize; + int tailRecordSize = 0; + for ( Iterator iterator = tailRec.iterator(); iterator.hasNext(); ) + { + Record r = (Record) iterator.next(); + tailRecordSize += r.getRecordSize(); + } + return drawingRecordSize + objRecordSize + tailRecordSize; } /** @@ -529,6 +548,7 @@ public class EscherAggregate extends AbstractEscherHolderRecord if ( patriarch != null ) { shapeToObj.clear(); + tailRec.clear(); clearEscherRecords(); if ( patriarch.getChildren().size() != 0 ) { @@ -568,6 +588,12 @@ public class EscherAggregate extends AbstractEscherHolderRecord EscherRecord escherTextbox = ( (TextboxShape) shapeModel ).getEscherTextbox(); shapeToObj.put( escherTextbox, ( (TextboxShape) shapeModel ).getTextObjectRecord() ); // escherParent.addChildRecord(escherTextbox); + + if ( shapeModel instanceof CommentShape ){ + CommentShape comment = (CommentShape)shapeModel; + tailRec.add(comment.getNoteRecord()); + } + } escherParent.addChildRecord( shapeModel.getSpContainer() ); } diff --git a/src/java/org/apache/poi/hssf/record/NoteRecord.java b/src/java/org/apache/poi/hssf/record/NoteRecord.java new file mode 100644 index 000000000..018ae7e76 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/NoteRecord.java @@ -0,0 +1,246 @@ +/* ==================================================================== + 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.LittleEndian; + +/** + * NOTE: Comment Associated with a Cell (1Ch) + * + * @author Yegor Kozlov + */ +public class NoteRecord extends Record { + public final static short sid = 0x1C; + + /** + * Flag indicating that the comment is hidden (default) + */ + public final static short NOTE_HIDDEN = 0x0; + + /** + * Flag indicating that the comment is visible + */ + public final static short NOTE_VISIBLE = 0x2; + + private short field_1_row; + private short field_2_col; + private short field_3_flags; + private short field_4_shapeid; + private String field_5_author; + + /** + * Construct a new NoteRecord and + * fill its data with the default values + */ + public NoteRecord() + { + field_5_author = ""; + field_3_flags = 0; + } + + /** + * Constructs a NoteRecord and fills its fields + * from the supplied RecordInputStream. + * + * @param in the stream to read from + */ + public NoteRecord(RecordInputStream in) + { + super(in); + + } + + /** + * @return id of this record. + */ + public short getSid() + { + return sid; + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not a NoteRecord record"); + } + } + + /** + * Read the record data from the supplied RecordInputStream + */ + protected void fillFields(RecordInputStream in) + { + field_1_row = in.readShort(); + field_2_col = in.readShort(); + field_3_flags = in.readShort(); + field_4_shapeid = in.readShort(); + int length = in.readShort(); + byte[] bytes = in.readRemainder(); + field_5_author = new String(bytes, 1, length); + } + + /** + * Serialize the record data into the supplied array of bytes + * + * @param offset offset in the data + * @param data the data to serialize into + * + * @return size of the record + */ + public int serialize(int offset, byte [] data) + { + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + + LittleEndian.putShort(data, 4 + offset , field_1_row); + LittleEndian.putShort(data, 6 + offset , field_2_col); + LittleEndian.putShort(data, 8 + offset , field_3_flags); + LittleEndian.putShort(data, 10 + offset , field_4_shapeid); + LittleEndian.putShort(data, 12 + offset , (short)field_5_author.length()); + + byte[] str = field_5_author.getBytes(); + System.arraycopy(str, 0, data, 15 + offset, str.length); + + return getRecordSize(); + } + + /** + * Size of record + */ + public int getRecordSize() + { + int retval = 4 + 2 + 2 + 2 + 2 + 2 + 1 + field_5_author.length() + 1; + + return retval; + } + + /** + * Convert this record to string. + * Used by BiffViewer and other utulities. + */ + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + buffer.append("[NOTE]\n"); + buffer.append(" .recordid = 0x" + Integer.toHexString( getSid() ) + ", size = " + getRecordSize() + "\n"); + buffer.append(" .row = " + field_1_row + "\n"); + buffer.append(" .col = " + field_2_col + "\n"); + buffer.append(" .flags = " + field_3_flags + "\n"); + buffer.append(" .shapeid = " + field_4_shapeid + "\n"); + buffer.append(" .author = " + field_5_author + "\n"); + buffer.append("[/NOTE]\n"); + return buffer.toString(); + } + + /** + * Return the row that contains the comment + * + * @return the row that contains the comment + */ + public short getRow(){ + return field_1_row; + } + + /** + * Specify the row that contains the comment + * + * @param row the row that contains the comment + */ + public void setRow(short row){ + field_1_row = row; + } + + /** + * Return the column that contains the comment + * + * @return the column that contains the comment + */ + public short getColumn(){ + return field_2_col; + } + + /** + * Specify the column that contains the comment + * + * @param col the column that contains the comment + */ + public void setColumn(short col){ + field_2_col = col; + } + + /** + * Options flags. + * + * @return the options flag + * @see NoteRecord.NOTE_VISIBLE + * @see NoteRecord.NOTE_HIDDEN + */ + public short getFlags(){ + return field_3_flags; + } + + /** + * Options flag + * + * @param flags the options flag + * @see #NOTE_VISIBLE + * @see #NOTE_HIDDEN + */ + public void setFlags(short flags){ + field_3_flags = flags; + } + + /** + * Object id for OBJ record that contains the comment + */ + public short getShapeId(){ + return field_4_shapeid; + } + + /** + * Object id for OBJ record that contains the comment + */ + public void setShapeId(short id){ + field_4_shapeid = id; + } + + /** + * Name of the original comment author + * + * @return the name of the original author of the comment + */ + public String getAuthor(){ + return field_5_author; + } + + /** + * Name of the original comment author + * + * @param author the name of the original author of the comment + */ + public void setAuthor(String author){ + field_5_author = author; + } +} diff --git a/src/java/org/apache/poi/hssf/record/NoteStructureSubRecord.java b/src/java/org/apache/poi/hssf/record/NoteStructureSubRecord.java new file mode 100644 index 000000000..6ad3f8eb6 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/NoteStructureSubRecord.java @@ -0,0 +1,130 @@ +/* ==================================================================== + 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.*; + +/** + * Represents a NoteStructure (0xD) sub record. + * + *

    + * The docs say nothing about it. The length of this record is always 26 bytes. + *

    + * + * @author Yegor Kozlov + */ +public class NoteStructureSubRecord + extends SubRecord +{ + public final static short sid = 0x0D; + + private byte[] reserved; + + /** + * Construct a new NoteStructureSubRecord and + * fill its data with the default values + */ + public NoteStructureSubRecord() + { + //all we know is that the the length of NoteStructureSubRecord is always 22 bytes + reserved = new byte[22]; + } + + /** + * Constructs a NoteStructureSubRecord and sets its fields appropriately. + * + */ + public NoteStructureSubRecord(RecordInputStream in) + { + super(in); + + } + + /** + * Checks the sid matches the expected side for this record + * + * @param id the expected sid. + */ + protected void validateSid(short id) + { + if (id != sid) + { + throw new RecordFormatException("Not a Note Structure record"); + } + } + + /** + * Read the record data from the supplied RecordInputStream + */ + protected void fillFields(RecordInputStream in) + { + //just grab the raw data + reserved = in.readRemainder(); + } + + /** + * Convert this record to string. + * Used by BiffViewer and other utulities. + */ + public String toString() + { + StringBuffer buffer = new StringBuffer(); + + String nl = System.getProperty("line.separator"); + buffer.append("[ftNts ]" + nl); + buffer.append(" size = ").append(getRecordSize()).append(nl); + buffer.append(" reserved = ").append(HexDump.toHex(reserved)).append(nl); + buffer.append("[/ftNts ]" + nl); + return buffer.toString(); + } + + /** + * Serialize the record data into the supplied array of bytes + * + * @param offset offset in the data + * @param data the data to serialize into + * + * @return size of the record + */ + public int serialize(int offset, byte[] data) + { + LittleEndian.putShort(data, 0 + offset, sid); + LittleEndian.putShort(data, 2 + offset, (short)(getRecordSize() - 4)); + System.arraycopy(reserved, 0, data, offset + 4, getRecordSize() - 4); + + return getRecordSize(); + } + + /** + * Size of record + */ + public int getRecordSize() + { + return 4 + reserved.length; + } + + /** + * @return id of this record. + */ + public short getSid() + { + return sid; + } +} + + diff --git a/src/java/org/apache/poi/hssf/record/RecordFactory.java b/src/java/org/apache/poi/hssf/record/RecordFactory.java index 9f777ca42..0ddfb6040 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactory.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactory.java @@ -73,7 +73,8 @@ public class RecordFactory ObjRecord.class, TextObjectRecord.class, PaletteRecord.class, StringRecord.class, RecalcIdRecord.class, SharedFormulaRecord.class, HorizontalPageBreakRecord.class, VerticalPageBreakRecord.class, - WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class + WriteProtectRecord.class, FilePassRecord.class, PaneRecord.class, + NoteRecord.class }; } private static Map recordsMap = recordsToMap(records); diff --git a/src/java/org/apache/poi/hssf/record/SubRecord.java b/src/java/org/apache/poi/hssf/record/SubRecord.java index deca230f1..944c671d6 100644 --- a/src/java/org/apache/poi/hssf/record/SubRecord.java +++ b/src/java/org/apache/poi/hssf/record/SubRecord.java @@ -64,6 +64,9 @@ abstract public class SubRecord case EndSubRecord.sid: r = new EndSubRecord( in ); break; + case NoteStructureSubRecord.sid: + r = new NoteStructureSubRecord( in ); + break; default: r = new UnknownRecord( in ); } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 1f706d316..ec81c0d61 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -33,8 +33,7 @@ import org.apache.poi.hssf.record.formula.Ptg; import java.text.DateFormat; import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; +import java.util.*; /** * High level representation of a cell in a row of a spreadsheet. @@ -51,6 +50,7 @@ import java.util.Date; * @author Andrew C. Oliver (acoliver at apache dot org) * @author Dan Sherman (dsherman at isisph.com) * @author Brian Sanders (kestrel at burdell dot org) Active Cell support + * @author Yegor Kozlov cell comments support * @version 1.0-pre */ @@ -113,6 +113,7 @@ public class HSSFCell private Workbook book; private Sheet sheet; private CellValueRecordInterface record; + private HSSFComment comment; /** * Creates new Cell - Should only be called by HSSFRow. This creates a cell @@ -959,4 +960,60 @@ public class HSSFCell return "Unknown Cell Type: " + getCellType(); } } + + /** + * Assign a comment to this cell + * + * @param comment comment associated with this cell + */ + public void setCellComment(HSSFComment comment){ + comment.setRow((short)record.getRow()); + comment.setColumn(record.getColumn()); + this.comment = comment; + } + + /** + * Returns the comment associated with this cell + * + * @return comment associated with this cell + */ + public HSSFComment getCellComment(){ + if (comment == null) { + HashMap txshapes = new HashMap(); //map shapeId and TextObjectRecord + for (Iterator it = sheet.getRecords().iterator(); it.hasNext(); ) { + Record rec = ( Record ) it.next(); + if (rec instanceof NoteRecord){ + NoteRecord note = (NoteRecord)rec; + if (note.getRow() == record.getRow() && note.getColumn() == record.getColumn()){ + TextObjectRecord txo = (TextObjectRecord)txshapes.get(new Integer(note.getShapeId())); + comment = new HSSFComment(null, null); + comment.setRow(note.getRow()); + comment.setColumn(note.getColumn()); + comment.setAuthor(note.getAuthor()); + comment.setVisible(note.getFlags() == NoteRecord.NOTE_VISIBLE); + comment.setString(txo.getStr()); + break; + } + } else if (rec instanceof ObjRecord){ + ObjRecord obj = (ObjRecord)rec; + SubRecord sub = (SubRecord)obj.getSubRecords().get(0); + if (sub instanceof CommonObjectDataSubRecord){ + CommonObjectDataSubRecord cmo = (CommonObjectDataSubRecord)sub; + if (cmo.getObjectType() == CommonObjectDataSubRecord.OBJECT_TYPE_COMMENT){ + //find the nearest TextObjectRecord which holds comment's text and map it to its shapeId + while(it.hasNext()) { + rec = ( Record ) it.next(); + if (rec instanceof TextObjectRecord) { + txshapes.put(new Integer(cmo.getObjectId()), rec); + break; + } + } + + } + } + } + } + } + return comment; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java new file mode 100644 index 000000000..d56db733a --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java @@ -0,0 +1,143 @@ +/* ==================================================================== + 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.usermodel; + +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.NoteRecord; +import org.apache.poi.hssf.record.TextObjectRecord; +import org.apache.poi.ddf.*; + +import java.util.Map; +import java.util.List; +import java.util.Iterator; + +/** + * Represents a cell comment - a sticky note associated with a cell. + * + * @author Yegor Kolzlov + */ +public class HSSFComment extends HSSFTextbox { + + private boolean visible; + private short col, row; + private String author; + + /** + * Construct a new comment with the given parent and anchor. + * + * @param parent + * @param anchor defines position of this anchor in the sheet + */ + public HSSFComment( HSSFShape parent, HSSFAnchor anchor ) + { + super( parent, anchor ); + setShapeType(OBJECT_TYPE_COMMENT); + + //default color for comments + fillColor = 0x08000050; + + //by default comments are hidden + visible = false; + + author = ""; + } + + + /** + * Returns whether this comment is visible. + * + * @param visible true if the comment is visible, false otherwise + */ + public void setVisible(boolean visible){ + this.visible = visible; + } + + /** + * Sets whether this comment is visible. + * + * @return true if the comment is visible, false otherwise + */ + public boolean isVisible(){ + return this.visible; + } + + /** + * Return the row of the cell that contains the comment + * + * @return the 0-based row of the cell that contains the comment + */ + public int getRow(){ + return row; + } + + /** + * Set the row of the cell that contains the comment + * + * @param row the 0-based row of the cell that contains the comment + */ + public void setRow(int row){ + this.row = (short)row; + } + + /** + * Return the column of the cell that contains the comment + * + * @return the 0-based column of the cell that contains the comment + */ + public short getColumn(){ + return col; + } + + /** + * Set the column of the cell that contains the comment + * + * @param col the 0-based column of the cell that contains the comment + */ + public void setColumn(short col){ + this.col = col; + } + + /** + * Name of the original comment author + * + * @return the name of the original author of the comment + */ + public String getAuthor(){ + return author; + } + + /** + * Name of the original comment author + * + * @param author the name of the original author of the comment + */ + public void setAuthor(String author){ + this.author = author; + } + + /** + * Sets the rich text string used by this comment. + * + * @param string Sets the rich text string used by this object. + */ + public void setString( HSSFRichTextString string ) + { + //if font is not set we must set the default one implicitly + if (string.numFormattingRuns() == 0) string.applyFont((short)0); + super.setString(string); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index bf97179ed..4dfa70075 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -125,6 +125,21 @@ public class HSSFPatriarch return shape; } + /** + * Constructs a cell comment. + * + * @param anchor the client anchor describes how this comment is attached + * to the sheet. + * @return the newly created comment. + */ + public HSSFComment createComment(HSSFAnchor anchor) + { + HSSFComment shape = new HSSFComment(null, anchor); + shape.anchor = anchor; + shapes.add(shape); + return shape; + } + /** * Returns a list of all shapes contained by the patriarch. */ diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java index e2d64bac3..dca653ad8 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java @@ -47,7 +47,7 @@ public class HSSFSimpleShape // public final static short OBJECT_TYPE_LIST_BOX = 18; // public final static short OBJECT_TYPE_GROUP_BOX = 19; // public final static short OBJECT_TYPE_COMBO_BOX = 20; -// public final static short OBJECT_TYPE_COMMENT = 25; + public final static short OBJECT_TYPE_COMMENT = 25; // public final static short OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING = 30; int shapeType = OBJECT_TYPE_LINE; @@ -65,6 +65,7 @@ public class HSSFSimpleShape * @see #OBJECT_TYPE_OVAL * @see #OBJECT_TYPE_RECTANGLE * @see #OBJECT_TYPE_PICTURE + * @see #OBJECT_TYPE_COMMENT */ public int getShapeType() { return shapeType; } @@ -77,6 +78,7 @@ public class HSSFSimpleShape * @see #OBJECT_TYPE_OVAL * @see #OBJECT_TYPE_RECTANGLE * @see #OBJECT_TYPE_PICTURE + * @see #OBJECT_TYPE_COMMENT */ public void setShapeType( int shapeType ){ this.shapeType = shapeType; } diff --git a/src/testcases/org/apache/poi/hssf/HSSFTests.java b/src/testcases/org/apache/poi/hssf/HSSFTests.java index d0fc8fe85..c26ba9beb 100644 --- a/src/testcases/org/apache/poi/hssf/HSSFTests.java +++ b/src/testcases/org/apache/poi/hssf/HSSFTests.java @@ -110,6 +110,7 @@ import org.apache.poi.hssf.util.TestCellReference; import org.apache.poi.hssf.util.TestRKUtil; import org.apache.poi.hssf.util.TestRangeAddress; import org.apache.poi.hssf.util.TestSheetReferences; +import org.apache.poi.hssf.usermodel.TestHSSFComment; /** * Test Suite for running just HSSF tests. Mostly @@ -227,7 +228,8 @@ public class HSSFTests suite.addTest(new TestSuite(TestModelFactory.class)); suite.addTest(new TestSuite(TestDrawingManager.class)); suite.addTest(new TestSuite(TestSheet.class)); - + + suite.addTest(new TestSuite(TestHSSFComment.class)); //$JUnit-END$ return suite; } diff --git a/src/testcases/org/apache/poi/hssf/data/SimpleWithComments.xls b/src/testcases/org/apache/poi/hssf/data/SimpleWithComments.xls new file mode 100644 index 0000000000000000000000000000000000000000..0fbec16a63fb27b3e0d780d91cbaaadda70ba45a GIT binary patch literal 13824 zcmeHOYiv|S6h3!f-M;9yrO2a{wLoaMg%oOp@akg3M|g-(rD#cove<^olI^N!2rdFY zjF_NkOcbN2e~bxe6ik%ZVl*mAl@N)FKP;N~2aJmW1q0OYJ9lsQ!Il&vLGjLJ=FB~3 zX3m*!-gECB`ntGx*SjN+Nrsy(PPx#RE1nd-f%pjP*(Z?q_3_c7!x0C|cK;y+a=mCs zk6TWZ_2hinji^)Pm^kDe*8zkgKfyQ4Iw=rY7J4uo@y`oC-VuJpf3@8$YQqYHHgu!o z%`!Y z<=~k`O%ShCW~Rz0Es=Dp1Z0Xx3rz|5Ez1&*%~i^1uAY%#R8Fjf-7QtY7HF9y)%YQ6 zYQBI!HB-Q!mZL#xWqP_Yvj*w<)U*_p4Gl@9H%kq$PAawX_v^JXtLkLVUKy&ba=Op2 z)C{`)s;nyJmDFym;87MXcvaB zS2kf@6K}T>L>QEclUN6&5OJvVVjl7SDdIba83PM~<-==3c#1w6b|Y+yghG9ihmr!Q z2@P%amcb*EaAgU4I1dqXq4A|HH2Ds5&@dgq{-f$j3va$FiLK(}@ryymt zu1I;5u`J5GDkDBbN#t9k+zwiWW-FHy@)h~O0`34Jw>2IKG%|tXNr^d`No>ptjZq~$#ptCK4xtlfY zGJLQ{*Ia3EIMMQ5PKW03Y4>y@9!H)ED2>CfN4%H}SuQuB1xv8_qeld(DeBH=#nEGi z$*7}$Ej#T!xZt_2A6|ZD#ljxe+H@zhPZVky`-~DP(L9t5$B%9%H@2x8oauS1+asM( ze`~0tW2PqZ$b5{*LcmZ5YGxCFyzwGXwAZD9(&YcAKxt2a@+pkY1jPYRsA-)i2}=3x z)n{6cy)ys(i3|7M`%+$~Ir>@Ufk7G356Z|)P&z}c;kB!>qS63VtkXd@RM_p(yUPA6 zLd6_yQ4(hU0;GyFdQysTaOEgbfNi>!ne-L|2Kn zwMVjpQlHXQ$`w`)2P1PkjnO(BT*@3-WgO$&{AO&!WwxV4_US?rr1c3mn&X?(r>P1t z$8UO)n+gsmn(ULYHC)V6c0<=SoQNHc#2`&h#DRI~6R~M0Lxn*fgBqHF!eB;q5<>Ly zVc3f`@fyO=U!AK6L%$9?^1nmi0(LHLohj0*&H>ZA_0`|c-qq%N`&qBlOnC46I`H0v z#|}-WsvsyQ^*i7>dp5#zf7t=g{oodOj2_*Rl!ESr=b-)>nJPe;myU7HJ*n$&Dt|$H zYb4wmULEz%U61uxf)qY~W<=G89~q3t4wmj|^nY%c8#4-RXCq)EU?X57U?X57U?X57 zU?X57U?X57U?X57kcz;i&i{@hJx6v<8s*#ZBF_J{XLs}b{~dfTJnsh!kn*sfZU1G;nD+vZj---8wbDeQy9vY-b~2BVZ$7BVZ$7BVZ$7BVZ$7BVZ$7 zBXC_KV19CBo}>9bo*((~dm^5x`M(aH_4&mRkIg)@^L;))gXFoN@?ucF^XEC=`oNNB zeclI9ejc9GcK=QU7UTZ~VcgXCWv;9RjmTqYxxu?(_8q{$auYSg`JQ0)^FT6FE_QSr`Uq nU{?kG97DWA8mxZ((y(9ut