]> source.dussan.org Git - poi.git/commitdiff
Comment support from bug 41198, patch from Yegor
authorNick Burch <nick@apache.org>
Mon, 1 Jan 2007 21:02:22 +0000 (21:02 +0000)
committerNick Burch <nick@apache.org>
Mon, 1 Jan 2007 21:02:22 +0000 (21:02 +0000)
git-svn-id: https://svn.apache.org/repos/asf/jakarta/poi/trunk@491629 13f79535-47bb-0310-9956-ffa450edef68

20 files changed:
src/documentation/content/xdocs/hssf/quick-guide.xml
src/examples/src/org/apache/poi/hssf/usermodel/examples/CellComments.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/dev/BiffViewer.java
src/java/org/apache/poi/hssf/eventmodel/EventRecordFactory.java
src/java/org/apache/poi/hssf/model/AbstractShape.java
src/java/org/apache/poi/hssf/model/CommentShape.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/EscherAggregate.java
src/java/org/apache/poi/hssf/record/NoteRecord.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/NoteStructureSubRecord.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/record/RecordFactory.java
src/java/org/apache/poi/hssf/record/SubRecord.java
src/java/org/apache/poi/hssf/usermodel/HSSFCell.java
src/java/org/apache/poi/hssf/usermodel/HSSFComment.java [new file with mode: 0644]
src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java
src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java
src/testcases/org/apache/poi/hssf/HSSFTests.java
src/testcases/org/apache/poi/hssf/data/SimpleWithComments.xls [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/TestNoteRecord.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/record/TestNoteStructureSubRecord.java [new file with mode: 0644]
src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java [new file with mode: 0644]

index 59a27dc653556e129281231d80c4f0b549c2de15..8bc58d6ba31eea00b99731597fd96ccbb7bbc1b1 100644 (file)
@@ -47,6 +47,7 @@
                     <li><link href="#Outlining">Outlining</link></li>
                     <li><link href="#Images">Images</link></li>
                     <li><link href="#NamedRanges">Named Ranges and Named Cells</link></li>
+                    <li><link href="#CellComments">How to set cell comments</link></li>
                 </ul>
             </section>
             <section><title>Features</title>
             </source>
             
         </section>
-        
+        <anchor id="CellComments"/>
+        <section><title>Cell Comments</title>
+        <p>
+  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:
+        </p>
+            <source>
+    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();
+        </source>
+         <p>
+  Reading cell comments
+        </p>
+        <source>
+    HSSFCell cell = sheet.get(3).getColumn((short)1);
+    HSSFComment comment = cell.getCellComment(); 
+    if (comment != null) {
+      HSSFRichTextString str = comment.getString();
+      String author = comment.getAuthor();
+    }
+       </source>
+     </section>
+
     </body>
 </document>
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 (file)
index 0000000..7ec606f
--- /dev/null
@@ -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.
+ *
+ * <p>
+ * Excel comment is a kind of a text shape,
+ * so inserting a comment is very similar to placing a text box in a worksheet
+ * </p>
+ *
+ * @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();
+
+
+    }
+}
index 6cf4e38256ce1ba8f9cb987f8bf3a0bae3305b5c..2d5f02bebc69b2e45830d609a7d364dc1d91cef1 100644 (file)
@@ -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 );
         }
index daa0ba08a9b83fb63655b20470bf9fef40f84a64..04660c56800071b1f47ba5a31c8f7f14f92c193f 100644 (file)
@@ -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
             };
        
     }
index 609f78c964d56122c54e36301be92a1503ac6983..19878db797cebdc4506c68bc84ec0643bd0df4ce 100644 (file)
@@ -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 (file)
index 0000000..9be3fa3
--- /dev/null
@@ -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 <code>HSSFComment</code>
+ * 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 <code>TextObjectRecord</code>
+                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 <code>NoteRecord</code>
+     *  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 <code>NoteRecord</code> holding the comment attributes
+     *
+     * @return <code>NoteRecord</code> holding the comment attributes
+     */
+    public NoteRecord getNoteRecord()
+    {
+        return note;
+    }
+
+}
index 3e69d614177f3ae49bd9433e4d9b441eda972d22..28a717682a994d20271bf51b23b4aedc69aa7369 100644 (file)
@@ -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 (file)
index 0000000..018ae7e
--- /dev/null
@@ -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 <code>NoteRecord</code> and
+     * fill its data with the default values
+     */
+    public NoteRecord()
+    {
+        field_5_author = "";
+        field_3_flags = 0;
+    }
+
+    /**
+     * Constructs a <code>NoteRecord</code> and fills its fields
+     * from the supplied <code>RecordInputStream</code>.
+     *
+     * @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 <code>RecordInputStream</code>
+     */
+    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 <code>data</code>
+     * @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 (file)
index 0000000..6ad3f8e
--- /dev/null
@@ -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.
+ *
+ * <p>
+ * The docs say nothing about it. The length of this record is always 26 bytes.
+ * </p>
+ *
+ * @author Yegor Kozlov
+ */
+public class NoteStructureSubRecord
+    extends SubRecord
+{
+    public final static short      sid                             = 0x0D;
+
+    private byte[] reserved;
+
+    /**
+     * Construct a new <code>NoteStructureSubRecord</code> and
+     * fill its data with the default values
+     */
+    public NoteStructureSubRecord()
+    {
+        //all we know is that the the length of <code>NoteStructureSubRecord</code> 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 <code>RecordInputStream</code>
+     */
+    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 <code>data</code>
+     * @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;
+    }
+}
+
+
index 9f777ca42336fe81505e4945d9076c8b5631f4ea..0ddfb6040887a9a2b3d42bf9e0a866683a2efe69 100644 (file)
@@ -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);
index deca230f11319d3a66dce944f569a7f4b4752e67..944c671d6a0594c3f239af9b60258f20a13cf1d5 100644 (file)
@@ -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 );
         }
index 1f706d316fed4c1b966e6f992e6f982d0479b9f6..ec81c0d6140e160a2927bca2ae20af861b41b3f4 100644 (file)
@@ -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 (file)
index 0000000..d56db73
--- /dev/null
@@ -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 <code>true</code> if the comment is visible, <code>false</code> otherwise
+     */
+    public void setVisible(boolean visible){
+        this.visible = visible;
+    }
+
+    /**
+     * Sets whether this comment is visible.
+     *
+     * @return <code>true</code> if the comment is visible, <code>false</code> 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);
+    }
+}
index bf97179ed0e5baf2a0a5a99843eeb827d3fbcc98..4dfa7007543f68e69c0969e7a6952404e1ac59c6 100644 (file)
@@ -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.
      */
index e2d64bac31106778588bf3e1c2f0e8364283e94e..dca653ad8d232cb978ae6b8f1fb62c8ef66e5169 100644 (file)
@@ -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; }
 
index d0fc8fe85426b73ea523aa3e72f7bd7f4a720f00..c26ba9beb35c76b2c54275dad6174097e1dbb7f4 100644 (file)
@@ -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 (file)
index 0000000..0fbec16
Binary files /dev/null and b/src/testcases/org/apache/poi/hssf/data/SimpleWithComments.xls differ
diff --git a/src/testcases/org/apache/poi/hssf/record/TestNoteRecord.java b/src/testcases/org/apache/poi/hssf/record/TestNoteRecord.java
new file mode 100644 (file)
index 0000000..5d45094
--- /dev/null
@@ -0,0 +1,82 @@
+/* ====================================================================
+   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 java.util.Arrays;
+
+/**
+ * Tests the serialization and deserialization of the NoteRecord
+ * class works correctly.  Test data taken directly from a real
+ * Excel file.
+ *
+ * @author Yegor Kozlov
+ */
+public class TestNoteRecord
+        extends TestCase
+{
+    private byte[] data = new byte[] {
+        0x06, 0x00, 0x01, 0x00, 0x02, 0x00, 0x02, 0x04, 0x1A, 0x00,
+        0x00, 0x41, 0x70, 0x61, 0x63, 0x68, 0x65, 0x20, 0x53, 0x6F,
+        0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x20, 0x46, 0x6F, 0x75,
+        0x6E, 0x64, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x00
+    };
+
+    public TestNoteRecord(String name)
+    {
+        super(name);
+    }
+
+    public void testRead()
+            throws Exception
+    {
+
+        NoteRecord record = new NoteRecord(new TestcaseRecordInputStream(NoteRecord.sid, (short)data.length, data));
+
+        assertEquals(NoteRecord.sid, record.getSid());
+        record.validateSid(NoteRecord.sid);
+        assertEquals(6, record.getRow());
+        assertEquals(1, record.getColumn());
+        assertEquals(NoteRecord.NOTE_VISIBLE, record.getFlags());
+        assertEquals(1026, record.getShapeId());
+        assertEquals("Apache Software Foundation", record.getAuthor());
+
+    }
+
+    public void testWrite()
+    {
+        NoteRecord record = new NoteRecord();
+        assertEquals(NoteRecord.sid, record.getSid());
+        record.validateSid(NoteRecord.sid);
+        
+        record.setRow((short)6);
+        record.setColumn((short)1);
+        record.setFlags(NoteRecord.NOTE_VISIBLE);
+        record.setShapeId((short)1026);
+        record.setAuthor("Apache Software Foundation");
+
+        byte [] ser = record.serialize();
+        assertEquals(ser.length - 4, data.length);
+
+        byte[] recdata = new byte[ser.length - 4];
+        System.arraycopy(ser, 4, recdata, 0, recdata.length);
+        assertTrue(Arrays.equals(data, recdata));
+    }
+}
diff --git a/src/testcases/org/apache/poi/hssf/record/TestNoteStructureSubRecord.java b/src/testcases/org/apache/poi/hssf/record/TestNoteStructureSubRecord.java
new file mode 100644 (file)
index 0000000..833aa66
--- /dev/null
@@ -0,0 +1,68 @@
+/* ====================================================================
+   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 java.util.Arrays;
+
+/**
+ * Tests the serialization and deserialization of the NoteRecord
+ * class works correctly.  Test data taken directly from a real
+ * Excel file.
+ *
+ * @author Yegor Kozlov
+ */
+public class TestNoteStructureSubRecord
+        extends TestCase
+{
+    private byte[] data = new byte[] {
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0x80, 0x00, 0x00, 0x00,
+        0x00, 0x00, (byte)0xBF, 0x00, 0x00, 0x00, 0x00, 0x00, (byte)0x81, 0x01,
+        (byte)0xCC, (byte)0xEC
+    };
+
+    public TestNoteStructureSubRecord(String name)
+    {
+        super(name);
+    }
+
+    public void testRead()
+            throws Exception
+    {
+
+        NoteStructureSubRecord record = new NoteStructureSubRecord(new TestcaseRecordInputStream(NoteStructureSubRecord.sid, (short)data.length, data));
+
+        assertEquals(NoteStructureSubRecord.sid, record.getSid());
+        record.validateSid(NoteStructureSubRecord.sid);
+        assertEquals(data.length + 4, record.getRecordSize());
+
+    }
+
+    public void testWrite()
+    {
+        NoteStructureSubRecord record = new NoteStructureSubRecord();
+        assertEquals(NoteStructureSubRecord.sid, record.getSid());
+        record.validateSid(NoteStructureSubRecord.sid);
+        assertEquals(data.length + 4, record.getRecordSize());
+
+        byte [] ser = record.serialize();
+        assertEquals(ser.length - 4, data.length);
+
+    }
+}
diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java
new file mode 100644 (file)
index 0000000..3f38747
--- /dev/null
@@ -0,0 +1,120 @@
+/* ====================================================================
+   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 junit.framework.TestCase;
+
+import java.io.*;
+
+/**
+ * Tests TestHSSFCellComment.
+ *
+ * @author  Yegor Kozlov
+ */
+
+public class TestHSSFComment extends TestCase {
+
+    /**
+     * Test that we can create cells and add comments to it.
+     */
+    public static void testWriteComments() throws Exception {
+        String cellText = "Hello, World";
+        String commentText = "We can set comments in POI";
+        String commentAuthor = "Apache Software Foundation";
+        int cellRow = 3;
+        short cellColumn = 1;
+
+        HSSFWorkbook wb = new HSSFWorkbook();
+
+        HSSFSheet sheet = wb.createSheet();
+
+        HSSFCell cell = sheet.createRow(cellRow).createCell(cellColumn);
+        cell.setCellValue(new HSSFRichTextString(cellText));
+        assertNull(cell.getCellComment());
+
+        HSSFPatriarch patr = sheet.createDrawingPatriarch();
+        HSSFClientAnchor anchor = new HSSFClientAnchor();
+        anchor.setAnchor( (short)4, 2, 0, 0, (short) 6, 5, 0, 0);
+        HSSFComment comment = patr.createComment(anchor);
+        HSSFRichTextString string1 = new HSSFRichTextString(commentText);
+        comment.setString(string1);
+        comment.setAuthor(commentAuthor);
+        cell.setCellComment(comment);
+
+        //verify our settings
+        assertEquals(HSSFSimpleShape.OBJECT_TYPE_COMMENT, comment.getShapeType());
+        assertEquals(commentAuthor, comment.getAuthor());
+        assertEquals(commentText, comment.getString().getString());
+        assertEquals(cellRow, comment.getRow());
+        assertEquals(cellColumn, comment.getColumn());
+
+        //serialize the workbook and read it again
+        ByteArrayOutputStream out = new ByteArrayOutputStream();
+        wb.write(out);
+        out.close();
+
+        wb = new HSSFWorkbook(new ByteArrayInputStream(out.toByteArray()));
+        sheet = wb.getSheetAt(0);
+        cell = sheet.getRow(cellRow).getCell(cellColumn);
+        comment = cell.getCellComment();
+
+        assertNotNull(comment);
+        assertEquals(HSSFSimpleShape.OBJECT_TYPE_COMMENT, comment.getShapeType());
+        assertEquals(commentAuthor, comment.getAuthor());
+        assertEquals(commentText, comment.getString().getString());
+        assertEquals(cellRow, comment.getRow());
+        assertEquals(cellColumn, comment.getColumn());
+    }
+
+    /**
+     * test that we can read cell comments from an existing workbook.
+     */
+    public static void testReadComments() throws Exception {
+
+         String dir = System.getProperty("HSSF.testdata.path");
+         FileInputStream is = new FileInputStream(new File(dir, "SimpleWithComments.xls"));
+         HSSFWorkbook wb = new HSSFWorkbook(is);
+         is.close();
+
+         HSSFSheet sheet = wb.getSheetAt(0);
+
+         HSSFCell cell;
+         HSSFRow row;
+         HSSFComment comment;
+
+         for (int rownum = 0; rownum < 3; rownum++) {
+             row = sheet.getRow(rownum);
+             cell = row.getCell((short)0);
+             comment = cell.getCellComment();
+             assertNull("Cells in the first column are not commented", comment);
+         }
+
+         for (int rownum = 0; rownum < 3; rownum++) {
+             row = sheet.getRow(rownum);
+             cell = row.getCell((short)1);
+             comment = cell.getCellComment();
+             assertNotNull("Cells in the second column have comments", comment);
+             assertEquals(HSSFSimpleShape.OBJECT_TYPE_COMMENT, comment.getShapeType());
+             assertEquals("Yegor Kozlov", comment.getAuthor());
+             assertFalse("cells in the second column have not empyy notes", 
+                     "".equals(comment.getString().getString()));
+             assertEquals(rownum, comment.getRow());
+             assertEquals(cell.getCellNum(), comment.getColumn());
+         }
+     }
+
+}