diff --git a/build.xml b/build.xml index 4c519e113..b90e1615c 100644 --- a/build.xml +++ b/build.xml @@ -632,6 +632,7 @@ under the License. + diff --git a/src/java/org/apache/poi/ddf/AbstractEscherOptRecord.java b/src/java/org/apache/poi/ddf/AbstractEscherOptRecord.java index a86fbda66..b97eeafda 100644 --- a/src/java/org/apache/poi/ddf/AbstractEscherOptRecord.java +++ b/src/java/org/apache/poi/ddf/AbstractEscherOptRecord.java @@ -16,10 +16,7 @@ ==================================================================== */ package org.apache.poi.ddf; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; @@ -139,6 +136,33 @@ public abstract class AbstractEscherOptRecord extends EscherRecord } ); } + /** + * Set an escher property. If a property with given propId already + exists it is replaced. + * + * @param value the property to set. + */ + public void setEscherProperty(EscherProperty value){ + for ( Iterator iterator = + properties.iterator(); iterator.hasNext(); ) { + EscherProperty prop = iterator.next(); + if (prop.getId() == value.getId()){ + iterator.remove(); + } + } + properties.add( value ); + sortProperties(); + } + + public void removeEscherProperty(int num){ + for ( Iterator iterator = getEscherProperties().iterator(); iterator.hasNext(); ) { + EscherProperty prop = iterator.next(); + if (prop.getPropertyNumber() == num){ + iterator.remove(); + } + } + } + /** * Retrieve the string representation of this record. */ diff --git a/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java b/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java index 4b39b8159..cf48a38d8 100644 --- a/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java +++ b/src/java/org/apache/poi/ddf/DefaultEscherRecordFactory.java @@ -65,8 +65,7 @@ public class DefaultEscherRecordFactory implements EscherRecordFactory { // However, EscherTextboxRecord are containers of records for the // host application, not of other Escher records, so treat them // differently - if ( ( options & (short) 0x000F ) == (short) 0x000F - && recordId != EscherTextboxRecord.RECORD_ID ) { + if (isContainer(options, recordId)) { EscherContainerRecord r = new EscherContainerRecord(); r.setRecordId( recordId ); r.setOptions( options ); @@ -145,4 +144,17 @@ public class DefaultEscherRecordFactory implements EscherRecordFactory { } return result; } + + public static boolean isContainer(short options, short recordId){ + if(recordId >= EscherContainerRecord.DGG_CONTAINER && recordId + <= EscherContainerRecord.SOLVER_CONTAINER){ + return true; + } else { + if (recordId == EscherTextboxRecord.RECORD_ID) { + return false; + } else { + return ( options & (short) 0x000F ) == (short) 0x000F; + } + } + } } diff --git a/src/java/org/apache/poi/ddf/EscherArrayProperty.java b/src/java/org/apache/poi/ddf/EscherArrayProperty.java index 6faec5232..01e2f5c36 100644 --- a/src/java/org/apache/poi/ddf/EscherArrayProperty.java +++ b/src/java/org/apache/poi/ddf/EscherArrayProperty.java @@ -62,6 +62,9 @@ public final class EscherArrayProperty extends EscherComplexProperty { } public int getNumberOfElementsInArray() { + if (emptyComplexPart){ + return 0; + } return LittleEndian.getUShort(_complexData, 0); } diff --git a/src/java/org/apache/poi/ddf/EscherBoolProperty.java b/src/java/org/apache/poi/ddf/EscherBoolProperty.java index da9499af3..304439548 100644 --- a/src/java/org/apache/poi/ddf/EscherBoolProperty.java +++ b/src/java/org/apache/poi/ddf/EscherBoolProperty.java @@ -70,7 +70,7 @@ public class EscherBoolProperty public String toXml(String tab){ StringBuilder builder = new StringBuilder(); builder.append(tab).append("<").append(getClass().getSimpleName()).append(" id=\"0x").append(HexDump.toHex(getId())) - .append("\" name=\"").append(getName()).append("\" blipId=\"") + .append("\" name=\"").append(getName()).append("\" simpleValue=\"").append(getPropertyValue()).append("\" blipId=\"") .append(isBlipId()).append("\" value=\"").append(isTrue()).append("\"").append("/>\n"); return builder.toString(); } diff --git a/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java index fbca0fb87..ffc9170c4 100644 --- a/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java +++ b/src/java/org/apache/poi/ddf/EscherClientAnchorRecord.java @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -38,6 +37,14 @@ public class EscherClientAnchorRecord public static final short RECORD_ID = (short) 0xF010; public static final String RECORD_DESCRIPTION = "MsofbtClientAnchor"; + /** + * bit[0] - fMove (1 bit): A bit that specifies whether the shape will be kept intact when the cells are moved. + * bit[1] - fSize (1 bit): A bit that specifies whether the shape will be kept intact when the cells are resized. If fMove is 1, the value MUST be 1. + * bit[2-4] - reserved, MUST be 0 and MUST be ignored + * bit[5-15]- Undefined and MUST be ignored. + * + * it can take values: 0, 2, 3 + */ private short field_1_flag; private short field_2_col1; private short field_3_dx1; diff --git a/src/java/org/apache/poi/ddf/EscherContainerRecord.java b/src/java/org/apache/poi/ddf/EscherContainerRecord.java index 4190594b5..860edd807 100644 --- a/src/java/org/apache/poi/ddf/EscherContainerRecord.java +++ b/src/java/org/apache/poi/ddf/EscherContainerRecord.java @@ -25,6 +25,8 @@ import java.util.NoSuchElementException; import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; /** * Escher container records store other escher records as children. @@ -42,6 +44,32 @@ public final class EscherContainerRecord extends EscherRecord { public static final short SP_CONTAINER = (short)0xF004; public static final short SOLVER_CONTAINER = (short)0xF005; + private static POILogger log = POILogFactory.getLogger(EscherContainerRecord.class); + + /** + * in case if document contains any charts we have such document structure: + * BOF + * ... + * DrawingRecord + * ... + * ObjRecord|TxtObjRecord + * ... + * EOF + * ... + * BOF(Chart begin) + * ... + * DrawingRecord + * ... + * ObjRecord|TxtObjRecord + * ... + * EOF + * So, when we call EscherAggregate.createAggregate() we have not all needed data. + * When we got warning "WARNING: " + bytesRemaining + " bytes remaining but no space left" + * we should save value of bytesRemaining + * and add it to container size when we serialize it + */ + private int _remainingLength; + private final List _childRecords = new ArrayList(); public int fillFields(byte[] data, int pOffset, EscherRecordFactory recordFactory) { @@ -56,7 +84,8 @@ public final class EscherContainerRecord extends EscherRecord { bytesRemaining -= childBytesWritten; addChildRecord(child); if (offset >= data.length && bytesRemaining > 0) { - System.out.println("WARNING: " + bytesRemaining + " bytes remaining but no space left"); + _remainingLength = bytesRemaining; + log.log(POILogger.WARN, "Not enough Escher data: " + bytesRemaining + " bytes remaining but no space left"); } } return bytesWritten; @@ -74,6 +103,7 @@ public final class EscherContainerRecord extends EscherRecord { EscherRecord r = iterator.next(); remainingBytes += r.getRecordSize(); } + remainingBytes += _remainingLength; LittleEndian.putInt(data, offset+4, remainingBytes); int pos = offset+8; iterator = _childRecords.iterator(); diff --git a/src/java/org/apache/poi/ddf/EscherRecord.java b/src/java/org/apache/poi/ddf/EscherRecord.java index cad6be171..382333ba3 100644 --- a/src/java/org/apache/poi/ddf/EscherRecord.java +++ b/src/java/org/apache/poi/ddf/EscherRecord.java @@ -311,7 +311,7 @@ public abstract class EscherRecord { protected String formatXmlRecordHeader(String className, String recordId, String version, String instance){ StringBuilder builder = new StringBuilder(); builder.append("<").append(className).append(" recordId=\"0x").append(recordId).append("\" version=\"0x") - .append(version).append("\" instance=\"0x").append(instance).append("\">\n"); + .append(version).append("\" instance=\"0x").append(instance).append("\" size=\"").append(getRecordSize()).append("\">\n"); return builder.toString(); } diff --git a/src/java/org/apache/poi/ddf/EscherSpRecord.java b/src/java/org/apache/poi/ddf/EscherSpRecord.java index 11a893f46..7d4dd5cfb 100644 --- a/src/java/org/apache/poi/ddf/EscherSpRecord.java +++ b/src/java/org/apache/poi/ddf/EscherSpRecord.java @@ -119,7 +119,7 @@ public class EscherSpRecord public String toXml(String tab) { StringBuilder builder = new StringBuilder(); builder.append(tab).append(formatXmlRecordHeader(getClass().getSimpleName(), HexDump.toHex(getRecordId()), HexDump.toHex(getVersion()), HexDump.toHex(getInstance()))) - .append(tab).append("\t").append("").append(HexDump.toHex(getShapeType())).append("\n") + .append(tab).append("\t").append("0x").append(HexDump.toHex(getShapeType())).append("\n") .append(tab).append("\t").append("").append(field_1_shapeId).append("\n") .append(tab).append("\t").append("").append(decodeFlags(field_2_flags) + " (0x" + HexDump.toHex(field_2_flags) + ")").append("\n"); builder.append(tab).append("\n"); diff --git a/src/java/org/apache/poi/hssf/dev/ReSave.java b/src/java/org/apache/poi/hssf/dev/ReSave.java new file mode 100755 index 000000000..2a111832a --- /dev/null +++ b/src/java/org/apache/poi/hssf/dev/ReSave.java @@ -0,0 +1,63 @@ +/* + * 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.dev; + +import org.apache.poi.hssf.usermodel.HSSFPatriarch; +import org.apache.poi.hssf.usermodel.HSSFShape; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + +import java.io.FileInputStream; +import java.io.FileOutputStream; + +/** + * Utility to test that POI produces readable output + * after re-saving xls files. + * + * Usage: ReSave [-dg] input.xls + * -dg initialize drawings, causes to re-build escher aggregates in all sheets + */ +public class ReSave { + public static void main(String[] args) throws Exception { + boolean initDrawing = false; + for(String arg : args) { + if(arg.equals("-dg")) initDrawing = true; + else { + System.out.print("reading " + arg + "..."); + FileInputStream is = new FileInputStream(arg); + HSSFWorkbook wb = new HSSFWorkbook(is); + is.close(); + System.out.println("done"); + + for(int i = 0; i < wb.getNumberOfSheets(); i++){ + HSSFSheet sheet = wb.getSheetAt(i); + if(initDrawing) { + HSSFPatriarch dg = sheet.getDrawingPatriarch(); + } + } + + String outputFile = arg.replace(".xls", "-saved.xls"); + System.out.print("saving to " + outputFile + "..."); + FileOutputStream out = new FileOutputStream(outputFile); + wb.write(out); + out.close(); + System.out.println("done"); + } + } + } +} diff --git a/src/java/org/apache/poi/hssf/model/AbstractShape.java b/src/java/org/apache/poi/hssf/model/AbstractShape.java index 3fd8883d6..3441ba9f4 100644 --- a/src/java/org/apache/poi/hssf/model/AbstractShape.java +++ b/src/java/org/apache/poi/hssf/model/AbstractShape.java @@ -26,6 +26,7 @@ import org.apache.poi.hssf.usermodel.*; * * @author Glen Stampoultzis (glens at apache.org) */ +@Deprecated public abstract class AbstractShape { /** diff --git a/src/java/org/apache/poi/hssf/model/ComboboxShape.java b/src/java/org/apache/poi/hssf/model/ComboboxShape.java index 674fc46f0..ffca90a79 100644 --- a/src/java/org/apache/poi/hssf/model/ComboboxShape.java +++ b/src/java/org/apache/poi/hssf/model/ComboboxShape.java @@ -27,6 +27,7 @@ import org.apache.poi.hssf.usermodel.*; * * @author Yegor Kozlov */ +@Deprecated public class ComboboxShape extends AbstractShape { private EscherContainerRecord spContainer; diff --git a/src/java/org/apache/poi/hssf/model/CommentShape.java b/src/java/org/apache/poi/hssf/model/CommentShape.java index f739a44d7..19e83fff6 100644 --- a/src/java/org/apache/poi/hssf/model/CommentShape.java +++ b/src/java/org/apache/poi/hssf/model/CommentShape.java @@ -39,6 +39,7 @@ import org.apache.poi.hssf.usermodel.HSSFShape; * * @author Yegor Kozlov */ +@Deprecated public final class CommentShape extends TextboxShape { private NoteRecord _note; diff --git a/src/java/org/apache/poi/hssf/model/DrawingManager2.java b/src/java/org/apache/poi/hssf/model/DrawingManager2.java index c9993c241..0d5e34f24 100644 --- a/src/java/org/apache/poi/hssf/model/DrawingManager2.java +++ b/src/java/org/apache/poi/hssf/model/DrawingManager2.java @@ -112,7 +112,7 @@ public class DrawingManager2 /** * Finds the next available (1 based) drawing group id */ - short findNewDrawingGroupId() + public short findNewDrawingGroupId() { short dgId = 1; while ( drawingGroupExists( dgId ) ) @@ -147,4 +147,7 @@ public class DrawingManager2 return dgg; } + public void incrementDrawingsSaved(){ + dgg.setDrawingsSaved(dgg.getDrawingsSaved()+1); + } } diff --git a/src/java/org/apache/poi/hssf/model/InternalSheet.java b/src/java/org/apache/poi/hssf/model/InternalSheet.java index 3317a9534..856df327e 100644 --- a/src/java/org/apache/poi/hssf/model/InternalSheet.java +++ b/src/java/org/apache/poi/hssf/model/InternalSheet.java @@ -21,43 +21,7 @@ import java.util.ArrayList; import java.util.Iterator; import java.util.List; -import org.apache.poi.hssf.record.BOFRecord; -import org.apache.poi.hssf.record.CFHeaderRecord; -import org.apache.poi.hssf.record.CalcCountRecord; -import org.apache.poi.hssf.record.CalcModeRecord; -import org.apache.poi.hssf.record.CellValueRecordInterface; -import org.apache.poi.hssf.record.ColumnInfoRecord; -import org.apache.poi.hssf.record.DVALRecord; -import org.apache.poi.hssf.record.DefaultColWidthRecord; -import org.apache.poi.hssf.record.DefaultRowHeightRecord; -import org.apache.poi.hssf.record.DeltaRecord; -import org.apache.poi.hssf.record.DimensionsRecord; -import org.apache.poi.hssf.record.DrawingRecord; -import org.apache.poi.hssf.record.EOFRecord; -import org.apache.poi.hssf.record.EscherAggregate; -import org.apache.poi.hssf.record.FeatHdrRecord; -import org.apache.poi.hssf.record.FeatRecord; -import org.apache.poi.hssf.record.GridsetRecord; -import org.apache.poi.hssf.record.GutsRecord; -import org.apache.poi.hssf.record.IndexRecord; -import org.apache.poi.hssf.record.IterationRecord; -import org.apache.poi.hssf.record.MergeCellsRecord; -import org.apache.poi.hssf.record.NoteRecord; -import org.apache.poi.hssf.record.ObjRecord; -import org.apache.poi.hssf.record.PaneRecord; -import org.apache.poi.hssf.record.PrintGridlinesRecord; -import org.apache.poi.hssf.record.PrintHeadersRecord; -import org.apache.poi.hssf.record.Record; -import org.apache.poi.hssf.record.RecordBase; -import org.apache.poi.hssf.record.RefModeRecord; -import org.apache.poi.hssf.record.RowRecord; -import org.apache.poi.hssf.record.SCLRecord; -import org.apache.poi.hssf.record.SaveRecalcRecord; -import org.apache.poi.hssf.record.SelectionRecord; -import org.apache.poi.hssf.record.TextObjectRecord; -import org.apache.poi.hssf.record.UncalcedRecord; -import org.apache.poi.hssf.record.WSBoolRecord; -import org.apache.poi.hssf.record.WindowTwoRecord; +import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.aggregates.ChartSubstreamRecordAggregate; import org.apache.poi.hssf.record.aggregates.ColumnInfoRecordsAggregate; import org.apache.poi.hssf.record.aggregates.ConditionalFormattingTable; @@ -387,12 +351,10 @@ public final class InternalSheet { continue; } if (rb instanceof EscherAggregate){ - // EscherAggregate is used only as a container for SODRAWING and OBJ record combinations - // So, if the container is empty, there is no reason to clone this record - // See https://issues.apache.org/bugzilla/show_bug.cgi?id=49529 - if (0 == rb.getRecordSize()){ - continue; - } + /** + * this record will be removed after reading actual data from EscherAggregate + */ + rb = new DrawingRecord(); } Record rec = (Record) ((Record) rb).clone(); clonedRecords.add(rec); @@ -1523,6 +1485,7 @@ public final class InternalSheet { * if none currently exist * @param drawingManager The DrawingManager2 for our workbook * @param createIfMissing Should one be created if missing? + * @return location of EscherAggregate record. if no EscherAggregate record is found return -1 */ public int aggregateDrawingRecords(DrawingManager2 drawingManager, boolean createIfMissing) { int loc = findFirstRecordLocBySid(DrawingRecord.sid); @@ -1533,7 +1496,7 @@ public final class InternalSheet { return -1; } - EscherAggregate aggregate = new EscherAggregate( drawingManager ); + EscherAggregate aggregate = new EscherAggregate(); loc = findFirstRecordLocBySid(EscherAggregate.sid); if (loc == -1) { loc = findFirstRecordLocBySid( WindowTwoRecord.sid ); @@ -1544,23 +1507,10 @@ public final class InternalSheet { return loc; } List records = getRecords(); - EscherAggregate r = EscherAggregate.createAggregate( records, loc, drawingManager ); - int startloc = loc; - while ( loc + 1 < records.size() - && records.get( loc ) instanceof DrawingRecord - && (records.get( loc + 1 ) instanceof ObjRecord || - records.get( loc + 1 ) instanceof TextObjectRecord) ) - { - loc += 2; - if (records.get( loc ) instanceof NoteRecord) loc ++; - } - int endloc = loc-1; - for(int i = 0; i < (endloc - startloc + 1); i++) - records.remove(startloc); - records.add(startloc, r); + EscherAggregate.createAggregate( records, loc, drawingManager ); - return startloc; + return loc; } /** diff --git a/src/java/org/apache/poi/hssf/model/LineShape.java b/src/java/org/apache/poi/hssf/model/LineShape.java index 2480d4260..e4517e4d7 100644 --- a/src/java/org/apache/poi/hssf/model/LineShape.java +++ b/src/java/org/apache/poi/hssf/model/LineShape.java @@ -27,6 +27,7 @@ import org.apache.poi.hssf.usermodel.*; * * @author Glen Stampoultzis (glens at apache.org) */ +@Deprecated public class LineShape extends AbstractShape { diff --git a/src/java/org/apache/poi/hssf/model/PolygonShape.java b/src/java/org/apache/poi/hssf/model/PolygonShape.java index 02308bf48..1543e2df8 100644 --- a/src/java/org/apache/poi/hssf/model/PolygonShape.java +++ b/src/java/org/apache/poi/hssf/model/PolygonShape.java @@ -27,6 +27,7 @@ import org.apache.poi.hssf.usermodel.HSSFShape; import org.apache.poi.hssf.usermodel.HSSFPolygon; import org.apache.poi.util.LittleEndian; +@Deprecated public class PolygonShape extends AbstractShape { @@ -63,7 +64,7 @@ public class PolygonShape spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); spContainer.setOptions( (short) 0x000F ); sp.setRecordId( EscherSpRecord.RECORD_ID ); - sp.setOptions( (short) ( ( EscherAggregate.ST_DONUT << 4 ) | 0x2 ) ); + sp.setOptions( (short) ( ( EscherAggregate.ST_NOT_PRIMATIVE << 4 ) | 0x2 ) ); sp.setShapeId( shapeId ); if (hssfShape.getParent() == null) sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); diff --git a/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java b/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java index 739e1ce32..1763b9d56 100644 --- a/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java +++ b/src/java/org/apache/poi/hssf/model/SimpleFilledShape.java @@ -25,6 +25,7 @@ import org.apache.poi.hssf.record.EndSubRecord; import org.apache.poi.hssf.usermodel.HSSFSimpleShape; import org.apache.poi.hssf.usermodel.HSSFShape; +@Deprecated public class SimpleFilledShape extends AbstractShape { diff --git a/src/java/org/apache/poi/hssf/model/TextboxShape.java b/src/java/org/apache/poi/hssf/model/TextboxShape.java index 1ea79d094..1b2a154a1 100644 --- a/src/java/org/apache/poi/hssf/model/TextboxShape.java +++ b/src/java/org/apache/poi/hssf/model/TextboxShape.java @@ -28,6 +28,7 @@ import org.apache.poi.hssf.usermodel.*; * * @author Glen Stampoultzis (glens at apache.org) */ +@Deprecated public class TextboxShape extends AbstractShape { diff --git a/src/java/org/apache/poi/hssf/record/DrawingRecord.java b/src/java/org/apache/poi/hssf/record/DrawingRecord.java index 9ba18aee0..fac9e715b 100644 --- a/src/java/org/apache/poi/hssf/record/DrawingRecord.java +++ b/src/java/org/apache/poi/hssf/record/DrawingRecord.java @@ -20,24 +20,24 @@ package org.apache.poi.hssf.record; import org.apache.poi.util.LittleEndianOutput; /** * DrawingRecord (0x00EC)

- * */ public final class DrawingRecord extends StandardRecord { public static final short sid = 0x00EC; - private static final byte[] EMPTY_BYTE_ARRAY = { }; + private static final byte[] EMPTY_BYTE_ARRAY = {}; private byte[] recordData; private byte[] contd; public DrawingRecord() { - recordData = EMPTY_BYTE_ARRAY; + recordData = EMPTY_BYTE_ARRAY; } public DrawingRecord(RecordInputStream in) { - recordData = in.readRemainder(); + recordData = in.readRemainder(); } + @Deprecated public void processContinueRecord(byte[] record) { //don't merge continue record with the drawing record, it must be serialized separately contd = record; @@ -46,6 +46,7 @@ public final class DrawingRecord extends StandardRecord { public void serialize(LittleEndianOutput out) { out.write(recordData); } + protected int getDataSize() { return recordData.length; } @@ -54,32 +55,39 @@ public final class DrawingRecord extends StandardRecord { return sid; } + @Deprecated public byte[] getData() { - if(contd != null) { - byte[] newBuffer = new byte[ recordData.length + contd.length ]; - System.arraycopy( recordData, 0, newBuffer, 0, recordData.length ); - System.arraycopy( contd, 0, newBuffer, recordData.length, contd.length); - return newBuffer; - } + return recordData; + } + + public byte[] getRecordData(){ return recordData; } public void setData(byte[] thedata) { - if (thedata == null) { - throw new IllegalArgumentException("data must not be null"); - } + if (thedata == null) { + throw new IllegalArgumentException("data must not be null"); + } recordData = thedata; } + /** + * Cloning of drawing records must be executed through HSSFPatriarch, because all id's must be changed + * @return cloned drawing records + */ public Object clone() { - DrawingRecord rec = new DrawingRecord(); - - rec.recordData = recordData.clone(); - if (contd != null) { - // TODO - this code probably never executes - rec.contd = contd.clone(); - } - - return rec; + DrawingRecord rec = new DrawingRecord(); + rec.recordData = recordData.clone(); + if (contd != null) { + // TODO - this code probably never executes + rec.contd = contd.clone(); + } + + return rec; } -} \ No newline at end of file + + @Override + public String toString() { + return "DrawingRecord["+recordData.length+"]"; + } +} diff --git a/src/java/org/apache/poi/hssf/record/EscherAggregate.java b/src/java/org/apache/poi/hssf/record/EscherAggregate.java index 6cbec1501..68937de15 100644 --- a/src/java/org/apache/poi/hssf/record/EscherAggregate.java +++ b/src/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -17,44 +17,22 @@ package org.apache.poi.hssf.record; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.*; import org.apache.poi.ddf.DefaultEscherRecordFactory; -import org.apache.poi.ddf.EscherBoolProperty; -import org.apache.poi.ddf.EscherChildAnchorRecord; -import org.apache.poi.ddf.EscherClientAnchorRecord; import org.apache.poi.ddf.EscherClientDataRecord; import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherDgRecord; -import org.apache.poi.ddf.EscherDggRecord; -import org.apache.poi.ddf.EscherOptRecord; -import org.apache.poi.ddf.EscherProperties; import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ddf.EscherRecordFactory; import org.apache.poi.ddf.EscherSerializationListener; -import org.apache.poi.ddf.EscherSimpleProperty; import org.apache.poi.ddf.EscherSpRecord; import org.apache.poi.ddf.EscherSpgrRecord; import org.apache.poi.ddf.EscherTextboxRecord; -import org.apache.poi.hssf.model.AbstractShape; -import org.apache.poi.hssf.model.CommentShape; -import org.apache.poi.hssf.model.ConvertAnchor; import org.apache.poi.hssf.model.DrawingManager2; -import org.apache.poi.hssf.model.TextboxShape; -import org.apache.poi.hssf.usermodel.HSSFAnchor; -import org.apache.poi.hssf.usermodel.HSSFChildAnchor; -import org.apache.poi.hssf.usermodel.HSSFClientAnchor; -import org.apache.poi.hssf.usermodel.HSSFPatriarch; -import org.apache.poi.hssf.usermodel.HSSFPicture; -import org.apache.poi.hssf.usermodel.HSSFShape; -import org.apache.poi.hssf.usermodel.HSSFShapeContainer; -import org.apache.poi.hssf.usermodel.HSSFShapeGroup; -import org.apache.poi.hssf.usermodel.HSSFSimpleShape; -import org.apache.poi.hssf.usermodel.HSSFTextbox; +import org.apache.poi.hssf.usermodel.*; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -65,975 +43,753 @@ import org.apache.poi.util.POILogger; * combination of MSODRAWING -> OBJ -> MSODRAWING -> OBJ records * but the escher records are serialized _across_ the MSODRAWING * records. - *

+ *

* It gets even worse when you start looking at TXO records. - *

+ *

* So what we do with this class is aggregate lazily. That is * we don't aggregate the MSODRAWING -> OBJ records unless we * need to modify them. - * + *

+ * At first document contains 4 types of records which belong to drawing layer. + * There are can be such sequence of record: + *

+ * DrawingRecord + * ContinueRecord + * ... + * ContinueRecord + * ObjRecord | TextObjectRecord + * ..... + * ContinueRecord + * ... + * ContinueRecord + * ObjRecord | TextObjectRecord + * NoteRecord + * ... + * NoteRecord + *

+ * To work with shapes we have to read data from Drawing and Continue records into single array of bytes and + * build escher(office art) records tree from this array. + * Each shape in drawing layer matches corresponding ObjRecord + * Each textbox matches corresponding TextObjectRecord + *

+ * ObjRecord contains information about shape. Thus each ObjRecord corresponds EscherContainerRecord(SPGR) + *

+ * EscherAggrefate contains also NoteRecords + * NoteRecords must be serial * * @author Glen Stampoultzis (glens at apache.org) */ + public final class EscherAggregate extends AbstractEscherHolderRecord { - public static final short sid = 9876; // not a real sid - dummy value - private static POILogger log = POILogFactory.getLogger(EscherAggregate.class); + public static final short sid = 9876; // not a real sid - dummy value + private static POILogger log = POILogFactory.getLogger(EscherAggregate.class); - public static final short ST_MIN = (short) 0; - public static final short ST_NOT_PRIMATIVE = ST_MIN; - public static final short ST_RECTANGLE = (short) 1; - public static final short ST_ROUNDRECTANGLE = (short) 2; - public static final short ST_ELLIPSE = (short) 3; - public static final short ST_DIAMOND = (short) 4; - public static final short ST_ISOCELESTRIANGLE = (short) 5; - public static final short ST_RIGHTTRIANGLE = (short) 6; - public static final short ST_PARALLELOGRAM = (short) 7; - public static final short ST_TRAPEZOID = (short) 8; - public static final short ST_HEXAGON = (short) 9; - public static final short ST_OCTAGON = (short) 10; - public static final short ST_PLUS = (short) 11; - public static final short ST_STAR = (short) 12; - public static final short ST_ARROW = (short) 13; - public static final short ST_THICKARROW = (short) 14; - public static final short ST_HOMEPLATE = (short) 15; - public static final short ST_CUBE = (short) 16; - public static final short ST_BALLOON = (short) 17; - public static final short ST_SEAL = (short) 18; - public static final short ST_ARC = (short) 19; - public static final short ST_LINE = (short) 20; - public static final short ST_PLAQUE = (short) 21; - public static final short ST_CAN = (short) 22; - public static final short ST_DONUT = (short) 23; - public static final short ST_TEXTSIMPLE = (short) 24; - public static final short ST_TEXTOCTAGON = (short) 25; - public static final short ST_TEXTHEXAGON = (short) 26; - public static final short ST_TEXTCURVE = (short) 27; - public static final short ST_TEXTWAVE = (short) 28; - public static final short ST_TEXTRING = (short) 29; - public static final short ST_TEXTONCURVE = (short) 30; - public static final short ST_TEXTONRING = (short) 31; - public static final short ST_STRAIGHTCONNECTOR1 = (short) 32; - public static final short ST_BENTCONNECTOR2 = (short) 33; - public static final short ST_BENTCONNECTOR3 = (short) 34; - public static final short ST_BENTCONNECTOR4 = (short) 35; - public static final short ST_BENTCONNECTOR5 = (short) 36; - public static final short ST_CURVEDCONNECTOR2 = (short) 37; - public static final short ST_CURVEDCONNECTOR3 = (short) 38; - public static final short ST_CURVEDCONNECTOR4 = (short) 39; - public static final short ST_CURVEDCONNECTOR5 = (short) 40; - public static final short ST_CALLOUT1 = (short) 41; - public static final short ST_CALLOUT2 = (short) 42; - public static final short ST_CALLOUT3 = (short) 43; - public static final short ST_ACCENTCALLOUT1 = (short) 44; - public static final short ST_ACCENTCALLOUT2 = (short) 45; - public static final short ST_ACCENTCALLOUT3 = (short) 46; - public static final short ST_BORDERCALLOUT1 = (short) 47; - public static final short ST_BORDERCALLOUT2 = (short) 48; - public static final short ST_BORDERCALLOUT3 = (short) 49; - public static final short ST_ACCENTBORDERCALLOUT1 = (short) 50; - public static final short ST_ACCENTBORDERCALLOUT2 = (short) 51; - public static final short ST_ACCENTBORDERCALLOUT3 = (short) 52; - public static final short ST_RIBBON = (short) 53; - public static final short ST_RIBBON2 = (short) 54; - public static final short ST_CHEVRON = (short) 55; - public static final short ST_PENTAGON = (short) 56; - public static final short ST_NOSMOKING = (short) 57; - public static final short ST_SEAL8 = (short) 58; - public static final short ST_SEAL16 = (short) 59; - public static final short ST_SEAL32 = (short) 60; - public static final short ST_WEDGERECTCALLOUT = (short) 61; - public static final short ST_WEDGERRECTCALLOUT = (short) 62; - public static final short ST_WEDGEELLIPSECALLOUT = (short) 63; - public static final short ST_WAVE = (short) 64; - public static final short ST_FOLDEDCORNER = (short) 65; - public static final short ST_LEFTARROW = (short) 66; - public static final short ST_DOWNARROW = (short) 67; - public static final short ST_UPARROW = (short) 68; - public static final short ST_LEFTRIGHTARROW = (short) 69; - public static final short ST_UPDOWNARROW = (short) 70; - public static final short ST_IRREGULARSEAL1 = (short) 71; - public static final short ST_IRREGULARSEAL2 = (short) 72; - public static final short ST_LIGHTNINGBOLT = (short) 73; - public static final short ST_HEART = (short) 74; - public static final short ST_PICTUREFRAME = (short) 75; - public static final short ST_QUADARROW = (short) 76; - public static final short ST_LEFTARROWCALLOUT = (short) 77; - public static final short ST_RIGHTARROWCALLOUT = (short) 78; - public static final short ST_UPARROWCALLOUT = (short) 79; - public static final short ST_DOWNARROWCALLOUT = (short) 80; - public static final short ST_LEFTRIGHTARROWCALLOUT = (short) 81; - public static final short ST_UPDOWNARROWCALLOUT = (short) 82; - public static final short ST_QUADARROWCALLOUT = (short) 83; - public static final short ST_BEVEL = (short) 84; - public static final short ST_LEFTBRACKET = (short) 85; - public static final short ST_RIGHTBRACKET = (short) 86; - public static final short ST_LEFTBRACE = (short) 87; - public static final short ST_RIGHTBRACE = (short) 88; - public static final short ST_LEFTUPARROW = (short) 89; - public static final short ST_BENTUPARROW = (short) 90; - public static final short ST_BENTARROW = (short) 91; - public static final short ST_SEAL24 = (short) 92; - public static final short ST_STRIPEDRIGHTARROW = (short) 93; - public static final short ST_NOTCHEDRIGHTARROW = (short) 94; - public static final short ST_BLOCKARC = (short) 95; - public static final short ST_SMILEYFACE = (short) 96; - public static final short ST_VERTICALSCROLL = (short) 97; - public static final short ST_HORIZONTALSCROLL = (short) 98; - public static final short ST_CIRCULARARROW = (short) 99; - public static final short ST_NOTCHEDCIRCULARARROW = (short) 100; - public static final short ST_UTURNARROW = (short) 101; - public static final short ST_CURVEDRIGHTARROW = (short) 102; - public static final short ST_CURVEDLEFTARROW = (short) 103; - public static final short ST_CURVEDUPARROW = (short) 104; - public static final short ST_CURVEDDOWNARROW = (short) 105; - public static final short ST_CLOUDCALLOUT = (short) 106; - public static final short ST_ELLIPSERIBBON = (short) 107; - public static final short ST_ELLIPSERIBBON2 = (short) 108; - public static final short ST_FLOWCHARTPROCESS = (short) 109; - public static final short ST_FLOWCHARTDECISION = (short) 110; - public static final short ST_FLOWCHARTINPUTOUTPUT = (short) 111; - public static final short ST_FLOWCHARTPREDEFINEDPROCESS = (short) 112; - public static final short ST_FLOWCHARTINTERNALSTORAGE = (short) 113; - public static final short ST_FLOWCHARTDOCUMENT = (short) 114; - public static final short ST_FLOWCHARTMULTIDOCUMENT = (short) 115; - public static final short ST_FLOWCHARTTERMINATOR = (short) 116; - public static final short ST_FLOWCHARTPREPARATION = (short) 117; - public static final short ST_FLOWCHARTMANUALINPUT = (short) 118; - public static final short ST_FLOWCHARTMANUALOPERATION = (short) 119; - public static final short ST_FLOWCHARTCONNECTOR = (short) 120; - public static final short ST_FLOWCHARTPUNCHEDCARD = (short) 121; - public static final short ST_FLOWCHARTPUNCHEDTAPE = (short) 122; - public static final short ST_FLOWCHARTSUMMINGJUNCTION = (short) 123; - public static final short ST_FLOWCHARTOR = (short) 124; - public static final short ST_FLOWCHARTCOLLATE = (short) 125; - public static final short ST_FLOWCHARTSORT = (short) 126; - public static final short ST_FLOWCHARTEXTRACT = (short) 127; - public static final short ST_FLOWCHARTMERGE = (short) 128; - public static final short ST_FLOWCHARTOFFLINESTORAGE = (short) 129; - public static final short ST_FLOWCHARTONLINESTORAGE = (short) 130; - public static final short ST_FLOWCHARTMAGNETICTAPE = (short) 131; - public static final short ST_FLOWCHARTMAGNETICDISK = (short) 132; - public static final short ST_FLOWCHARTMAGNETICDRUM = (short) 133; - public static final short ST_FLOWCHARTDISPLAY = (short) 134; - public static final short ST_FLOWCHARTDELAY = (short) 135; - public static final short ST_TEXTPLAINTEXT = (short) 136; - public static final short ST_TEXTSTOP = (short) 137; - public static final short ST_TEXTTRIANGLE = (short) 138; - public static final short ST_TEXTTRIANGLEINVERTED = (short) 139; - public static final short ST_TEXTCHEVRON = (short) 140; - public static final short ST_TEXTCHEVRONINVERTED = (short) 141; - public static final short ST_TEXTRINGINSIDE = (short) 142; - public static final short ST_TEXTRINGOUTSIDE = (short) 143; - public static final short ST_TEXTARCHUPCURVE = (short) 144; - public static final short ST_TEXTARCHDOWNCURVE = (short) 145; - public static final short ST_TEXTCIRCLECURVE = (short) 146; - public static final short ST_TEXTBUTTONCURVE = (short) 147; - public static final short ST_TEXTARCHUPPOUR = (short) 148; - public static final short ST_TEXTARCHDOWNPOUR = (short) 149; - public static final short ST_TEXTCIRCLEPOUR = (short) 150; - public static final short ST_TEXTBUTTONPOUR = (short) 151; - public static final short ST_TEXTCURVEUP = (short) 152; - public static final short ST_TEXTCURVEDOWN = (short) 153; - public static final short ST_TEXTCASCADEUP = (short) 154; - public static final short ST_TEXTCASCADEDOWN = (short) 155; - public static final short ST_TEXTWAVE1 = (short) 156; - public static final short ST_TEXTWAVE2 = (short) 157; - public static final short ST_TEXTWAVE3 = (short) 158; - public static final short ST_TEXTWAVE4 = (short) 159; - public static final short ST_TEXTINFLATE = (short) 160; - public static final short ST_TEXTDEFLATE = (short) 161; - public static final short ST_TEXTINFLATEBOTTOM = (short) 162; - public static final short ST_TEXTDEFLATEBOTTOM = (short) 163; - public static final short ST_TEXTINFLATETOP = (short) 164; - public static final short ST_TEXTDEFLATETOP = (short) 165; - public static final short ST_TEXTDEFLATEINFLATE = (short) 166; - public static final short ST_TEXTDEFLATEINFLATEDEFLATE = (short) 167; - public static final short ST_TEXTFADERIGHT = (short) 168; - public static final short ST_TEXTFADELEFT = (short) 169; - public static final short ST_TEXTFADEUP = (short) 170; - public static final short ST_TEXTFADEDOWN = (short) 171; - public static final short ST_TEXTSLANTUP = (short) 172; - public static final short ST_TEXTSLANTDOWN = (short) 173; - public static final short ST_TEXTCANUP = (short) 174; - public static final short ST_TEXTCANDOWN = (short) 175; - public static final short ST_FLOWCHARTALTERNATEPROCESS = (short) 176; - public static final short ST_FLOWCHARTOFFPAGECONNECTOR = (short) 177; - public static final short ST_CALLOUT90 = (short) 178; - public static final short ST_ACCENTCALLOUT90 = (short) 179; - public static final short ST_BORDERCALLOUT90 = (short) 180; - public static final short ST_ACCENTBORDERCALLOUT90 = (short) 181; - public static final short ST_LEFTRIGHTUPARROW = (short) 182; - public static final short ST_SUN = (short) 183; - public static final short ST_MOON = (short) 184; - public static final short ST_BRACKETPAIR = (short) 185; - public static final short ST_BRACEPAIR = (short) 186; - public static final short ST_SEAL4 = (short) 187; - public static final short ST_DOUBLEWAVE = (short) 188; - public static final short ST_ACTIONBUTTONBLANK = (short) 189; - public static final short ST_ACTIONBUTTONHOME = (short) 190; - public static final short ST_ACTIONBUTTONHELP = (short) 191; - public static final short ST_ACTIONBUTTONINFORMATION = (short) 192; - public static final short ST_ACTIONBUTTONFORWARDNEXT = (short) 193; - public static final short ST_ACTIONBUTTONBACKPREVIOUS = (short) 194; - public static final short ST_ACTIONBUTTONEND = (short) 195; - public static final short ST_ACTIONBUTTONBEGINNING = (short) 196; - public static final short ST_ACTIONBUTTONRETURN = (short) 197; - public static final short ST_ACTIONBUTTONDOCUMENT = (short) 198; - public static final short ST_ACTIONBUTTONSOUND = (short) 199; - public static final short ST_ACTIONBUTTONMOVIE = (short) 200; - public static final short ST_HOSTCONTROL = (short) 201; - public static final short ST_TEXTBOX = (short) 202; - public static final short ST_NIL = (short) 0x0FFF; + public static final short ST_MIN = (short) 0; + public static final short ST_NOT_PRIMATIVE = ST_MIN; + public static final short ST_RECTANGLE = (short) 1; + public static final short ST_ROUNDRECTANGLE = (short) 2; + public static final short ST_ELLIPSE = (short) 3; + public static final short ST_DIAMOND = (short) 4; + public static final short ST_ISOCELESTRIANGLE = (short) 5; + public static final short ST_RIGHTTRIANGLE = (short) 6; + public static final short ST_PARALLELOGRAM = (short) 7; + public static final short ST_TRAPEZOID = (short) 8; + public static final short ST_HEXAGON = (short) 9; + public static final short ST_OCTAGON = (short) 10; + public static final short ST_PLUS = (short) 11; + public static final short ST_STAR = (short) 12; + public static final short ST_ARROW = (short) 13; + public static final short ST_THICKARROW = (short) 14; + public static final short ST_HOMEPLATE = (short) 15; + public static final short ST_CUBE = (short) 16; + public static final short ST_BALLOON = (short) 17; + public static final short ST_SEAL = (short) 18; + public static final short ST_ARC = (short) 19; + public static final short ST_LINE = (short) 20; + public static final short ST_PLAQUE = (short) 21; + public static final short ST_CAN = (short) 22; + public static final short ST_DONUT = (short) 23; + public static final short ST_TEXTSIMPLE = (short) 24; + public static final short ST_TEXTOCTAGON = (short) 25; + public static final short ST_TEXTHEXAGON = (short) 26; + public static final short ST_TEXTCURVE = (short) 27; + public static final short ST_TEXTWAVE = (short) 28; + public static final short ST_TEXTRING = (short) 29; + public static final short ST_TEXTONCURVE = (short) 30; + public static final short ST_TEXTONRING = (short) 31; + public static final short ST_STRAIGHTCONNECTOR1 = (short) 32; + public static final short ST_BENTCONNECTOR2 = (short) 33; + public static final short ST_BENTCONNECTOR3 = (short) 34; + public static final short ST_BENTCONNECTOR4 = (short) 35; + public static final short ST_BENTCONNECTOR5 = (short) 36; + public static final short ST_CURVEDCONNECTOR2 = (short) 37; + public static final short ST_CURVEDCONNECTOR3 = (short) 38; + public static final short ST_CURVEDCONNECTOR4 = (short) 39; + public static final short ST_CURVEDCONNECTOR5 = (short) 40; + public static final short ST_CALLOUT1 = (short) 41; + public static final short ST_CALLOUT2 = (short) 42; + public static final short ST_CALLOUT3 = (short) 43; + public static final short ST_ACCENTCALLOUT1 = (short) 44; + public static final short ST_ACCENTCALLOUT2 = (short) 45; + public static final short ST_ACCENTCALLOUT3 = (short) 46; + public static final short ST_BORDERCALLOUT1 = (short) 47; + public static final short ST_BORDERCALLOUT2 = (short) 48; + public static final short ST_BORDERCALLOUT3 = (short) 49; + public static final short ST_ACCENTBORDERCALLOUT1 = (short) 50; + public static final short ST_ACCENTBORDERCALLOUT2 = (short) 51; + public static final short ST_ACCENTBORDERCALLOUT3 = (short) 52; + public static final short ST_RIBBON = (short) 53; + public static final short ST_RIBBON2 = (short) 54; + public static final short ST_CHEVRON = (short) 55; + public static final short ST_PENTAGON = (short) 56; + public static final short ST_NOSMOKING = (short) 57; + public static final short ST_SEAL8 = (short) 58; + public static final short ST_SEAL16 = (short) 59; + public static final short ST_SEAL32 = (short) 60; + public static final short ST_WEDGERECTCALLOUT = (short) 61; + public static final short ST_WEDGERRECTCALLOUT = (short) 62; + public static final short ST_WEDGEELLIPSECALLOUT = (short) 63; + public static final short ST_WAVE = (short) 64; + public static final short ST_FOLDEDCORNER = (short) 65; + public static final short ST_LEFTARROW = (short) 66; + public static final short ST_DOWNARROW = (short) 67; + public static final short ST_UPARROW = (short) 68; + public static final short ST_LEFTRIGHTARROW = (short) 69; + public static final short ST_UPDOWNARROW = (short) 70; + public static final short ST_IRREGULARSEAL1 = (short) 71; + public static final short ST_IRREGULARSEAL2 = (short) 72; + public static final short ST_LIGHTNINGBOLT = (short) 73; + public static final short ST_HEART = (short) 74; + public static final short ST_PICTUREFRAME = (short) 75; + public static final short ST_QUADARROW = (short) 76; + public static final short ST_LEFTARROWCALLOUT = (short) 77; + public static final short ST_RIGHTARROWCALLOUT = (short) 78; + public static final short ST_UPARROWCALLOUT = (short) 79; + public static final short ST_DOWNARROWCALLOUT = (short) 80; + public static final short ST_LEFTRIGHTARROWCALLOUT = (short) 81; + public static final short ST_UPDOWNARROWCALLOUT = (short) 82; + public static final short ST_QUADARROWCALLOUT = (short) 83; + public static final short ST_BEVEL = (short) 84; + public static final short ST_LEFTBRACKET = (short) 85; + public static final short ST_RIGHTBRACKET = (short) 86; + public static final short ST_LEFTBRACE = (short) 87; + public static final short ST_RIGHTBRACE = (short) 88; + public static final short ST_LEFTUPARROW = (short) 89; + public static final short ST_BENTUPARROW = (short) 90; + public static final short ST_BENTARROW = (short) 91; + public static final short ST_SEAL24 = (short) 92; + public static final short ST_STRIPEDRIGHTARROW = (short) 93; + public static final short ST_NOTCHEDRIGHTARROW = (short) 94; + public static final short ST_BLOCKARC = (short) 95; + public static final short ST_SMILEYFACE = (short) 96; + public static final short ST_VERTICALSCROLL = (short) 97; + public static final short ST_HORIZONTALSCROLL = (short) 98; + public static final short ST_CIRCULARARROW = (short) 99; + public static final short ST_NOTCHEDCIRCULARARROW = (short) 100; + public static final short ST_UTURNARROW = (short) 101; + public static final short ST_CURVEDRIGHTARROW = (short) 102; + public static final short ST_CURVEDLEFTARROW = (short) 103; + public static final short ST_CURVEDUPARROW = (short) 104; + public static final short ST_CURVEDDOWNARROW = (short) 105; + public static final short ST_CLOUDCALLOUT = (short) 106; + public static final short ST_ELLIPSERIBBON = (short) 107; + public static final short ST_ELLIPSERIBBON2 = (short) 108; + public static final short ST_FLOWCHARTPROCESS = (short) 109; + public static final short ST_FLOWCHARTDECISION = (short) 110; + public static final short ST_FLOWCHARTINPUTOUTPUT = (short) 111; + public static final short ST_FLOWCHARTPREDEFINEDPROCESS = (short) 112; + public static final short ST_FLOWCHARTINTERNALSTORAGE = (short) 113; + public static final short ST_FLOWCHARTDOCUMENT = (short) 114; + public static final short ST_FLOWCHARTMULTIDOCUMENT = (short) 115; + public static final short ST_FLOWCHARTTERMINATOR = (short) 116; + public static final short ST_FLOWCHARTPREPARATION = (short) 117; + public static final short ST_FLOWCHARTMANUALINPUT = (short) 118; + public static final short ST_FLOWCHARTMANUALOPERATION = (short) 119; + public static final short ST_FLOWCHARTCONNECTOR = (short) 120; + public static final short ST_FLOWCHARTPUNCHEDCARD = (short) 121; + public static final short ST_FLOWCHARTPUNCHEDTAPE = (short) 122; + public static final short ST_FLOWCHARTSUMMINGJUNCTION = (short) 123; + public static final short ST_FLOWCHARTOR = (short) 124; + public static final short ST_FLOWCHARTCOLLATE = (short) 125; + public static final short ST_FLOWCHARTSORT = (short) 126; + public static final short ST_FLOWCHARTEXTRACT = (short) 127; + public static final short ST_FLOWCHARTMERGE = (short) 128; + public static final short ST_FLOWCHARTOFFLINESTORAGE = (short) 129; + public static final short ST_FLOWCHARTONLINESTORAGE = (short) 130; + public static final short ST_FLOWCHARTMAGNETICTAPE = (short) 131; + public static final short ST_FLOWCHARTMAGNETICDISK = (short) 132; + public static final short ST_FLOWCHARTMAGNETICDRUM = (short) 133; + public static final short ST_FLOWCHARTDISPLAY = (short) 134; + public static final short ST_FLOWCHARTDELAY = (short) 135; + public static final short ST_TEXTPLAINTEXT = (short) 136; + public static final short ST_TEXTSTOP = (short) 137; + public static final short ST_TEXTTRIANGLE = (short) 138; + public static final short ST_TEXTTRIANGLEINVERTED = (short) 139; + public static final short ST_TEXTCHEVRON = (short) 140; + public static final short ST_TEXTCHEVRONINVERTED = (short) 141; + public static final short ST_TEXTRINGINSIDE = (short) 142; + public static final short ST_TEXTRINGOUTSIDE = (short) 143; + public static final short ST_TEXTARCHUPCURVE = (short) 144; + public static final short ST_TEXTARCHDOWNCURVE = (short) 145; + public static final short ST_TEXTCIRCLECURVE = (short) 146; + public static final short ST_TEXTBUTTONCURVE = (short) 147; + public static final short ST_TEXTARCHUPPOUR = (short) 148; + public static final short ST_TEXTARCHDOWNPOUR = (short) 149; + public static final short ST_TEXTCIRCLEPOUR = (short) 150; + public static final short ST_TEXTBUTTONPOUR = (short) 151; + public static final short ST_TEXTCURVEUP = (short) 152; + public static final short ST_TEXTCURVEDOWN = (short) 153; + public static final short ST_TEXTCASCADEUP = (short) 154; + public static final short ST_TEXTCASCADEDOWN = (short) 155; + public static final short ST_TEXTWAVE1 = (short) 156; + public static final short ST_TEXTWAVE2 = (short) 157; + public static final short ST_TEXTWAVE3 = (short) 158; + public static final short ST_TEXTWAVE4 = (short) 159; + public static final short ST_TEXTINFLATE = (short) 160; + public static final short ST_TEXTDEFLATE = (short) 161; + public static final short ST_TEXTINFLATEBOTTOM = (short) 162; + public static final short ST_TEXTDEFLATEBOTTOM = (short) 163; + public static final short ST_TEXTINFLATETOP = (short) 164; + public static final short ST_TEXTDEFLATETOP = (short) 165; + public static final short ST_TEXTDEFLATEINFLATE = (short) 166; + public static final short ST_TEXTDEFLATEINFLATEDEFLATE = (short) 167; + public static final short ST_TEXTFADERIGHT = (short) 168; + public static final short ST_TEXTFADELEFT = (short) 169; + public static final short ST_TEXTFADEUP = (short) 170; + public static final short ST_TEXTFADEDOWN = (short) 171; + public static final short ST_TEXTSLANTUP = (short) 172; + public static final short ST_TEXTSLANTDOWN = (short) 173; + public static final short ST_TEXTCANUP = (short) 174; + public static final short ST_TEXTCANDOWN = (short) 175; + public static final short ST_FLOWCHARTALTERNATEPROCESS = (short) 176; + public static final short ST_FLOWCHARTOFFPAGECONNECTOR = (short) 177; + public static final short ST_CALLOUT90 = (short) 178; + public static final short ST_ACCENTCALLOUT90 = (short) 179; + public static final short ST_BORDERCALLOUT90 = (short) 180; + public static final short ST_ACCENTBORDERCALLOUT90 = (short) 181; + public static final short ST_LEFTRIGHTUPARROW = (short) 182; + public static final short ST_SUN = (short) 183; + public static final short ST_MOON = (short) 184; + public static final short ST_BRACKETPAIR = (short) 185; + public static final short ST_BRACEPAIR = (short) 186; + public static final short ST_SEAL4 = (short) 187; + public static final short ST_DOUBLEWAVE = (short) 188; + public static final short ST_ACTIONBUTTONBLANK = (short) 189; + public static final short ST_ACTIONBUTTONHOME = (short) 190; + public static final short ST_ACTIONBUTTONHELP = (short) 191; + public static final short ST_ACTIONBUTTONINFORMATION = (short) 192; + public static final short ST_ACTIONBUTTONFORWARDNEXT = (short) 193; + public static final short ST_ACTIONBUTTONBACKPREVIOUS = (short) 194; + public static final short ST_ACTIONBUTTONEND = (short) 195; + public static final short ST_ACTIONBUTTONBEGINNING = (short) 196; + public static final short ST_ACTIONBUTTONRETURN = (short) 197; + public static final short ST_ACTIONBUTTONDOCUMENT = (short) 198; + public static final short ST_ACTIONBUTTONSOUND = (short) 199; + public static final short ST_ACTIONBUTTONMOVIE = (short) 200; + public static final short ST_HOSTCONTROL = (short) 201; + public static final short ST_TEXTBOX = (short) 202; + public static final short ST_NIL = (short) 0x0FFF; - protected HSSFPatriarch patriarch; + /** + * if we want to get the same byte array if we open existing file and serialize it we should save + * note records in right order. This list contains ids of NoteRecords in such order as we read from existing file + */ + private List _tailIds = new ArrayList(); - /** Maps shape container objects to their {@link TextObjectRecord} or {@link ObjRecord} */ - private Map shapeToObj = new HashMap(); - private DrawingManager2 drawingManager; - private short drawingGroupId; + /** + * Maps shape container objects to their {@link TextObjectRecord} or {@link ObjRecord} + */ + private final Map shapeToObj = new HashMap(); - /** - * list of "tail" records that need to be serialized after all drawing group records - */ - private List tailRec = new ArrayList(); + /** + * list of "tail" records that need to be serialized after all drawing group records + */ + private Map tailRec = new HashMap(); - public EscherAggregate( DrawingManager2 drawingManager ) - { - this.drawingManager = drawingManager; - } + public EscherAggregate() { + buildBaseTree(); + } - /** - * @return Returns the current sid. - */ - public short getSid() - { - return sid; - } + public EscherAggregate(DrawingManager2 drawingManager) { + } - /** - * Calculates the string representation of this record. This is - * simply a dump of all the records. - */ - public String toString() - { - String nl = System.getProperty( "line.separtor" ); + /** + * @return Returns the current sid. + */ + public short getSid() { + return sid; + } - StringBuffer result = new StringBuffer(); - result.append( '[' ).append( getRecordName() ).append( ']' + nl ); - for ( Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); ) - { - EscherRecord escherRecord = (EscherRecord) iterator.next(); - result.append( escherRecord.toString() ); - } - result.append( "[/" ).append( getRecordName() ).append( ']' + nl ); + /** + * Calculates the string representation of this record. This is + * simply a dump of all the records. + */ + public String toString() { + String nl = System.getProperty("line.separtor"); - return result.toString(); - } - - public String toXml(String tab){ + StringBuffer result = new StringBuffer(); + result.append('[').append(getRecordName()).append(']' + nl); + for (Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); ) { + EscherRecord escherRecord = (EscherRecord) iterator.next(); + result.append(escherRecord.toString()); + } + result.append("[/").append(getRecordName()).append(']' + nl); + + return result.toString(); + } + + /** + * Calculates the xml representation of this record. This is + * simply a dump of all the records. + */ + public String toXml(String tab) { StringBuilder builder = new StringBuilder(); builder.append(tab).append("<").append(getRecordName()).append(">\n"); - for ( Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); ) - { + for (Iterator iterator = getEscherRecords().iterator(); iterator.hasNext(); ) { EscherRecord escherRecord = (EscherRecord) iterator.next(); - builder.append( escherRecord.toXml(tab+"\t") ); + builder.append(escherRecord.toXml(tab + "\t")); } builder.append(tab).append("\n"); return builder.toString(); } - /** - * Collapses the drawing records into an aggregate. - */ - public static EscherAggregate createAggregate( List records, int locFirstDrawingRecord, DrawingManager2 drawingManager ) - { - // Keep track of any shape records created so we can match them back to the object id's. - // Textbox objects are also treated as shape objects. - final List shapeRecords = new ArrayList(); - EscherRecordFactory recordFactory = new DefaultEscherRecordFactory() - { - public EscherRecord createRecord( byte[] data, int offset ) - { - EscherRecord r = super.createRecord( data, offset ); - if ( r.getRecordId() == EscherClientDataRecord.RECORD_ID || r.getRecordId() == EscherTextboxRecord.RECORD_ID ) - { - shapeRecords.add( r ); - } - return r; - } - }; - - // Calculate the size of the buffer - EscherAggregate agg = new EscherAggregate(drawingManager); - int loc = locFirstDrawingRecord; - int dataSize = 0; - while ( loc + 1 < records.size() - && sid( records, loc ) == DrawingRecord.sid - && isObjectRecord( records, loc + 1 ) ) - { - dataSize += ( (DrawingRecord) records.get( loc ) ).getData().length; - loc += 2; - } - - // Create one big buffer - byte buffer[] = new byte[dataSize]; - int offset = 0; - loc = locFirstDrawingRecord; - while ( loc + 1 < records.size() - && sid( records, loc ) == DrawingRecord.sid - && isObjectRecord( records, loc + 1 ) ) - { - DrawingRecord drawingRecord = (DrawingRecord) records.get( loc ); - System.arraycopy( drawingRecord.getData(), 0, buffer, offset, drawingRecord.getData().length ); - offset += drawingRecord.getData().length; - loc += 2; - } - - // Decode the shapes - // agg.escherRecords = new ArrayList(); - int pos = 0; - while ( pos < dataSize ) - { - EscherRecord r = recordFactory.createRecord( buffer, pos ); - int bytesRead = r.fillFields( buffer, pos, recordFactory ); - agg.addEscherRecord( r ); - pos += bytesRead; - } - - // Associate the object records with the shapes - loc = locFirstDrawingRecord; - int shapeIndex = 0; - agg.shapeToObj = new HashMap(); - while ( loc + 1 < records.size() - && sid( records, loc ) == DrawingRecord.sid - && isObjectRecord( records, loc + 1 ) ) - { - Record objRecord = (Record) records.get( loc + 1 ); - agg.shapeToObj.put( shapeRecords.get( shapeIndex++ ), objRecord ); - loc += 2; - } - - return agg; - - } - - /** - * Serializes this aggregate to a byte array. Since this is an aggregate - * record it will effectively serialize the aggregated records. - * - * @param offset The offset into the start of the array. - * @param data The byte array to serialize to. - * @return The number of bytes serialized. - */ - public int serialize( int offset, byte[] data ) - { - convertUserModelToRecords(); - - // Determine buffer size - List records = getEscherRecords(); - int size = getEscherRecordSize( records ); - byte[] buffer = new byte[size]; - - - // Serialize escher records into one big data structure and keep note of ending offsets. - final List spEndingOffsets = new ArrayList(); - final List shapes = new ArrayList(); - int pos = 0; - for ( Iterator iterator = records.iterator(); iterator.hasNext(); ) - { - EscherRecord e = (EscherRecord) iterator.next(); - pos += e.serialize( pos, buffer, new EscherSerializationListener() - { - public void beforeRecordSerialize( int offset, short recordId, EscherRecord record ) - { - } - - public void afterRecordSerialize( int offset, short recordId, int size, EscherRecord record ) - { - if ( recordId == EscherClientDataRecord.RECORD_ID || recordId == EscherTextboxRecord.RECORD_ID ) - { - spEndingOffsets.add( Integer.valueOf( offset ) ); - shapes.add( record ); - } - } - } ); - } - // todo: fix this - shapes.add( 0, null ); - spEndingOffsets.add( 0, null ); - - // Split escher records into separate MSODRAWING and OBJ, TXO records. (We don't break on - // the first one because it's the patriach). - pos = offset; - for ( int i = 1; i < shapes.size(); i++ ) - { - int endOffset = ( (Integer) spEndingOffsets.get( i ) ).intValue() - 1; - int startOffset; - if ( i == 1 ) - startOffset = 0; - else - startOffset = ( (Integer) spEndingOffsets.get( i - 1 ) ).intValue(); - - // Create and write a new MSODRAWING record - DrawingRecord drawing = new DrawingRecord(); - byte[] drawingData = new byte[endOffset - startOffset + 1]; - System.arraycopy( buffer, startOffset, drawingData, 0, drawingData.length ); - drawing.setData( drawingData ); - int temp = drawing.serialize( pos, data ); - pos += temp; - - // Write the matching OBJ record - Record obj = shapeToObj.get( shapes.get( i ) ); - temp = obj.serialize( pos, data ); - pos += temp; - - } - - // 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() ); - return bytesWritten; - } - - /** - * How many bytes do the raw escher records contain. - * @param records List of escher records - * @return the number of bytes - */ - private int getEscherRecordSize( List records ) - { - int size = 0; - for ( Iterator iterator = records.iterator(); iterator.hasNext(); ) - size += ( (EscherRecord) iterator.next() ).getRecordSize(); - return size; - } - - public int getRecordSize() { - // TODO - convert this to RecordAggregate - convertUserModelToRecords(); - List records = getEscherRecords(); - int rawEscherSize = getEscherRecordSize( records ); - int drawingRecordSize = rawEscherSize + ( shapeToObj.size() ) * 4; - int objRecordSize = 0; - for ( Iterator iterator = shapeToObj.values().iterator(); iterator.hasNext(); ) - { - Record r = (Record) iterator.next(); - objRecordSize += r.getRecordSize(); - } - int tailRecordSize = 0; - for ( Iterator iterator = tailRec.iterator(); iterator.hasNext(); ) - { - Record r = (Record) iterator.next(); - tailRecordSize += r.getRecordSize(); - } - return drawingRecordSize + objRecordSize + tailRecordSize; - } - - /** - * Associates an escher record to an OBJ record or a TXO record. - */ - Object associateShapeToObjRecord( EscherRecord r, ObjRecord objRecord ) - { - return shapeToObj.put( r, objRecord ); - } - - public HSSFPatriarch getPatriarch() - { - return patriarch; - } - - public void setPatriarch( HSSFPatriarch patriarch ) - { - this.patriarch = patriarch; - } - - /** - * Converts the Records into UserModel - * objects on the bound HSSFPatriarch - */ - public void convertRecordsToUserModel() { - if(patriarch == null) { - throw new IllegalStateException("Must call setPatriarch() first"); - } - - // The top level container ought to have - // the DgRecord and the container of one container - // per shape group (patriach overall first) - EscherContainerRecord topContainer = getEscherContainer(); - if(topContainer == null) { - return; - } - topContainer = topContainer.getChildContainers().get(0); - - List tcc = topContainer.getChildContainers(); - if(tcc.size() == 0) { - throw new IllegalStateException("No child escher containers at the point that should hold the patriach data, and one container per top level shape!"); - } - - // First up, get the patriach position - // This is in the first EscherSpgrRecord, in - // the first container, with a EscherSRecord too - EscherContainerRecord patriachContainer = - (EscherContainerRecord)tcc.get(0); - EscherSpgrRecord spgr = null; - for(Iterator it = patriachContainer.getChildIterator(); it.hasNext();) { - EscherRecord r = it.next(); - if(r instanceof EscherSpgrRecord) { - spgr = (EscherSpgrRecord)r; - break; - } - } - if(spgr != null) { - patriarch.setCoordinates( - spgr.getRectX1(), spgr.getRectY1(), - spgr.getRectX2(), spgr.getRectY2() - ); - } - - convertRecordsToUserModelRecursive(tcc, patriarch, null); - - // Now, clear any trace of what records make up - // the patriarch - // Otherwise, everything will go horribly wrong - // when we try to write out again.... -// clearEscherRecords(); - drawingManager.getDgg().setFileIdClusters(new EscherDggRecord.FileIdCluster[0]); - - // TODO: Support converting our records - // back into shapes - // log.log(POILogger.WARN, "Not processing objects into Patriarch!"); - } - - private static void convertRecordsToUserModelRecursive(List tcc, HSSFShapeContainer container, HSSFShape parent) { - // Now process the containers for each group - // and objects - for(int i=1; i 0) - { - HSSFShapeGroup group = new HSSFShapeGroup( parent, new HSSFClientAnchor() ); - addToParentOrContainer(group, container, parent); - - EscherContainerRecord groupContainer = (EscherContainerRecord) shapeContainer.getChild( 0 ); - convertRecordsToUserModel( groupContainer, group ); - - if (shapeChildren>1){ - convertRecordsToUserModelRecursive(shapeContainer.getChildRecords(), container, group); - } - } else - { - log.log( POILogger.WARN, - "Found drawing group without children." ); - } - - } else if (shapeContainer.getRecordId() == EscherContainerRecord.SP_CONTAINER) - { - EscherSpRecord spRecord = shapeContainer - .getChildById( EscherSpRecord.RECORD_ID ); - int type = spRecord.getShapeType(); - - switch (type) - { - case ST_TEXTBOX: - HSSFTextbox box = new HSSFTextbox( parent, - new HSSFClientAnchor() ); - addToParentOrContainer(box, container, parent); - - convertRecordsToUserModel( shapeContainer, box ); - break; - case ST_PICTUREFRAME: - // Duplicated from - // org.apache.poi.hslf.model.Picture.getPictureIndex() - EscherOptRecord opt = (EscherOptRecord) getEscherChild( - shapeContainer, EscherOptRecord.RECORD_ID ); - EscherSimpleProperty prop = (EscherSimpleProperty)opt.lookup( - EscherProperties.BLIP__BLIPTODISPLAY ); - if (prop == null) - { - log.log( POILogger.WARN, - "Picture index for picture shape not found." ); - } else - { - int pictureIndex = prop.getPropertyValue(); - - EscherClientAnchorRecord anchorRecord = (EscherClientAnchorRecord) getEscherChild( - shapeContainer, - EscherClientAnchorRecord.RECORD_ID ); - - EscherChildAnchorRecord childRecord = (EscherChildAnchorRecord) getEscherChild( - shapeContainer, - EscherChildAnchorRecord.RECORD_ID ); - - if (anchorRecord!=null && childRecord!=null){ - log.log( POILogger.WARN, "Picture with both CLIENT and CHILD anchor: "+ type ); - } - - HSSFAnchor anchor; - if (anchorRecord!=null){ - anchor = toClientAnchor(anchorRecord); - }else{ - anchor = toChildAnchor(childRecord); - } - - HSSFPicture picture = new HSSFPicture( parent, anchor ); - picture.setPictureIndex( pictureIndex ); - - addToParentOrContainer(picture, container, parent); - } - break; - default: - final HSSFSimpleShape shape = new HSSFSimpleShape( parent, - new HSSFClientAnchor() ); - addToParentOrContainer(shape, container, parent); - convertRecordsToUserModel( shapeContainer, shape); - - log.log( POILogger.WARN, "Unhandled shape type: " - + type ); - break; - } - } else - { - log.log( POILogger.WARN, "Unexpected record id of shape group." ); - } - - } - } - - private static void addToParentOrContainer(HSSFShape shape, HSSFShapeContainer container, HSSFShape parent) { - - if (parent instanceof HSSFShapeGroup) - ((HSSFShapeGroup) parent).addShape(shape); - else if (container instanceof HSSFPatriarch) - ((HSSFPatriarch) container).addShape(shape); - else - container.getChildren().add(shape); - } - - private static HSSFClientAnchor toClientAnchor(EscherClientAnchorRecord anchorRecord){ - HSSFClientAnchor anchor = new HSSFClientAnchor(); - anchor.setAnchorType(anchorRecord.getFlag()); - anchor.setCol1( anchorRecord.getCol1() ); - anchor.setCol2( anchorRecord.getCol2() ); - anchor.setDx1( anchorRecord.getDx1() ); - anchor.setDx2( anchorRecord.getDx2() ); - anchor.setDy1( anchorRecord.getDy1() ); - anchor.setDy2( anchorRecord.getDy2() ); - anchor.setRow1( anchorRecord.getRow1() ); - anchor.setRow2( anchorRecord.getRow2() ); - return anchor; + private static boolean isDrawingLayerRecord(final short sid) { + return sid == DrawingRecord.sid || + sid == ContinueRecord.sid || + sid == ObjRecord.sid || + sid == TextObjectRecord.sid; } - private static HSSFChildAnchor toChildAnchor(EscherChildAnchorRecord anchorRecord){ - HSSFChildAnchor anchor = new HSSFChildAnchor(); -// anchor.setAnchorType(anchorRecord.getFlag()); -// anchor.setCol1( anchorRecord.getCol1() ); -// anchor.setCol2( anchorRecord.getCol2() ); - anchor.setDx1( anchorRecord.getDx1() ); - anchor.setDx2( anchorRecord.getDx2() ); - anchor.setDy1( anchorRecord.getDy1() ); - anchor.setDy2( anchorRecord.getDy2() ); -// anchor.setRow1( anchorRecord.getRow1() ); -// anchor.setRow2( anchorRecord.getRow2() ); - return anchor; + /** + * Collapses the drawing records into an aggregate. + * read Drawing, Obj, TxtObj, Note and Continue records into single byte array, + * create Escher tree from byte array, create map + * + */ + public static EscherAggregate createAggregate(List records, int locFirstDrawingRecord, DrawingManager2 drawingManager) { + // Keep track of any shape records created so we can match them back to the object id's. + // Textbox objects are also treated as shape objects. + final List shapeRecords = new ArrayList(); + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory() { + public EscherRecord createRecord(byte[] data, int offset) { + EscherRecord r = super.createRecord(data, offset); + if (r.getRecordId() == EscherClientDataRecord.RECORD_ID || r.getRecordId() == EscherTextboxRecord.RECORD_ID) { + shapeRecords.add(r); + } + return r; + } + }; + + // Create one big buffer + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + EscherAggregate agg = new EscherAggregate(drawingManager); + int loc = locFirstDrawingRecord; + while (loc + 1 < records.size() + && (isDrawingLayerRecord(sid(records, loc)))) { + try { + if (!(sid(records, loc) == DrawingRecord.sid || sid(records, loc) == ContinueRecord.sid)) { + loc++; + continue; + } + if (sid(records, loc) == DrawingRecord.sid) { + buffer.write(((DrawingRecord) records.get(loc)).getRecordData()); + } else { + buffer.write(((ContinueRecord) records.get(loc)).getData()); + } + } catch (IOException e) { + throw new RuntimeException("Couldn't get data from drawing/continue records", e); + } + loc++; + } + + // Decode the shapes + // agg.escherRecords = new ArrayList(); + int pos = 0; + while (pos < buffer.size()) { + EscherRecord r = recordFactory.createRecord(buffer.toByteArray(), pos); + int bytesRead = r.fillFields(buffer.toByteArray(), pos, recordFactory); + agg.addEscherRecord(r); + pos += bytesRead; + } + + // Associate the object records with the shapes + loc = locFirstDrawingRecord + 1; + int shapeIndex = 0; + while (loc < records.size() + && (isDrawingLayerRecord(sid(records, loc)))) { + if (!isObjectRecord(records, loc)) { + loc++; + continue; + } + Record objRecord = (Record) records.get(loc); + agg.shapeToObj.put(shapeRecords.get(shapeIndex++), objRecord); + loc++; + } + + // any NoteRecords that follow the drawing block must be aggregated and and saved in the tailRec collection + // TODO remove this logic. 'tail' records should be inserted in the main record stream + while (loc < records.size()) { + if (sid(records, loc) == NoteRecord.sid) { + NoteRecord r = (NoteRecord) records.get(loc); + agg.tailRec.put(r.getShapeId(), r); + agg._tailIds.add(agg._tailIds.size(), r.getShapeId()); + } else { + break; + } + loc++; + } + + int locLastDrawingRecord = loc; + // replace drawing block with the created EscherAggregate + records.subList(locFirstDrawingRecord, locLastDrawingRecord).clear(); + records.add(locFirstDrawingRecord, agg); + return agg; } - private static void convertRecordsToUserModel(EscherContainerRecord shapeContainer, Object model) { - for(Iterator it = shapeContainer.getChildIterator(); it.hasNext();) { - EscherRecord r = it.next(); - if(r instanceof EscherSpgrRecord) { - // This may be overriden by a later EscherClientAnchorRecord - EscherSpgrRecord spgr = (EscherSpgrRecord)r; + /** + * Serializes this aggregate to a byte array. Since this is an aggregate + * record it will effectively serialize the aggregated records. + * + * @param offset The offset into the start of the array. + * @param data The byte array to serialize to. + * @return The number of bytes serialized. + */ + public int serialize(int offset, byte[] data) { + // Determine buffer size + List records = getEscherRecords(); + int size = getEscherRecordSize(records); + byte[] buffer = new byte[size]; - if(model instanceof HSSFShapeGroup) { - HSSFShapeGroup g = (HSSFShapeGroup)model; - g.setCoordinates( - spgr.getRectX1(), spgr.getRectY1(), - spgr.getRectX2(), spgr.getRectY2() - ); - } else { - throw new IllegalStateException("Got top level anchor but not processing a group"); - } - } - else if(r instanceof EscherClientAnchorRecord) { - EscherClientAnchorRecord car = (EscherClientAnchorRecord)r; + // Serialize escher records into one big data structure and keep note of ending offsets. + final List spEndingOffsets = new ArrayList(); + final List shapes = new ArrayList(); + int pos = 0; + for (Iterator iterator = records.iterator(); iterator.hasNext(); ) { + EscherRecord e = (EscherRecord) iterator.next(); + pos += e.serialize(pos, buffer, new EscherSerializationListener() { + public void beforeRecordSerialize(int offset, short recordId, EscherRecord record) { + } - if(model instanceof HSSFShape) { - HSSFShape g = (HSSFShape)model; - g.getAnchor().setDx1(car.getDx1()); - g.getAnchor().setDx2(car.getDx2()); - g.getAnchor().setDy1(car.getDy1()); - g.getAnchor().setDy2(car.getDy2()); - } else { - throw new IllegalStateException("Got top level anchor but not processing a group or shape"); - } - } - else if(r instanceof EscherTextboxRecord) { - EscherTextboxRecord tbr = (EscherTextboxRecord)r; + public void afterRecordSerialize(int offset, short recordId, int size, EscherRecord record) { + if (recordId == EscherClientDataRecord.RECORD_ID || recordId == EscherTextboxRecord.RECORD_ID) { + spEndingOffsets.add(offset); + shapes.add(record); + } + } + }); + } + // todo: fix this + shapes.add(0, null); + spEndingOffsets.add(0, null); - // Also need to find the TextObjectRecord too - // TODO - } - else if(r instanceof EscherSpRecord) { - // Use flags if needed - final EscherSpRecord spr = (EscherSpRecord) r; - if (model instanceof HSSFShape){ - final HSSFShape s = (HSSFShape) model; - } - } - else if(r instanceof EscherOptRecord) { - // Use properties if needed - } - else { - //System.err.println(r); - } - } - } + // Split escher records into separate MSODRAWING and OBJ, TXO records. (We don't break on + // the first one because it's the patriach). + pos = offset; + int writtenEscherBytes = 0; + int i; + for (i = 1; i < shapes.size(); i++) { + int endOffset = (Integer) spEndingOffsets.get(i) - 1; + int startOffset; + if (i == 1) + startOffset = 0; + else + startOffset = (Integer) spEndingOffsets.get(i - 1); - public void clear() - { - clearEscherRecords(); - shapeToObj.clear(); -// lastShapeId = 1024; - } + byte[] drawingData = new byte[endOffset - startOffset + 1]; + System.arraycopy(buffer, startOffset, drawingData, 0, drawingData.length); + pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i); - protected String getRecordName() - { - return "ESCHERAGGREGATE"; - } + writtenEscherBytes += drawingData.length; - // =============== Private methods ======================== + // Write the matching OBJ record + Record obj = shapeToObj.get(shapes.get(i)); + pos += obj.serialize(pos, data); - private static boolean isObjectRecord( List records, int loc ) - { - return sid( records, loc ) == ObjRecord.sid || sid( records, loc ) == TextObjectRecord.sid; - } + if (i == shapes.size() - 1 && endOffset < buffer.length - 1) { + drawingData = new byte[buffer.length - endOffset - 1]; + System.arraycopy(buffer, endOffset + 1, drawingData, 0, drawingData.length); + pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i); + } + } + if ((pos - offset) < buffer.length - 1) { + byte[] drawingData = new byte[buffer.length - (pos - offset)]; + System.arraycopy(buffer, (pos - offset), drawingData, 0, drawingData.length); + pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i); + } - private void convertUserModelToRecords() - { - if ( patriarch != null ) - { - shapeToObj.clear(); - tailRec.clear(); - clearEscherRecords(); - if ( patriarch.getChildren().size() != 0 ) - { - convertPatriarch( patriarch ); - EscherContainerRecord dgContainer = (EscherContainerRecord) getEscherRecord( 0 ); - EscherContainerRecord spgrContainer = null; - Iterator iter = dgContainer.getChildIterator(); - while (iter.hasNext()) { - EscherRecord child = iter.next(); - if (child.getRecordId() == EscherContainerRecord.SPGR_CONTAINER) { - spgrContainer = (EscherContainerRecord) child; - } - } - convertShapes( patriarch, spgrContainer, shapeToObj ); + // write records that need to be serialized after all drawing group records + Map tailCopy = new HashMap(tailRec); + // at first we should save records in correct order which were already in the file during EscherAggregate.createAggregate() + for (Integer id : _tailIds) { + NoteRecord note = tailCopy.get(id); + if (null != note) { + pos += note.serialize(pos, data); + tailCopy.remove(id); + } + } + // Add all other notes which were created after createAggregate() + for (i = 0; i < tailCopy.size(); i++) { + Record rec = (Record) tailCopy.values().toArray()[i]; + pos += rec.serialize(pos, data); + } + int bytesWritten = pos - offset; + if (bytesWritten != getRecordSize()) + throw new RecordFormatException(bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize()); + return bytesWritten; + } - patriarch = null; - } - } - } + /** + * @param drawingData - escher records saved into single byte array + * @param writtenEscherBytes - count of bytes already saved into drawing records (we should know it to decide create + * drawing or continue record) + * @param pos current position of data array + * @param data - array of bytes where drawing records must be serialized + * @param i - number of shape, saved into data array + * @return offset of data array after serialization + */ + private int writeDataIntoDrawingRecord(byte[] drawingData, int writtenEscherBytes, int pos, byte[] data, int i) { + int temp = 0; + //First record in drawing layer MUST be DrawingRecord + if (writtenEscherBytes + drawingData.length > RecordInputStream.MAX_RECORD_DATA_SIZE && i != 1) { + for (int j = 0; j < drawingData.length; j += RecordInputStream.MAX_RECORD_DATA_SIZE) { + byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)]; + System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)); + ContinueRecord drawing = new ContinueRecord(buf); + temp += drawing.serialize(pos + temp, data); + } + } else { + for (int j = 0; j < drawingData.length; j += RecordInputStream.MAX_RECORD_DATA_SIZE) { + if (j == 0) { + DrawingRecord drawing = new DrawingRecord(); + byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)]; + System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)); + drawing.setData(buf); + temp += drawing.serialize(pos + temp, data); + } else { + byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)]; + System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)); + ContinueRecord drawing = new ContinueRecord(buf); + temp += drawing.serialize(pos + temp, data); + } + } + } + return temp; + } - private void convertShapes( HSSFShapeContainer parent, EscherContainerRecord escherParent, Map shapeToObj ) - { - if ( escherParent == null ) throw new IllegalArgumentException( "Parent record required" ); + /** + * How many bytes do the raw escher records contain. + * + * @param records List of escher records + * @return the number of bytes + */ + private int getEscherRecordSize(List records) { + int size = 0; + for (Iterator iterator = records.iterator(); iterator.hasNext(); ) + size += ((EscherRecord) iterator.next()).getRecordSize(); + return size; + } - List shapes = parent.getChildren(); - for ( Iterator iterator = shapes.iterator(); iterator.hasNext(); ) - { - HSSFShape shape = (HSSFShape) iterator.next(); - if ( shape instanceof HSSFShapeGroup ) - { - convertGroup( (HSSFShapeGroup) shape, escherParent, shapeToObj ); - } - else - { - AbstractShape shapeModel = AbstractShape.createShape( - shape, - drawingManager.allocateShapeId(drawingGroupId) ); - shapeToObj.put( findClientData( shapeModel.getSpContainer() ), shapeModel.getObjRecord() ); - if ( shapeModel instanceof TextboxShape ) - { - EscherRecord escherTextbox = ( (TextboxShape) shapeModel ).getEscherTextbox(); - shapeToObj.put( escherTextbox, ( (TextboxShape) shapeModel ).getTextObjectRecord() ); - // escherParent.addChildRecord(escherTextbox); + /** + * @return record size, including header size of obj, text, note, drawing, continue records + */ + public int getRecordSize() { + // To determine size of aggregate record we have to know size of each DrawingRecord because if DrawingRecord + // is split into several continue records we have to add header size to total EscherAggregate size + int continueRecordsHeadersSize = 0; + // Determine buffer size + List records = getEscherRecords(); + int rawEscherSize = getEscherRecordSize(records); + byte[] buffer = new byte[rawEscherSize]; + final List spEndingOffsets = new ArrayList(); + int pos = 0; + for (EscherRecord e : records) { + pos += e.serialize(pos, buffer, new EscherSerializationListener() { + public void beforeRecordSerialize(int offset, short recordId, EscherRecord record) { + } - if ( shapeModel instanceof CommentShape ){ - CommentShape comment = (CommentShape)shapeModel; - tailRec.add(comment.getNoteRecord()); - } + public void afterRecordSerialize(int offset, short recordId, int size, EscherRecord record) { + if (recordId == EscherClientDataRecord.RECORD_ID || recordId == EscherTextboxRecord.RECORD_ID) { + spEndingOffsets.add(offset); + } + } + }); + } + spEndingOffsets.add(0, 0); - } - escherParent.addChildRecord( shapeModel.getSpContainer() ); - } - } -// drawingManager.newCluster( (short)1 ); -// drawingManager.newCluster( (short)2 ); + for (int i = 1; i < spEndingOffsets.size(); i++) { + if (i == spEndingOffsets.size() - 1 && spEndingOffsets.get(i) < pos) { + continueRecordsHeadersSize += 4; + } + if (spEndingOffsets.get(i) - spEndingOffsets.get(i - 1) <= RecordInputStream.MAX_RECORD_DATA_SIZE) { + continue; + } + continueRecordsHeadersSize += ((spEndingOffsets.get(i) - spEndingOffsets.get(i - 1)) / RecordInputStream.MAX_RECORD_DATA_SIZE) * 4; + } - } + int drawingRecordSize = rawEscherSize + (shapeToObj.size()) * 4; + if (rawEscherSize != 0 && spEndingOffsets.size() == 1/**EMPTY**/) { + continueRecordsHeadersSize += 4; + } + int objRecordSize = 0; + for (Iterator iterator = shapeToObj.values().iterator(); iterator.hasNext(); ) { + Record r = (Record) iterator.next(); + objRecordSize += r.getRecordSize(); + } + int tailRecordSize = 0; + for (Iterator iterator = tailRec.values().iterator(); iterator.hasNext(); ) { + Record r = (Record) iterator.next(); + tailRecordSize += r.getRecordSize(); + } + return drawingRecordSize + objRecordSize + tailRecordSize + continueRecordsHeadersSize; + } - private void convertGroup( HSSFShapeGroup shape, EscherContainerRecord escherParent, Map shapeToObj ) - { - EscherContainerRecord spgrContainer = new EscherContainerRecord(); - EscherContainerRecord spContainer = new EscherContainerRecord(); - EscherSpgrRecord spgr = new EscherSpgrRecord(); - EscherSpRecord sp = new EscherSpRecord(); - EscherOptRecord opt = new EscherOptRecord(); - EscherRecord anchor; - EscherClientDataRecord clientData = new EscherClientDataRecord(); + /** + * Associates an escher record to an OBJ record or a TXO record. + */ + public Object associateShapeToObjRecord(EscherRecord r, Record objRecord) { + return shapeToObj.put(r, objRecord); + } - spgrContainer.setRecordId( EscherContainerRecord.SPGR_CONTAINER ); - spgrContainer.setOptions( (short) 0x000F ); - spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); - spContainer.setOptions( (short) 0x000F ); - spgr.setRecordId( EscherSpgrRecord.RECORD_ID ); - spgr.setOptions( (short) 0x0001 ); - spgr.setRectX1( shape.getX1() ); - spgr.setRectY1( shape.getY1() ); - spgr.setRectX2( shape.getX2() ); - spgr.setRectY2( shape.getY2() ); - sp.setRecordId( EscherSpRecord.RECORD_ID ); - sp.setOptions( (short) 0x0002 ); - int shapeId = drawingManager.allocateShapeId(drawingGroupId); - sp.setShapeId( shapeId ); - if (shape.getAnchor() instanceof HSSFClientAnchor) - sp.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR ); - else - sp.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_CHILD ); - opt.setRecordId( EscherOptRecord.RECORD_ID ); - opt.setOptions( (short) 0x0023 ); - opt.addEscherProperty( new EscherBoolProperty( EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 0x00040004 ) ); - opt.addEscherProperty( new EscherBoolProperty( EscherProperties.GROUPSHAPE__PRINT, 0x00080000 ) ); + public void removeShapeToObjRecord(EscherRecord rec) { + shapeToObj.remove(rec); + } - anchor = ConvertAnchor.createAnchor( shape.getAnchor() ); -// clientAnchor.setCol1( ( (HSSFClientAnchor) shape.getAnchor() ).getCol1() ); -// clientAnchor.setRow1( (short) ( (HSSFClientAnchor) shape.getAnchor() ).getRow1() ); -// clientAnchor.setDx1( (short) shape.getAnchor().getDx1() ); -// clientAnchor.setDy1( (short) shape.getAnchor().getDy1() ); -// clientAnchor.setCol2( ( (HSSFClientAnchor) shape.getAnchor() ).getCol2() ); -// clientAnchor.setRow2( (short) ( (HSSFClientAnchor) shape.getAnchor() ).getRow2() ); -// clientAnchor.setDx2( (short) shape.getAnchor().getDx2() ); -// clientAnchor.setDy2( (short) shape.getAnchor().getDy2() ); - clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); - clientData.setOptions( (short) 0x0000 ); + protected String getRecordName() { + return "ESCHERAGGREGATE"; + } - spgrContainer.addChildRecord( spContainer ); - spContainer.addChildRecord( spgr ); - spContainer.addChildRecord( sp ); - spContainer.addChildRecord( opt ); - spContainer.addChildRecord( anchor ); - spContainer.addChildRecord( clientData ); + // =============== Private methods ======================== - ObjRecord obj = new ObjRecord(); - CommonObjectDataSubRecord cmo = new CommonObjectDataSubRecord(); - cmo.setObjectType( CommonObjectDataSubRecord.OBJECT_TYPE_GROUP ); - cmo.setObjectId( shapeId ); - cmo.setLocked( true ); - cmo.setPrintable( true ); - cmo.setAutofill( true ); - cmo.setAutoline( true ); - GroupMarkerSubRecord gmo = new GroupMarkerSubRecord(); - EndSubRecord end = new EndSubRecord(); - obj.addSubRecord( cmo ); - obj.addSubRecord( gmo ); - obj.addSubRecord( end ); - shapeToObj.put( clientData, obj ); + private static boolean isObjectRecord(List records, int loc) { + return sid(records, loc) == ObjRecord.sid || sid(records, loc) == TextObjectRecord.sid; + } - escherParent.addChildRecord( spgrContainer ); + private EscherRecord findClientData(EscherContainerRecord spContainer) { + for (Iterator iterator = spContainer.getChildIterator(); iterator.hasNext(); ) { + EscherRecord r = iterator.next(); + if (r.getRecordId() == EscherClientDataRecord.RECORD_ID) { + return r; + } + } + throw new IllegalArgumentException("Can not find client data record"); + } - convertShapes( shape, spgrContainer, shapeToObj ); + private void buildBaseTree() { + EscherContainerRecord dgContainer = new EscherContainerRecord(); + EscherContainerRecord spgrContainer = new EscherContainerRecord(); + EscherContainerRecord spContainer1 = new EscherContainerRecord(); + EscherSpgrRecord spgr = new EscherSpgrRecord(); + EscherSpRecord sp1 = new EscherSpRecord(); + dgContainer.setRecordId(EscherContainerRecord.DG_CONTAINER); + dgContainer.setOptions((short) 0x000F); + EscherDgRecord dg = new EscherDgRecord(); + dg.setRecordId(EscherDgRecord.RECORD_ID); + short dgId = 1; + dg.setOptions((short) (dgId << 4)); + dg.setNumShapes(0); + dg.setLastMSOSPID(1024); + spgrContainer.setRecordId(EscherContainerRecord.SPGR_CONTAINER); + spgrContainer.setOptions((short) 0x000F); + spContainer1.setRecordId(EscherContainerRecord.SP_CONTAINER); + spContainer1.setOptions((short) 0x000F); + spgr.setRecordId(EscherSpgrRecord.RECORD_ID); + spgr.setOptions((short) 0x0001); // version + spgr.setRectX1(0); + spgr.setRectY1(0); + spgr.setRectX2(1023); + spgr.setRectY2(255); + sp1.setRecordId(EscherSpRecord.RECORD_ID); - } + sp1.setOptions((short) 0x0002); + sp1.setVersion((short) 0x2); + sp1.setShapeId(-1); + sp1.setFlags(EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_PATRIARCH); + dgContainer.addChildRecord(dg); + dgContainer.addChildRecord(spgrContainer); + spgrContainer.addChildRecord(spContainer1); + spContainer1.addChildRecord(spgr); + spContainer1.addChildRecord(sp1); + addEscherRecord(dgContainer); + } - private EscherRecord findClientData( EscherContainerRecord spContainer ) - { - for (Iterator iterator = spContainer.getChildIterator(); iterator.hasNext();) { - EscherRecord r = iterator.next(); - if (r.getRecordId() == EscherClientDataRecord.RECORD_ID) { - return r; - } - } - throw new IllegalArgumentException( "Can not find client data record" ); - } + public void setDgId(short dgId) { + EscherContainerRecord dgContainer = getEscherContainer(); + EscherDgRecord dg = dgContainer.getChildById(EscherDgRecord.RECORD_ID); + dg.setOptions((short) (dgId << 4)); + } - private void convertPatriarch( HSSFPatriarch patriarch ) - { - EscherContainerRecord dgContainer = new EscherContainerRecord(); - EscherDgRecord dg; - EscherContainerRecord spgrContainer = new EscherContainerRecord(); - EscherContainerRecord spContainer1 = new EscherContainerRecord(); - EscherSpgrRecord spgr = new EscherSpgrRecord(); - EscherSpRecord sp1 = new EscherSpRecord(); + public void setMainSpRecordId(int shapeId) { + EscherContainerRecord dgContainer = getEscherContainer(); + EscherContainerRecord spgrConatiner = (EscherContainerRecord) dgContainer.getChildById(EscherContainerRecord.SPGR_CONTAINER); + EscherContainerRecord spContainer = (EscherContainerRecord) spgrConatiner.getChild(0); + EscherSpRecord sp = (EscherSpRecord) spContainer.getChildById(EscherSpRecord.RECORD_ID); + sp.setShapeId(shapeId); + } - dgContainer.setRecordId( EscherContainerRecord.DG_CONTAINER ); - dgContainer.setOptions( (short) 0x000F ); - dg = drawingManager.createDgRecord(); - drawingGroupId = dg.getDrawingGroupId(); -// dg.setOptions( (short) ( drawingId << 4 ) ); -// dg.setNumShapes( getNumberOfShapes( patriarch ) ); -// dg.setLastMSOSPID( 0 ); // populated after all shape id's are assigned. - spgrContainer.setRecordId( EscherContainerRecord.SPGR_CONTAINER ); - spgrContainer.setOptions( (short) 0x000F ); - spContainer1.setRecordId( EscherContainerRecord.SP_CONTAINER ); - spContainer1.setOptions( (short) 0x000F ); - spgr.setRecordId( EscherSpgrRecord.RECORD_ID ); - spgr.setOptions( (short) 0x0001 ); // version - spgr.setRectX1( patriarch.getX1() ); - spgr.setRectY1( patriarch.getY1() ); - spgr.setRectX2( patriarch.getX2() ); - spgr.setRectY2( patriarch.getY2() ); - sp1.setRecordId( EscherSpRecord.RECORD_ID ); - sp1.setOptions( (short) 0x0002 ); - sp1.setShapeId( drawingManager.allocateShapeId(dg.getDrawingGroupId()) ); - sp1.setFlags( EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_PATRIARCH ); - - dgContainer.addChildRecord( dg ); - dgContainer.addChildRecord( spgrContainer ); - spgrContainer.addChildRecord( spContainer1 ); - spContainer1.addChildRecord( spgr ); - spContainer1.addChildRecord( sp1 ); - - addEscherRecord( dgContainer ); - } + private static short sid(List records, int loc) { + return ((Record) records.get(loc)).getSid(); + } - private static short sid( List records, int loc ) - { - return ( (Record) records.get( loc ) ).getSid(); - } + // Duplicated from org.apache.poi.hslf.model.Shape + /** + * Helper method to return escher child by record ID + * + * @return escher record or null if not found. + */ + private static EscherRecord getEscherChild(EscherContainerRecord owner, + int recordId) { + for (Iterator iterator = owner.getChildRecords().iterator(); iterator + .hasNext(); ) { + EscherRecord escherRecord = (EscherRecord) iterator.next(); + if (escherRecord.getRecordId() == recordId) + return escherRecord; + } + return null; + } - // Duplicated from org.apache.poi.hslf.model.Shape + /** + * @return unmodifiable copy of the mapping of {@link EscherClientDataRecord} and {@link EscherTextboxRecord} + * to their {@link TextObjectRecord} or {@link ObjRecord} . + *

+ * We need to access it outside of EscherAggregate when building shapes + */ + public Map getShapeToObjMapping() { + return Collections.unmodifiableMap(shapeToObj); + } - /** - * Helper method to return escher child by record ID - * - * @return escher record or null if not found. - */ - private static EscherRecord getEscherChild(EscherContainerRecord owner, - int recordId) - { - for (Iterator iterator = owner.getChildRecords().iterator(); iterator - .hasNext();) - { - EscherRecord escherRecord = (EscherRecord) iterator.next(); - if (escherRecord.getRecordId() == recordId) - return escherRecord; - } - return null; - } + /** + * @return unmodifiable copy of tail records. We need to access them when building shapes. + * Every HSSFComment shape has a link to a NoteRecord from the tailRec collection. + */ + public Map getTailRecords() { + return tailRec; + } + /** + * @param obj - ObjRecord with id == NoteRecord.id + * @return null if note record is not found else returns note record with id == obj.id + */ + public NoteRecord getNoteRecordByObj(ObjRecord obj) { + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) obj.getSubRecords().get(0); + return tailRec.get(cod.getObjectId()); + } + + /** + * Add tail record to existing map + * @param note to be added + */ + public void addTailRecord(NoteRecord note) { + tailRec.put(note.getShapeId(), note); + } + + /** + * Remove tail record from the existing map + * @param note to be removed + */ + public void removeTailRecord(NoteRecord note) { + tailRec.remove(note.getShapeId()); + } } diff --git a/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java b/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java index 344ad07ef..aac88b80c 100644 --- a/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java +++ b/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java @@ -338,14 +338,14 @@ public final class RecordFactoryInputStream { } if (_lastRecord instanceof DrawingGroupRecord) { ((DrawingGroupRecord) _lastRecord).processContinueRecord(contRec.getData()); - return null; - } - if (_lastRecord instanceof DrawingRecord) { - ((DrawingRecord) _lastRecord).processContinueRecord(contRec.getData()); - return null; - } - if (_lastRecord instanceof UnknownRecord) { - //Gracefully handle records that we don't know about, + return null; + } + if (_lastRecord instanceof DrawingRecord) { +// ((DrawingRecord) _lastRecord).appendContinueRecord(contRec.getData()); + return contRec; + } + if (_lastRecord instanceof UnknownRecord) { + //Gracefully handle records that we don't know about, //that happen to be continued return record; } diff --git a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java index 30d3fc629..c183b5070 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java @@ -286,9 +286,9 @@ public final class PageSettingsBlock extends RecordAggregate { pls.visitContainedRecords(rv); } visitIfPresent(_printSetup, rv); - visitIfPresent(_bitmap, rv); visitIfPresent(_printSize, rv); visitIfPresent(_headerFooter, rv); + visitIfPresent(_bitmap, rv); } private static void visitIfPresent(Record r, RecordVisitor rv) { if (r != null) { diff --git a/src/java/org/apache/poi/hssf/usermodel/EscherGraphics.java b/src/java/org/apache/poi/hssf/usermodel/EscherGraphics.java index 7500ac7b7..90a93e526 100644 --- a/src/java/org/apache/poi/hssf/usermodel/EscherGraphics.java +++ b/src/java/org/apache/poi/hssf/usermodel/EscherGraphics.java @@ -349,6 +349,7 @@ public class EscherGraphics shape.setLineStyle(HSSFShape.LINESTYLE_NONE); shape.setFillColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); shape.setLineStyleColor(foreground.getRed(), foreground.getGreen(), foreground.getBlue()); + shape.setNoFill(false); } /** diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFAnchor.java index 6a8780610..91bbd5b95 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFAnchor.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFAnchor.java @@ -18,40 +18,96 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherChildAnchorRecord; +import org.apache.poi.ddf.EscherClientAnchorRecord; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherRecord; + /** * An anchor is what specifics the position of a shape within a client object * or within another containing shape. * * @author Glen Stampoultzis (glens at apache.org) */ -public abstract class HSSFAnchor -{ - int dx1; - int dy1; - int dx2; - int dy2; +public abstract class HSSFAnchor { - public HSSFAnchor() - { + protected boolean _isHorizontallyFlipped = false; + protected boolean _isVerticallyFlipped = false; + + public HSSFAnchor() { + createEscherAnchor(); } - public HSSFAnchor( int dx1, int dy1, int dx2, int dy2 ) - { - this.dx1 = dx1; - this.dy1 = dy1; - this.dx2 = dx2; - this.dy2 = dy2; + public HSSFAnchor(int dx1, int dy1, int dx2, int dy2) { + createEscherAnchor(); + setDx1(dx1); + setDy1(dy1); + setDx2(dx2); + setDy2(dy2); } - public int getDx1(){ return dx1; } - public void setDx1( int dx1 ){ this.dx1 = dx1; } - public int getDy1(){ return dy1; } - public void setDy1( int dy1 ){ this.dy1 = dy1; } - public int getDy2(){ return dy2; } - public void setDy2( int dy2 ){ this.dy2 = dy2; } - public int getDx2(){ return dx2; } - public void setDx2( int dx2 ){ this.dx2 = dx2; } + public static HSSFAnchor createAnchorFromEscher(EscherContainerRecord container){ + if (null != container.getChildById(EscherChildAnchorRecord.RECORD_ID)){ + return new HSSFChildAnchor((EscherChildAnchorRecord) container.getChildById(EscherChildAnchorRecord.RECORD_ID)); + } else { + if (null != container.getChildById(EscherClientAnchorRecord.RECORD_ID)){ + return new HSSFClientAnchor((EscherClientAnchorRecord) container.getChildById(EscherClientAnchorRecord.RECORD_ID)); + } + return null; + } + } + /** + * @return x coordinate of the left up corner + */ + public abstract int getDx1(); + + /** + * @param dx1 x coordinate of the left up corner + */ + public abstract void setDx1(int dx1); + + /** + * @return y coordinate of the left up corner + */ + public abstract int getDy1(); + + /** + * @param dy1 y coordinate of the left up corner + */ + public abstract void setDy1(int dy1); + + /** + * @return y coordinate of the right down corner + */ + public abstract int getDy2(); + + /** + * @param dy2 y coordinate of the right down corner + */ + public abstract void setDy2(int dy2); + + /** + * @return x coordinate of the right down corner + */ + public abstract int getDx2(); + + /** + * @param dx2 x coordinate of the right down corner + */ + public abstract void setDx2(int dx2); + + /** + * @return whether this shape is horizontally flipped + */ public abstract boolean isHorizontallyFlipped(); + + /** + * @return whether this shape is vertically flipped + */ public abstract boolean isVerticallyFlipped(); + + protected abstract EscherRecord getEscherAnchor(); + + protected abstract void createEscherAnchor(); } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 28c15fac7..1ad05587e 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -1021,7 +1021,7 @@ public class HSSFCell implements Cell { */ public HSSFComment getCellComment(){ if (_comment == null) { - _comment = findCellComment(_sheet.getSheet(), _record.getRow(), _record.getColumn()); + _comment = _sheet.findCellComment(_record.getRow(), _record.getColumn()); } return _comment; } @@ -1033,98 +1033,12 @@ public class HSSFCell implements Cell { * all comments after performing this action! */ public void removeCellComment() { - HSSFComment comment = findCellComment(_sheet.getSheet(), _record.getRow(), _record.getColumn()); + HSSFComment comment = _sheet.findCellComment(_record.getRow(), _record.getColumn()); _comment = null; - - if(comment == null) { - // Nothing to do + if (null == comment){ return; } - - // Zap the underlying NoteRecord - List sheetRecords = _sheet.getSheet().getRecords(); - sheetRecords.remove(comment.getNoteRecord()); - - // If we have a TextObjectRecord, is should - // be proceeed by: - // MSODRAWING with container - // OBJ - // MSODRAWING with EscherTextboxRecord - if(comment.getTextObjectRecord() != null) { - TextObjectRecord txo = comment.getTextObjectRecord(); - int txoAt = sheetRecords.indexOf(txo); - - if(sheetRecords.get(txoAt-3) instanceof DrawingRecord && - sheetRecords.get(txoAt-2) instanceof ObjRecord && - sheetRecords.get(txoAt-1) instanceof DrawingRecord) { - // Zap these, in reverse order - sheetRecords.remove(txoAt-1); - sheetRecords.remove(txoAt-2); - sheetRecords.remove(txoAt-3); - } else { - throw new IllegalStateException("Found the wrong records before the TextObjectRecord, can't remove comment"); - } - - // Now remove the text record - sheetRecords.remove(txo); - } - } - - /** - * Cell comment finder. - * Returns cell comment for the specified sheet, row and column. - * - * @return cell comment or null if not found - */ - protected static HSSFComment findCellComment(InternalSheet sheet, int row, int column) { - // TODO - optimise this code by searching backwards, find NoteRecord first, quit if not found. Find one TXO by id - HSSFComment comment = null; - Map noteTxo = - new HashMap(); - int i = 0; - for (Iterator it = sheet.getRecords().iterator(); it.hasNext();) { - RecordBase rec = it.next(); - if (rec instanceof NoteRecord) { - NoteRecord note = (NoteRecord) rec; - if (note.getRow() == row && note.getColumn() == column) { - if(i < noteTxo.size()) { - TextObjectRecord txo = noteTxo.get(note.getShapeId()); - if(txo != null){ - comment = new HSSFComment(note, txo); - comment.setRow(note.getRow()); - comment.setColumn(note.getColumn()); - comment.setAuthor(note.getAuthor()); - comment.setVisible(note.getFlags() == NoteRecord.NOTE_VISIBLE); - comment.setString(txo.getStr()); - } else{ - log.log(POILogger.WARN, "Failed to match NoteRecord and TextObjectRecord, row: " + row + ", column: " + column); - } - } else { - log.log(POILogger.WARN, "Failed to match NoteRecord and TextObjectRecord, row: " + row + ", column: " + column); - } - break; - } - i++; - } else if (rec instanceof ObjRecord) { - ObjRecord obj = (ObjRecord) rec; - SubRecord sub = obj.getSubRecords().get(0); - if (sub instanceof CommonObjectDataSubRecord) { - CommonObjectDataSubRecord cmo = (CommonObjectDataSubRecord) sub; - if (cmo.getObjectType() == CommonObjectDataSubRecord.OBJECT_TYPE_COMMENT) { - //map ObjectId and corresponding TextObjectRecord, - //it will be used to match NoteRecord and TextObjectRecord - while (it.hasNext()) { - rec = it.next(); - if (rec instanceof TextObjectRecord) { - noteTxo.put(cmo.getObjectId(), (TextObjectRecord) rec); - break; - } - } - } - } - } - } - return comment; + _sheet.getDrawingPatriarch().removeShape(comment); } /** diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java index 513ac619f..be69c2a98 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java @@ -18,31 +18,126 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherChildAnchorRecord; +import org.apache.poi.ddf.EscherRecord; + public final class HSSFChildAnchor extends HSSFAnchor { - public HSSFChildAnchor() - { + + private EscherChildAnchorRecord _escherChildAnchor; + + /** + * create anchor from existing file + * @param escherChildAnchorRecord + */ + public HSSFChildAnchor(EscherChildAnchorRecord escherChildAnchorRecord) { + this._escherChildAnchor = escherChildAnchorRecord; } - public HSSFChildAnchor( int dx1, int dy1, int dx2, int dy2 ) - { - super( dx1, dy1, dx2, dy2 ); + public HSSFChildAnchor() { + _escherChildAnchor = new EscherChildAnchorRecord(); } - public void setAnchor(int dx1, int dy1, int dx2, int dy2) - { - this.dx1 = dx1; - this.dy1 = dy1; - this.dx2 = dx2; - this.dy2 = dy2; + /** + * create anchor from scratch + * @param dx1 x coordinate of the left up corner + * @param dy1 y coordinate of the left up corner + * @param dx2 x coordinate of the right down corner + * @param dy2 y coordinate of the right down corner + */ + public HSSFChildAnchor(int dx1, int dy1, int dx2, int dy2) { + super(Math.min(dx1, dx2), Math.min(dy1, dy2), Math.max(dx1, dx2), Math.max(dy1, dy2)); + if (dx1 > dx2){ + _isHorizontallyFlipped = true; + } + if (dy1 > dy2){ + _isVerticallyFlipped = true; + } } - public boolean isHorizontallyFlipped() - { - return dx1 > dx2; + @Override + public int getDx1() { + return _escherChildAnchor.getDx1(); } - public boolean isVerticallyFlipped() - { - return dy1 > dy2; + @Override + public void setDx1(int dx1) { + _escherChildAnchor.setDx1(dx1); + } + + @Override + public int getDy1() { + return _escherChildAnchor.getDy1(); + } + + @Override + public void setDy1(int dy1) { + _escherChildAnchor.setDy1(dy1); + } + + @Override + public int getDy2() { + return _escherChildAnchor.getDy2(); + } + + @Override + public void setDy2(int dy2) { + _escherChildAnchor.setDy2(dy2); + } + + @Override + public int getDx2() { + return _escherChildAnchor.getDx2(); + } + + @Override + public void setDx2(int dx2) { + _escherChildAnchor.setDx2(dx2); + } + + /** + * @param dx1 x coordinate of the left up corner + * @param dy1 y coordinate of the left up corner + * @param dx2 x coordinate of the right down corner + * @param dy2 y coordinate of the right down corner + */ + public void setAnchor(int dx1, int dy1, int dx2, int dy2) { + setDx1(Math.min(dx1, dx2)); + setDy1(Math.min(dy1, dy2)); + setDx2(Math.max(dx1, dx2)); + setDy2(Math.max(dy1, dy2)); + } + + + public boolean isHorizontallyFlipped() { + return _isHorizontallyFlipped; + } + + + public boolean isVerticallyFlipped() { + return _isVerticallyFlipped; + } + + @Override + protected EscherRecord getEscherAnchor() { + return _escherChildAnchor; + } + + @Override + protected void createEscherAnchor() { + _escherChildAnchor = new EscherChildAnchorRecord(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + if (obj.getClass() != getClass()) + return false; + HSSFChildAnchor anchor = (HSSFChildAnchor) obj; + + return anchor.getDx1() == getDx1() && anchor.getDx2() == getDx2() && anchor.getDy1() == getDy1() + && anchor.getDy2() == getDy2(); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java index cffb00cd5..ac199e005 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java @@ -17,9 +17,10 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherClientAnchorRecord; +import org.apache.poi.ddf.EscherRecord; import org.apache.poi.ss.usermodel.ClientAnchor; - /** * A client anchor is attached to an excel worksheet. It anchors against a * top-left and buttom-right cell. @@ -27,35 +28,34 @@ import org.apache.poi.ss.usermodel.ClientAnchor; * @author Glen Stampoultzis (glens at apache.org) */ public final class HSSFClientAnchor extends HSSFAnchor implements ClientAnchor { - short col1; - int row1; - short col2; - int row2; - int anchorType; + + private EscherClientAnchorRecord _escherClientAnchor; + + public HSSFClientAnchor(EscherClientAnchorRecord escherClientAnchorRecord) { + this._escherClientAnchor = escherClientAnchorRecord; + } /** * Creates a new client anchor and defaults all the anchor positions to 0. */ - public HSSFClientAnchor() - { + public HSSFClientAnchor() { } /** * Creates a new client anchor and sets the top-left and bottom-right * coordinates of the anchor. * - * @param dx1 the x coordinate within the first cell. - * @param dy1 the y coordinate within the first cell. - * @param dx2 the x coordinate within the second cell. - * @param dy2 the y coordinate within the second cell. - * @param col1 the column (0 based) of the first cell. - * @param row1 the row (0 based) of the first cell. - * @param col2 the column (0 based) of the second cell. - * @param row2 the row (0 based) of the second cell. + * @param dx1 the x coordinate within the first cell. + * @param dy1 the y coordinate within the first cell. + * @param dx2 the x coordinate within the second cell. + * @param dy2 the y coordinate within the second cell. + * @param col1 the column (0 based) of the first cell. + * @param row1 the row (0 based) of the first cell. + * @param col2 the column (0 based) of the second cell. + * @param row2 the row (0 based) of the second cell. */ - public HSSFClientAnchor( int dx1, int dy1, int dx2, int dy2, short col1, int row1, short col2, int row2 ) - { - super( dx1, dy1, dx2, dy2 ); + public HSSFClientAnchor(int dx1, int dy1, int dx2, int dy2, short col1, int row1, short col2, int row2) { + super(dx1, dy1, dx2, dy2); checkRange(dx1, 0, 1023, "dx1"); checkRange(dx2, 0, 1023, "dx2"); @@ -66,35 +66,37 @@ public final class HSSFClientAnchor extends HSSFAnchor implements ClientAnchor { checkRange(row1, 0, 255 * 256, "row1"); checkRange(row2, 0, 255 * 256, "row2"); - this.col1 = col1; - this.row1 = row1; - this.col2 = col2; - this.row2 = row2; + setCol1((short) Math.min(col1, col2)); + setCol2((short) Math.max(col1, col2)); + setRow1((short) Math.min(row1, row2)); + setRow2((short) Math.max(row1, row2)); + + if (col1 > col2){ + _isHorizontallyFlipped = true; + } + if (row1 > row2){ + _isVerticallyFlipped = true; + } } /** * Calculates the height of a client anchor in points. * - * @param sheet the sheet the anchor will be attached to - * @return the shape height. + * @param sheet the sheet the anchor will be attached to + * @return the shape height. */ - public float getAnchorHeightInPoints(HSSFSheet sheet ) - { + public float getAnchorHeightInPoints(HSSFSheet sheet) { int y1 = getDy1(); int y2 = getDy2(); - int row1 = Math.min( getRow1(), getRow2() ); - int row2 = Math.max( getRow1(), getRow2() ); + int row1 = Math.min(getRow1(), getRow2()); + int row2 = Math.max(getRow1(), getRow2()); float points = 0; - if (row1 == row2) - { + if (row1 == row2) { points = ((y2 - y1) / 256.0f) * getRowHeightInPoints(sheet, row2); - } - else - { + } else { points += ((256.0f - y1) / 256.0f) * getRowHeightInPoints(sheet, row1); - for (int i = row1 + 1; i < row2; i++) - { + for (int i = row1 + 1; i < row2; i++) { points += getRowHeightInPoints(sheet, i); } points += (y2 / 256.0f) * getRowHeightInPoints(sheet, row2); @@ -103,8 +105,7 @@ public final class HSSFClientAnchor extends HSSFAnchor implements ClientAnchor { return points; } - private float getRowHeightInPoints(HSSFSheet sheet, int rowNum) - { + private float getRowHeightInPoints(HSSFSheet sheet, int rowNum) { HSSFRow row = sheet.getRow(rowNum); if (row == null) { return sheet.getDefaultRowHeightInPoints(); @@ -112,55 +113,78 @@ public final class HSSFClientAnchor extends HSSFAnchor implements ClientAnchor { return row.getHeightInPoints(); } - public short getCol1() - { - return col1; + /** + * @return the column(0 based) of the first cell. + */ + public short getCol1() { + return _escherClientAnchor.getCol1(); } - public void setCol1( short col1 ) - { + /** + * @param col1 the column(0 based) of the first cell. + */ + public void setCol1(short col1) { checkRange(col1, 0, 255, "col1"); - this.col1 = col1; - } - public void setCol1( int col1 ){ - setCol1((short)col1); + _escherClientAnchor.setCol1(col1); } - public short getCol2() - { - return col2; + /** + * @param col1 0-based column of the first cell. + */ + public void setCol1(int col1) { + setCol1((short) col1); } - public void setCol2( short col2 ) - { + /** + * @return the column(0 based) of the first cell. + */ + public short getCol2() { + return _escherClientAnchor.getCol2(); + } + + /** + * @param col2 the column(0 based) of the second cell. + */ + public void setCol2(short col2) { checkRange(col2, 0, 255, "col2"); - this.col2 = col2; + _escherClientAnchor.setCol2(col2); } - public void setCol2( int col2 ){ - setCol2((short)col2); + /** + * @param col2 the column(0 based) of the second cell. + */ + public void setCol2(int col2) { + setCol2((short) col2); } - public int getRow1() - { - return row1; + /** + * @return the row(0 based) of the first cell. + */ + public int getRow1() { + return _escherClientAnchor.getRow1(); } - public void setRow1( int row1 ) - { + /** + * @param row1 0-based row of the first cell. + */ + public void setRow1(int row1) { checkRange(row1, 0, 256 * 256, "row1"); - this.row1 = row1; + _escherClientAnchor.setRow1(Integer.valueOf(row1).shortValue()); } - public int getRow2() - { - return row2; + /** + * @return the row(0 based) of the second cell. + */ + public int getRow2() { + return _escherClientAnchor.getRow2(); } - public void setRow2( int row2 ) - { + /** + * @return the row(0 based) of the second cell. + */ + public void setRow2(int row2) { checkRange(row2, 0, 256 * 256, "row2"); - this.row2 = row2; + _escherClientAnchor.setRow2(Integer.valueOf(row2).shortValue()); } /** @@ -171,79 +195,124 @@ public final class HSSFClientAnchor extends HSSFAnchor implements ClientAnchor { * @param y1 the y coordinate within the first cell. * @param x2 the x coordinate within the second cell. * @param y2 the y coordinate within the second cell. - * @param col1 the column (0 based) of the first cell. - * @param row1 the row (0 based) of the first cell. - * @param col2 the column (0 based) of the second cell. - * @param row2 the row (0 based) of the second cell. + * @param col1 the column (0 based) of the first cell. + * @param row1 the row (0 based) of the first cell. + * @param col2 the column (0 based) of the second cell. + * @param row2 the row (0 based) of the second cell. */ - public void setAnchor( short col1, int row1, int x1, int y1, short col2, int row2, int x2, int y2 ) - { - checkRange(dx1, 0, 1023, "dx1"); - checkRange(dx2, 0, 1023, "dx2"); - checkRange(dy1, 0, 255, "dy1"); - checkRange(dy2, 0, 255, "dy2"); - checkRange(col1, 0, 255, "col1"); - checkRange(col2, 0, 255, "col2"); - checkRange(row1, 0, 255 * 256, "row1"); - checkRange(row2, 0, 255 * 256, "row2"); + public void setAnchor(short col1, int row1, int x1, int y1, short col2, int row2, int x2, int y2) { + checkRange(getDx1(), 0, 1023, "dx1"); + checkRange(getDx2(), 0, 1023, "dx2"); + checkRange(getDy1(), 0, 255, "dy1"); + checkRange(getDy2(), 0, 255, "dy2"); + checkRange(getCol1(), 0, 255, "col1"); + checkRange(getCol2(), 0, 255, "col2"); + checkRange(getRow1(), 0, 255 * 256, "row1"); + checkRange(getRow2(), 0, 255 * 256, "row2"); - this.col1 = col1; - this.row1 = row1; - this.dx1 = x1; - this.dy1 = y1; - this.col2 = col2; - this.row2 = row2; - this.dx2 = x2; - this.dy2 = y2; + setCol1(col1); + setRow1(row1); + setDx1(x1); + setDy1(y1); + setCol2(col2); + setRow2(row2); + setDx2(x2); + setDy2(y2); } - /** - * @return true if the anchor goes from right to left. - */ - public boolean isHorizontallyFlipped() - { - if (col1 == col2) { - return dx1 > dx2; - } - return col1 > col2; + public boolean isHorizontallyFlipped() { + return _isHorizontallyFlipped; } - /** - * @return true if the anchor goes from bottom to top. - */ - public boolean isVerticallyFlipped() - { - if (row1 == row2) { - return dy1 > dy2; - } - return row1 > row2; + public boolean isVerticallyFlipped() { + return _isVerticallyFlipped; + } + + @Override + protected EscherRecord getEscherAnchor() { + return _escherClientAnchor; + } + + @Override + protected void createEscherAnchor() { + _escherClientAnchor = new EscherClientAnchorRecord(); } /** * Gets the anchor type - *

+ *

* 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells. */ - public int getAnchorType() - { - return anchorType; + public int getAnchorType() { + return _escherClientAnchor.getFlag(); } /** * Sets the anchor type - *

+ *

* 0 = Move and size with Cells, 2 = Move but don't size with cells, 3 = Don't move or size with cells. */ - public void setAnchorType( int anchorType ) - { - this.anchorType = anchorType; + public void setAnchorType(int anchorType) { + _escherClientAnchor.setFlag(Integer.valueOf(anchorType).shortValue()); } - private void checkRange( int value, int minRange, int maxRange, String varName ) - { + private void checkRange(int value, int minRange, int maxRange, String varName) { if (value < minRange || value > maxRange) throw new IllegalArgumentException(varName + " must be between " + minRange + " and " + maxRange); } + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj == this) + return true; + if (obj.getClass() != getClass()) + return false; + HSSFClientAnchor anchor = (HSSFClientAnchor) obj; + return anchor.getCol1() == getCol1() && anchor.getCol2() == getCol2() && anchor.getDx1() == getDx1() + && anchor.getDx2() == getDx2() && anchor.getDy1() == getDy1() && anchor.getDy2() == getDy2() + && anchor.getRow1() == getRow1() && anchor.getRow2() == getRow2() && anchor.getAnchorType() == getAnchorType(); + } + + @Override + public int getDx1() { + return _escherClientAnchor.getDx1(); + } + + @Override + public void setDx1(int dx1) { + _escherClientAnchor.setDx1(Integer.valueOf(dx1).shortValue()); + } + + @Override + public int getDy1() { + return _escherClientAnchor.getDy1(); + } + + @Override + public void setDy1(int dy1) { + _escherClientAnchor.setDy1(Integer.valueOf(dy1).shortValue()); + } + + @Override + public int getDy2() { + return _escherClientAnchor.getDy2(); + } + + @Override + public void setDy2(int dy2) { + _escherClientAnchor.setDy2(Integer.valueOf(dy2).shortValue()); + } + + @Override + public int getDx2() { + return _escherClientAnchor.getDx2(); + } + + @Override + public void setDx2(int dx2) { + _escherClientAnchor.setDx2(Integer.valueOf(dx2).shortValue()); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCombobox.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCombobox.java new file mode 100644 index 000000000..c6cba8f72 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCombobox.java @@ -0,0 +1,101 @@ +/* ==================================================================== + 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.ddf.*; +import org.apache.poi.hssf.record.*; + +/** + * @author Evgeniy Berlog + * @date 12.07.12 + */ +public class HSSFCombobox extends HSSFSimpleShape { + + public HSSFCombobox(EscherContainerRecord spContainer, ObjRecord objRecord) { + super(spContainer, objRecord); + } + + public HSSFCombobox(HSSFShape parent, HSSFAnchor anchor) { + super(parent, anchor); + super.setShapeType(OBJECT_TYPE_COMBO_BOX); + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) getObjRecord().getSubRecords().get(0); + cod.setObjectType(CommonObjectDataSubRecord.OBJECT_TYPE_COMBO_BOX); + } + + @Override + protected TextObjectRecord createTextObjRecord() { + return null; + } + + @Override + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spContainer.setRecordId(EscherContainerRecord.SP_CONTAINER); + spContainer.setOptions((short) 0x000F); + sp.setRecordId(EscherSpRecord.RECORD_ID); + sp.setOptions((short) ((EscherAggregate.ST_HOSTCONTROL << 4) | 0x2)); + + sp.setFlags(EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE); + opt.setRecordId(EscherOptRecord.RECORD_ID); + opt.addEscherProperty(new EscherBoolProperty(EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 17039620)); + opt.addEscherProperty(new EscherBoolProperty(EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE, 0x00080008)); + opt.addEscherProperty(new EscherBoolProperty(EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080000)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GROUPSHAPE__PRINT, 0x00020000)); + + HSSFClientAnchor userAnchor = (HSSFClientAnchor) getAnchor(); + userAnchor.setAnchorType(1); + EscherRecord anchor = userAnchor.getEscherAnchor(); + clientData.setRecordId(EscherClientDataRecord.RECORD_ID); + clientData.setOptions((short) 0x0000); + + spContainer.addChildRecord(sp); + spContainer.addChildRecord(opt); + spContainer.addChildRecord(anchor); + spContainer.addChildRecord(clientData); + + return spContainer; + } + + @Override + protected ObjRecord createObjRecord() { + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType(HSSFSimpleShape.OBJECT_TYPE_COMBO_BOX); + c.setLocked(true); + c.setPrintable(false); + c.setAutofill(true); + c.setAutoline(false); + FtCblsSubRecord f = new FtCblsSubRecord(); + LbsDataSubRecord l = LbsDataSubRecord.newAutoFilterInstance(); + EndSubRecord e = new EndSubRecord(); + obj.addSubRecord(c); + obj.addSubRecord(f); + obj.addSubRecord(l); + obj.addSubRecord(e); + return obj; + } + + @Override + public void setShapeType(int shapeType) { + throw new IllegalStateException("Shape type can not be changed in "+this.getClass().getSimpleName()); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java index 225e6da0e..8f4795b9b 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFComment.java @@ -16,10 +16,9 @@ ==================================================================== */ package org.apache.poi.hssf.usermodel; -import org.apache.poi.hssf.record.NoteRecord; -import org.apache.poi.hssf.record.TextObjectRecord; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; import org.apache.poi.ss.usermodel.Comment; -import org.apache.poi.ss.usermodel.RichTextString; /** * Represents a cell comment - a sticky note associated with a cell. @@ -28,59 +27,108 @@ import org.apache.poi.ss.usermodel.RichTextString; */ public class HSSFComment extends HSSFTextbox implements Comment { - /* - * TODO - make HSSFComment more consistent when created vs read from file. - * Currently HSSFComment has two main forms (corresponding to the two constructors). There - * are certain operations that only work on comment objects in one of the forms (e.g. deleting - * comments). - * POI is also deficient in its management of RowRecord fields firstCol and lastCol. Those - * fields are supposed to take comments into account, but POI does not do this yet (feb 2009). - * It seems like HSSFRow should manage a collection of local HSSFComments - */ - - private boolean _visible; - private int _row; - private int _col; - private String _author; + private final static int FILL_TYPE_SOLID = 0; + private final static int FILL_TYPE_PICTURE = 3; + + /* + * TODO - make HSSFComment more consistent when created vs read from file. + * Currently HSSFComment has two main forms (corresponding to the two constructors). There + * are certain operations that only work on comment objects in one of the forms (e.g. deleting + * comments). + * POI is also deficient in its management of RowRecord fields firstCol and lastCol. Those + * fields are supposed to take comments into account, but POI does not do this yet (feb 2009). + * It seems like HSSFRow should manage a collection of local HSSFComments + */ private NoteRecord _note; - private TextObjectRecord _txo; + + public HSSFComment(EscherContainerRecord spContainer, ObjRecord objRecord, TextObjectRecord textObjectRecord, NoteRecord _note) { + super(spContainer, objRecord, textObjectRecord); + this._note = _note; + } /** * Construct a new comment with the given parent and anchor. * * @param parent - * @param anchor defines position of this anchor in the sheet + * @param anchor defines position of this anchor in the sheet */ public HSSFComment(HSSFShape parent, HSSFAnchor anchor) { super(parent, anchor); - setShapeType(OBJECT_TYPE_COMMENT); - + _note = createNoteRecord(); //default color for comments - _fillColor = 0x08000050; + setFillColor(0x08000050); //by default comments are hidden - _visible = false; - - _author = ""; + setVisible(false); + setAuthor(""); + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) getObjRecord().getSubRecords().get(0); + cod.setObjectType(CommonObjectDataSubRecord.OBJECT_TYPE_COMMENT); } protected HSSFComment(NoteRecord note, TextObjectRecord txo) { - this((HSSFShape) null, (HSSFAnchor) null); - _txo = txo; + this(null, new HSSFClientAnchor()); _note = note; } + @Override + void afterInsert(HSSFPatriarch patriarch) { + super.afterInsert(patriarch); + patriarch._getBoundAggregate().addTailRecord(getNoteRecord()); + } + + @Override + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spContainer = super.createSpContainer(); + EscherOptRecord opt = spContainer.getChildById(EscherOptRecord.RECORD_ID); + opt.removeEscherProperty(EscherProperties.TEXT__TEXTLEFT); + opt.removeEscherProperty(EscherProperties.TEXT__TEXTRIGHT); + opt.removeEscherProperty(EscherProperties.TEXT__TEXTTOP); + opt.removeEscherProperty(EscherProperties.TEXT__TEXTBOTTOM); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.GROUPSHAPE__PRINT, false, false, 655362)); + return spContainer; + } + + @Override + protected ObjRecord createObjRecord() { + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType(OBJECT_TYPE_COMMENT); + c.setLocked(true); + c.setPrintable(true); + c.setAutofill(false); + c.setAutoline(true); + + NoteStructureSubRecord u = new NoteStructureSubRecord(); + EndSubRecord e = new EndSubRecord(); + obj.addSubRecord(c); + obj.addSubRecord(u); + obj.addSubRecord(e); + return obj; + } + + private NoteRecord createNoteRecord(){ + NoteRecord note = new NoteRecord(); + note.setFlags(NoteRecord.NOTE_HIDDEN); + note.setAuthor(""); + return note; + } + + @Override + void setShapeId(int shapeId) { + super.setShapeId(shapeId); + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) getObjRecord().getSubRecords().get(0); + cod.setObjectId((short) (shapeId % 1024)); + _note.setShapeId(shapeId % 1024); + } + /** * Returns whether this comment is visible. * * @param visible true if the comment is visible, false otherwise */ - public void setVisible(boolean visible){ - if(_note != null) { - _note.setFlags(visible ? NoteRecord.NOTE_VISIBLE : NoteRecord.NOTE_HIDDEN); - } - _visible = visible; + public void setVisible(boolean visible) { + _note.setFlags(visible ? NoteRecord.NOTE_VISIBLE : NoteRecord.NOTE_HIDDEN); } /** @@ -89,7 +137,7 @@ public class HSSFComment extends HSSFTextbox implements Comment { * @return true if the comment is visible, false otherwise */ public boolean isVisible() { - return _visible; + return _note.getFlags() == NoteRecord.NOTE_VISIBLE; } /** @@ -98,7 +146,7 @@ public class HSSFComment extends HSSFTextbox implements Comment { * @return the 0-based row of the cell that contains the comment */ public int getRow() { - return _row; + return _note.getRow(); } /** @@ -107,10 +155,7 @@ public class HSSFComment extends HSSFTextbox implements Comment { * @param row the 0-based row of the cell that contains the comment */ public void setRow(int row) { - if(_note != null) { - _note.setRow(row); - } - _row = row; + _note.setRow(row); } /** @@ -118,8 +163,8 @@ public class HSSFComment extends HSSFTextbox implements Comment { * * @return the 0-based column of the cell that contains the comment */ - public int getColumn(){ - return _col; + public int getColumn() { + return _note.getColumn(); } /** @@ -128,17 +173,15 @@ public class HSSFComment extends HSSFTextbox implements Comment { * @param col the 0-based column of the cell that contains the comment */ public void setColumn(int col) { - if(_note != null) { - _note.setColumn(col); - } - _col = col; + _note.setColumn(col); } + /** * @deprecated (Nov 2009) use {@link HSSFComment#setColumn(int)} } */ @Deprecated public void setColumn(short col) { - setColumn((int)col); + setColumn((int) col); } /** @@ -147,7 +190,7 @@ public class HSSFComment extends HSSFTextbox implements Comment { * @return the name of the original author of the comment */ public String getAuthor() { - return _author; + return _note.getAuthor(); } /** @@ -155,37 +198,57 @@ public class HSSFComment extends HSSFTextbox implements Comment { * * @param author the name of the original author of the comment */ - public void setAuthor(String author){ - if(_note != null) _note.setAuthor(author); - this._author = author; + public void setAuthor(String author) { + if (_note != null) _note.setAuthor(author); } - - /** - * Sets the rich text string used by this comment. - * - * @param string Sets the rich text string used by this object. - */ - public void setString(RichTextString string) { - HSSFRichTextString hstring = (HSSFRichTextString) string; - //if font is not set we must set the default one - if (hstring.numFormattingRuns() == 0) hstring.applyFont((short)0); - if (_txo != null) { - _txo.setStr(hstring); - } - super.setString(string); - } - /** * Returns the underlying Note record */ protected NoteRecord getNoteRecord() { - return _note; - } - /** - * Returns the underlying Text record - */ - protected TextObjectRecord getTextObjectRecord() { - return _txo; - } + return _note; + } + + @Override + public void setShapeType(int shapeType) { + throw new IllegalStateException("Shape type can not be changed in "+this.getClass().getSimpleName()); + } + + public void afterRemove(HSSFPatriarch patriarch){ + super.afterRemove(patriarch); + patriarch._getBoundAggregate().removeTailRecord(getNoteRecord()); + } + + @Override + protected HSSFShape cloneShape() { + TextObjectRecord txo = (TextObjectRecord) getTextObjectRecord().cloneViaReserialise(); + EscherContainerRecord spContainer = new EscherContainerRecord(); + byte [] inSp = getEscherContainer().serialize(); + spContainer.fillFields(inSp, 0, new DefaultEscherRecordFactory()); + ObjRecord obj = (ObjRecord) getObjRecord().cloneViaReserialise(); + NoteRecord note = (NoteRecord) getNoteRecord().cloneViaReserialise(); + return new HSSFComment(spContainer, obj, txo, note); + } + + public void setBackgroundImage(int pictureIndex){ + setPropertyValue(new EscherSimpleProperty( EscherProperties.FILL__PATTERNTEXTURE, false, true, pictureIndex)); + setPropertyValue(new EscherSimpleProperty( EscherProperties.FILL__FILLTYPE, false, false, FILL_TYPE_PICTURE)); + EscherBSERecord bse = getPatriarch().getSheet().getWorkbook().getWorkbook().getBSERecord(pictureIndex); + bse.setRef(bse.getRef() + 1); + } + + public void resetBackgroundImage(){ + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.FILL__PATTERNTEXTURE); + if (null != property){ + EscherBSERecord bse = getPatriarch().getSheet().getWorkbook().getWorkbook().getBSERecord(property.getPropertyValue()); + bse.setRef(bse.getRef() - 1); + getOptRecord().removeEscherProperty(EscherProperties.FILL__PATTERNTEXTURE); + } + setPropertyValue(new EscherSimpleProperty( EscherProperties.FILL__FILLTYPE, false, false, FILL_TYPE_SOLID)); + } + + public int getBackgroundImageId(){ + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.FILL__PATTERNTEXTURE); + return property == null ? 0 : property.getPropertyValue(); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java index 5b2a463f4..57451dd81 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFObjectData.java @@ -21,39 +21,28 @@ package org.apache.poi.hssf.usermodel; import java.io.IOException; import java.util.Iterator; -import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord; -import org.apache.poi.hssf.record.ObjRecord; -import org.apache.poi.hssf.record.SubRecord; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; import org.apache.poi.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.Entry; import org.apache.poi.util.HexDump; /** * Represents binary object (i.e. OLE) data stored in the file. Eg. A GIF, JPEG etc... + *

+ * Right now, 13, july, 2012 can not be created from scratch * * @author Daniel Noll */ -public final class HSSFObjectData { - /** - * Underlying object record ultimately containing a reference to the object. - */ - private final ObjRecord _record; - +public final class HSSFObjectData extends HSSFPicture { /** * Reference to the filesystem root, required for retrieving the object data. */ private final DirectoryEntry _root; - /** - * Constructs object data by wrapping a lower level object record. - * - * @param record the low-level object record. - * @param root the root of the filesystem, required for retrieving the object data. - */ - public HSSFObjectData(ObjRecord record, DirectoryEntry root) - { - _record = record; - _root = root; + public HSSFObjectData(EscherContainerRecord spContainer, ObjRecord objRecord, DirectoryEntry _root) { + super(spContainer, objRecord); + this._root = _root; } /** @@ -65,7 +54,7 @@ public final class HSSFObjectData { /** * Gets the object data. Only call for ones that have - * data though. See {@link #hasDirectoryEntry()} + * data though. See {@link #hasDirectoryEntry()} * * @return the object data as an OLE2 directory. * @throws IOException if there was an error reading the data. @@ -85,8 +74,8 @@ public final class HSSFObjectData { /** * Returns the data portion, for an ObjectData - * that doesn't have an associated POIFS Directory - * Entry + * that doesn't have an associated POIFS Directory + * Entry */ public byte[] getObjectData() { return findObjectRecord().getObjectData(); @@ -94,7 +83,7 @@ public final class HSSFObjectData { /** * Does this ObjectData have an associated POIFS - * Directory Entry? + * Directory Entry? * (Not all do, those that don't have a data portion) */ public boolean hasDirectoryEntry() { @@ -107,18 +96,51 @@ public final class HSSFObjectData { /** * Finds the EmbeddedObjectRefSubRecord, or throws an - * Exception if there wasn't one + * Exception if there wasn't one */ protected EmbeddedObjectRefSubRecord findObjectRecord() { - Iterator subRecordIter = _record.getSubRecords().iterator(); + Iterator subRecordIter = getObjRecord().getSubRecords().iterator(); while (subRecordIter.hasNext()) { Object subRecord = subRecordIter.next(); if (subRecord instanceof EmbeddedObjectRefSubRecord) { - return (EmbeddedObjectRefSubRecord)subRecord; + return (EmbeddedObjectRefSubRecord) subRecord; } } throw new IllegalStateException("Object data does not contain a reference to an embedded object OLE2 directory"); } + + @Override + protected EscherContainerRecord createSpContainer() { + throw new IllegalStateException("HSSFObjectData cannot be created from scratch"); + } + + @Override + protected ObjRecord createObjRecord() { + throw new IllegalStateException("HSSFObjectData cannot be created from scratch"); + } + + @Override + protected void afterRemove(HSSFPatriarch patriarch) { + throw new IllegalStateException("HSSFObjectData cannot be created from scratch"); + } + + @Override + void afterInsert(HSSFPatriarch patriarch) { + EscherAggregate agg = patriarch._getBoundAggregate(); + agg.associateShapeToObjRecord(getEscherContainer().getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + EscherBSERecord bse = + patriarch.getSheet().getWorkbook().getWorkbook().getBSERecord(getPictureIndex()); + bse.setRef(bse.getRef() + 1); + } + + @Override + protected HSSFShape cloneShape() { + EscherContainerRecord spContainer = new EscherContainerRecord(); + byte[] inSp = getEscherContainer().serialize(); + spContainer.fillFields(inSp, 0, new DefaultEscherRecordFactory()); + ObjRecord obj = (ObjRecord) getObjRecord().cloneViaReserialise(); + return new HSSFObjectData(spContainer, obj, _root); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index 672f82cc7..233a798dc 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -18,13 +18,12 @@ package org.apache.poi.hssf.usermodel; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; import java.util.List; -import org.apache.poi.ddf.EscherComplexProperty; -import org.apache.poi.ddf.EscherOptRecord; -import org.apache.poi.ddf.EscherProperty; -import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.record.EscherAggregate; import org.apache.poi.ss.usermodel.Chart; import org.apache.poi.util.StringUtil; @@ -40,41 +39,89 @@ import org.apache.poi.ss.usermodel.ClientAnchor; */ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { private final List _shapes = new ArrayList(); - private int _x1 = 0; - private int _y1 = 0 ; - private int _x2 = 1023; - private int _y2 = 255; + + private final EscherSpgrRecord _spgrRecord; + private final EscherContainerRecord _mainSpgrContainer; /** * The EscherAggregate we have been bound to. * (This will handle writing us out into records, - * and building up our shapes from the records) + * and building up our shapes from the records) */ private EscherAggregate _boundAggregate; - final HSSFSheet _sheet; // TODO make private + private final HSSFSheet _sheet; /** * Creates the patriarch. * * @param sheet the sheet this patriarch is stored in. */ - HSSFPatriarch(HSSFSheet sheet, EscherAggregate boundAggregate){ + HSSFPatriarch(HSSFSheet sheet, EscherAggregate boundAggregate) { _sheet = sheet; - _boundAggregate = boundAggregate; + _boundAggregate = boundAggregate; + _mainSpgrContainer = _boundAggregate.getEscherContainer().getChildContainers().get(0); + EscherContainerRecord spContainer = (EscherContainerRecord) _boundAggregate.getEscherContainer() + .getChildContainers().get(0).getChild(0); + _spgrRecord = spContainer.getChildById(EscherSpgrRecord.RECORD_ID); + buildShapeTree(); + } + + /** + * used to clone patriarch + * + * create patriarch from existing one + * @param patriarch - copy all the shapes from this patriarch to new one + * @param sheet where must be located new patriarch + * @return new patriarch with copies of all shapes from the existing patriarch + */ + static HSSFPatriarch createPatriarch(HSSFPatriarch patriarch, HSSFSheet sheet){ + HSSFPatriarch newPatriarch = new HSSFPatriarch(sheet, new EscherAggregate()); + newPatriarch.afterCreate(); + for (HSSFShape shape: patriarch.getChildren()){ + HSSFShape newShape; + if (shape instanceof HSSFShapeGroup){ + newShape = ((HSSFShapeGroup)shape).cloneShape(newPatriarch); + } else { + newShape = shape.cloneShape(); + } + newPatriarch.onCreate(newShape); + newPatriarch.addShape(newShape); + } + return newPatriarch; + } + + /** + * @param shape to be removed + * @return true of shape is removed + */ + public boolean removeShape(HSSFShape shape) { + boolean isRemoved = _mainSpgrContainer.removeChildRecord(shape.getEscherContainer()); + if (isRemoved){ + shape.afterRemove(this); + _shapes.remove(shape); + } + return isRemoved; + } + + void afterCreate() { + DrawingManager2 drawingManager = _sheet.getWorkbook().getWorkbook().getDrawingManager(); + short dgId = drawingManager.findNewDrawingGroupId(); + _boundAggregate.setDgId(dgId); + _boundAggregate.setMainSpRecordId(newShapeId()); + drawingManager.incrementDrawingsSaved(); } /** * Creates a new group record stored under this patriarch. * - * @param anchor the client anchor describes how this group is attached - * to the sheet. - * @return the newly created group. + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created group. */ - public HSSFShapeGroup createGroup(HSSFClientAnchor anchor) - { + public HSSFShapeGroup createGroup(HSSFClientAnchor anchor) { HSSFShapeGroup group = new HSSFShapeGroup(null, anchor); - group.anchor = anchor; addShape(group); + onCreate(group); return group; } @@ -82,84 +129,86 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { * Creates a simple shape. This includes such shapes as lines, rectangles, * and ovals. * - * @param anchor the client anchor describes how this group is attached - * to the sheet. - * @return the newly created shape. + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. */ - public HSSFSimpleShape createSimpleShape(HSSFClientAnchor anchor) - { + public HSSFSimpleShape createSimpleShape(HSSFClientAnchor anchor) { HSSFSimpleShape shape = new HSSFSimpleShape(null, anchor); - shape.anchor = anchor; addShape(shape); + //open existing file + onCreate(shape); return shape; } /** * Creates a picture. * - * @param anchor the client anchor describes how this group is attached - * to the sheet. - * @return the newly created shape. + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. */ - public HSSFPicture createPicture(HSSFClientAnchor anchor, int pictureIndex) - { + public HSSFPicture createPicture(HSSFClientAnchor anchor, int pictureIndex) { HSSFPicture shape = new HSSFPicture(null, anchor); - shape.setPictureIndex( pictureIndex ); - shape.anchor = anchor; + shape.setPictureIndex(pictureIndex); addShape(shape); - - EscherBSERecord bse = _sheet.getWorkbook().getWorkbook().getBSERecord(pictureIndex); - bse.setRef(bse.getRef() + 1); + //open existing file + onCreate(shape); return shape; } - public HSSFPicture createPicture(ClientAnchor anchor, int pictureIndex) - { - return createPicture((HSSFClientAnchor)anchor, pictureIndex); + /** + * + * @param anchor the client anchor describes how this picture is + * attached to the sheet. + * @param pictureIndex the index of the picture in the workbook collection + * of pictures. + * + * @return newly created shape + */ + public HSSFPicture createPicture(ClientAnchor anchor, int pictureIndex) { + return createPicture((HSSFClientAnchor) anchor, pictureIndex); } /** * Creates a polygon * - * @param anchor the client anchor describes how this group is attached - * to the sheet. - * @return the newly created shape. + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created shape. */ - public HSSFPolygon createPolygon(HSSFClientAnchor anchor) - { + public HSSFPolygon createPolygon(HSSFClientAnchor anchor) { HSSFPolygon shape = new HSSFPolygon(null, anchor); - shape.anchor = anchor; addShape(shape); + onCreate(shape); return shape; } /** * Constructs a textbox under the patriarch. * - * @param anchor the client anchor describes how this group is attached - * to the sheet. - * @return the newly created textbox. + * @param anchor the client anchor describes how this group is attached + * to the sheet. + * @return the newly created textbox. */ - public HSSFTextbox createTextbox(HSSFClientAnchor anchor) - { + public HSSFTextbox createTextbox(HSSFClientAnchor anchor) { HSSFTextbox shape = new HSSFTextbox(null, anchor); - shape.anchor = anchor; addShape(shape); + onCreate(shape); 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. + * @param anchor the client anchor describes how this comment is attached + * to the sheet. + * @return the newly created comment. */ - public HSSFComment createComment(HSSFAnchor anchor) - { + public HSSFComment createComment(HSSFAnchor anchor) { HSSFComment shape = new HSSFComment(null, anchor); - shape.anchor = anchor; addShape(shape); + onCreate(shape); return shape; } @@ -168,83 +217,116 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { * * @see org.apache.poi.hssf.usermodel.HSSFSheet#setAutoFilter(org.apache.poi.ss.util.CellRangeAddress) */ - HSSFSimpleShape createComboBox(HSSFAnchor anchor) - { - HSSFSimpleShape shape = new HSSFSimpleShape(null, anchor); - shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_COMBO_BOX); - shape.anchor = anchor; - addShape(shape); - return shape; - } + HSSFSimpleShape createComboBox(HSSFAnchor anchor) { + HSSFCombobox shape = new HSSFCombobox(null, anchor); + addShape(shape); + onCreate(shape); + return shape; + } public HSSFComment createCellComment(ClientAnchor anchor) { - return createComment((HSSFAnchor)anchor); + return createComment((HSSFAnchor) anchor); } /** - * Returns a list of all shapes contained by the patriarch. + * Returns a unmodifiable list of all shapes contained by the patriarch. */ - public List getChildren() - { - return _shapes; + public List getChildren() { + return Collections.unmodifiableList(_shapes); } /** * add a shape to this drawing */ @Internal - public void addShape(HSSFShape shape){ - shape._patriarch = this; + public void addShape(HSSFShape shape) { + shape.setPatriarch(this); _shapes.add(shape); } + private void onCreate(HSSFShape shape) { + EscherContainerRecord spgrContainer = + _boundAggregate.getEscherContainer().getChildContainers().get(0); + + EscherContainerRecord spContainer = shape.getEscherContainer(); + int shapeId = newShapeId(); + shape.setShapeId(shapeId); + + spgrContainer.addChildRecord(spContainer); + shape.afterInsert(this); + setFlipFlags(shape); + } + /** * Total count of all children and their children's children. + * @return count of shapes including shapes inside shape groups */ public int countOfAllChildren() { int count = _shapes.size(); - for (Iterator iterator = _shapes.iterator(); iterator.hasNext();) { + for (Iterator iterator = _shapes.iterator(); iterator.hasNext(); ) { HSSFShape shape = iterator.next(); count += shape.countOfAllChildren(); } return count; } + /** * Sets the coordinate space of this group. All children are constrained * to these coordinates. */ - public void setCoordinates(int x1, int y1, int x2, int y2){ - _x1 = x1; - _y1 = y1; - _x2 = x2; - _y2 = y2; + public void setCoordinates(int x1, int y1, int x2, int y2) { + _spgrRecord.setRectY1(y1); + _spgrRecord.setRectY2(y2); + _spgrRecord.setRectX1(x1); + _spgrRecord.setRectX2(x2); + } + + /** + * remove all shapes inside patriarch + */ + public void clear() { + ArrayList copy = new ArrayList(_shapes); + for (HSSFShape shape: copy){ + removeShape(shape); + } + } + + /** + * @return new unique shapeId + */ + int newShapeId() { + DrawingManager2 dm = _sheet.getWorkbook().getWorkbook().getDrawingManager(); + EscherDgRecord dg = + _boundAggregate.getEscherContainer().getChildById(EscherDgRecord.RECORD_ID); + short drawingGroupId = dg.getDrawingGroupId(); + return dm.allocateShapeId(drawingGroupId, dg); } /** * Does this HSSFPatriarch contain a chart? * (Technically a reference to a chart, since they - * get stored in a different block of records) + * get stored in a different block of records) * FIXME - detect chart in all cases (only seems - * to work on some charts so far) + * to work on some charts so far) */ public boolean containsChart() { // TODO - support charts properly in usermodel // We're looking for a EscherOptRecord EscherOptRecord optRecord = (EscherOptRecord) - _boundAggregate.findFirstWithId(EscherOptRecord.RECORD_ID); - if(optRecord == null) { + _boundAggregate.findFirstWithId(EscherOptRecord.RECORD_ID); + if (optRecord == null) { // No opt record, can't have chart return false; } - for(Iterator it = optRecord.getEscherProperties().iterator(); it.hasNext();) { + for (Iterator it = optRecord.getEscherProperties().iterator(); it.hasNext(); ) { EscherProperty prop = it.next(); - if(prop.getPropertyNumber() == 896 && prop.isComplex()) { - EscherComplexProperty cp = (EscherComplexProperty)prop; + if (prop.getPropertyNumber() == 896 && prop.isComplex()) { + EscherComplexProperty cp = (EscherComplexProperty) prop; String str = StringUtil.getFromUnicodeLE(cp.getComplexData()); - if(str.equals("Chart 1\0")) { + if (str.equals("Chart 1\0")) { return true; } } @@ -254,35 +336,31 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { } /** - * The top left x coordinate of this group. + * @return x coordinate of the left up corner */ - public int getX1() - { - return _x1; + public int getX1() { + return _spgrRecord.getRectX1(); } /** - * The top left y coordinate of this group. + * @return y coordinate of the left up corner */ - public int getY1() - { - return _y1; + public int getY1() { + return _spgrRecord.getRectY1(); } /** - * The bottom right x coordinate of this group. + * @return x coordinate of the right down corner */ - public int getX2() - { - return _x2; + public int getX2() { + return _spgrRecord.getRectX2(); } /** - * The bottom right y coordinate of this group. + * @return y coordinate of the right down corner */ - public int getY2() - { - return _y2; + public int getY2() { + return _spgrRecord.getRectY2(); } /** @@ -306,12 +384,51 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { * @param row2 the row (0 based) of the second cell. * @return the newly created client anchor */ - public HSSFClientAnchor createAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2){ - return new HSSFClientAnchor(dx1, dy1, dx2, dy2, (short)col1, row1, (short)col2, row2); + public HSSFClientAnchor createAnchor(int dx1, int dy1, int dx2, int dy2, int col1, int row1, int col2, int row2) { + return new HSSFClientAnchor(dx1, dy1, dx2, dy2, (short) col1, row1, (short) col2, row2); } - public Chart createChart(ClientAnchor anchor) { - throw new RuntimeException("NotImplemented"); - } + public Chart createChart(ClientAnchor anchor) { + throw new RuntimeException("NotImplemented"); + } + + /** + * create shape tree from existing escher records tree + */ + void buildShapeTree() { + EscherContainerRecord dgContainer = _boundAggregate.getEscherContainer(); + if (dgContainer == null) { + return; + } + EscherContainerRecord spgrConrainer = dgContainer.getChildContainers().get(0); + List spgrChildren = spgrConrainer.getChildContainers(); + + for (int i = 0; i < spgrChildren.size(); i++) { + EscherContainerRecord spContainer = spgrChildren.get(i); + if (i == 0) { + continue; + } else { + HSSFShapeFactory.createShapeTree(spContainer, _boundAggregate, this, _sheet.getWorkbook().getRootDirectory()); + } + } + } + + private void setFlipFlags(HSSFShape shape){ + EscherSpRecord sp = shape.getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + if (shape.getAnchor().isHorizontallyFlipped()) { + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPHORIZ); + } + if (shape.getAnchor().isVerticallyFlipped()) { + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPVERT); + } + } + + public Iterator iterator() { + return _shapes.iterator(); + } + + protected HSSFSheet getSheet() { + return _sheet; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPicture.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPicture.java index d4a7205c7..1f20d9f0f 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPicture.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPicture.java @@ -19,9 +19,12 @@ package org.apache.poi.hssf.usermodel; import java.awt.Dimension; import java.io.ByteArrayInputStream; +import java.io.UnsupportedEncodingException; -import org.apache.poi.ddf.EscherBSERecord; -import org.apache.poi.ddf.EscherBlipRecord; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.ObjRecord; import org.apache.poi.ss.usermodel.Picture; import org.apache.poi.ss.util.ImageUtils; import org.apache.poi.hssf.model.InternalWorkbook; @@ -32,7 +35,7 @@ import org.apache.poi.hssf.model.InternalWorkbook; * @author Glen Stampoultzis * @author Yegor Kozlov (yegor at apache.org) */ -public final class HSSFPicture extends HSSFSimpleShape implements Picture { +public class HSSFPicture extends HSSFSimpleShape implements Picture { public static final int PICTURE_TYPE_EMF = HSSFWorkbook.PICTURE_TYPE_EMF; // Windows Enhanced Metafile public static final int PICTURE_TYPE_WMF = HSSFWorkbook.PICTURE_TYPE_WMF; // Windows Metafile public static final int PICTURE_TYPE_PICT = HSSFWorkbook.PICTURE_TYPE_PICT; // Macintosh PICT @@ -54,7 +57,9 @@ public final class HSSFPicture extends HSSFSimpleShape implements Picture { */ private static final int PX_ROW = 15; - private int _pictureIndex; + public HSSFPicture(EscherContainerRecord spContainer, ObjRecord objRecord) { + super(spContainer, objRecord); + } /** * Constructs a picture object. @@ -62,17 +67,33 @@ public final class HSSFPicture extends HSSFSimpleShape implements Picture { public HSSFPicture( HSSFShape parent, HSSFAnchor anchor ) { super( parent, anchor ); - setShapeType(OBJECT_TYPE_PICTURE); + super.setShapeType(OBJECT_TYPE_PICTURE); + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) getObjRecord().getSubRecords().get(0); + cod.setObjectType(CommonObjectDataSubRecord.OBJECT_TYPE_PICTURE); } public int getPictureIndex() { - return _pictureIndex; + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.BLIP__BLIPTODISPLAY); + if (null == property){ + return -1; + } + return property.getPropertyValue(); } public void setPictureIndex( int pictureIndex ) { - this._pictureIndex = pictureIndex; + setPropertyValue(new EscherSimpleProperty( EscherProperties.BLIP__BLIPTODISPLAY, false, true, pictureIndex)); + } + + @Override + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spContainer = super.createSpContainer(); + EscherOptRecord opt = spContainer.getChildById(EscherOptRecord.RECORD_ID); + opt.removeEscherProperty(EscherProperties.LINESTYLE__LINEDASHING); + opt.removeEscherProperty(EscherProperties.LINESTYLE__NOLINEDRAWDASH); + spContainer.removeChildRecord(spContainer.getChildById(EscherTextboxRecord.RECORD_ID)); + return spContainer; } /** @@ -145,8 +166,8 @@ public final class HSSFPicture extends HSSFSimpleShape implements Picture { float w = 0; //space in the leftmost cell - w += getColumnWidthInPixels(anchor.col1)*(1 - (float)anchor.dx1/1024); - short col2 = (short)(anchor.col1 + 1); + w += getColumnWidthInPixels(anchor.getCol1())*(1 - (float)anchor.getDx1()/1024); + short col2 = (short)(anchor.getCol1() + 1); int dx2 = 0; while(w < scaledWidth){ @@ -160,12 +181,12 @@ public final class HSSFPicture extends HSSFSimpleShape implements Picture { double delta = w - scaledWidth; dx2 = (int)((cw-delta)/cw*1024); } - anchor.col2 = col2; - anchor.dx2 = dx2; + anchor.setCol2(col2); + anchor.setDx2(dx2); float h = 0; - h += (1 - (float)anchor.dy1/256)* getRowHeightInPixels(anchor.row1); - int row2 = anchor.row1 + 1; + h += (1 - (float)anchor.getDy1()/256)* getRowHeightInPixels(anchor.getRow1()); + int row2 = anchor.getRow1() + 1; int dy2 = 0; while(h < scaledHeight){ @@ -177,15 +198,15 @@ public final class HSSFPicture extends HSSFSimpleShape implements Picture { double delta = h - scaledHeight; dy2 = (int)((ch-delta)/ch*256); } - anchor.row2 = row2; - anchor.dy2 = dy2; + anchor.setRow2(row2); + anchor.setDy2(dy2); return anchor; } private float getColumnWidthInPixels(int column){ - int cw = _patriarch._sheet.getColumnWidth(column); + int cw = getPatriarch().getSheet().getColumnWidth(column); float px = getPixelWidth(column); return cw/px; @@ -193,18 +214,18 @@ public final class HSSFPicture extends HSSFSimpleShape implements Picture { private float getRowHeightInPixels(int i){ - HSSFRow row = _patriarch._sheet.getRow(i); + HSSFRow row = getPatriarch().getSheet().getRow(i); float height; if(row != null) height = row.getHeight(); - else height = _patriarch._sheet.getDefaultRowHeight(); + else height = getPatriarch().getSheet().getDefaultRowHeight(); return height/PX_ROW; } private float getPixelWidth(int column){ - int def = _patriarch._sheet.getDefaultColumnWidth()*256; - int cw = _patriarch._sheet.getColumnWidth(column); + int def = getPatriarch().getSheet().getDefaultColumnWidth()*256; + int cw = getPatriarch().getSheet().getColumnWidth(column); return cw == def ? PX_DEFAULT : PX_MODIFIED; } @@ -215,7 +236,7 @@ public final class HSSFPicture extends HSSFSimpleShape implements Picture { * @return image dimension */ public Dimension getImageDimension(){ - EscherBSERecord bse = _patriarch._sheet._book.getBSERecord(_pictureIndex); + EscherBSERecord bse = getPatriarch().getSheet()._book.getBSERecord(getPictureIndex()); byte[] data = bse.getBlipRecord().getPicturedata(); int type = bse.getBlipTypeWin32(); return ImageUtils.getImageDimension(new ByteArrayInputStream(data), type); @@ -227,8 +248,56 @@ public final class HSSFPicture extends HSSFSimpleShape implements Picture { * @return picture data for this shape */ public HSSFPictureData getPictureData(){ - InternalWorkbook iwb = _patriarch._sheet.getWorkbook().getWorkbook(); - EscherBlipRecord blipRecord = iwb.getBSERecord(_pictureIndex).getBlipRecord(); + InternalWorkbook iwb = getPatriarch().getSheet().getWorkbook().getWorkbook(); + EscherBlipRecord blipRecord = iwb.getBSERecord(getPictureIndex()).getBlipRecord(); return new HSSFPictureData(blipRecord); } + + @Override + void afterInsert(HSSFPatriarch patriarch) { + EscherAggregate agg = patriarch._getBoundAggregate(); + agg.associateShapeToObjRecord(getEscherContainer().getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + EscherBSERecord bse = + patriarch.getSheet().getWorkbook().getWorkbook().getBSERecord(getPictureIndex()); + bse.setRef(bse.getRef() + 1); + } + + /** + * The color applied to the lines of this shape. + */ + public String getFileName() { + EscherComplexProperty propFile = (EscherComplexProperty) getOptRecord().lookup( + EscherProperties.BLIP__BLIPFILENAME); + try { + if (null == propFile){ + return ""; + } + return new String(propFile.getComplexData(), "UTF-16LE").trim(); + } catch (UnsupportedEncodingException e) { + return ""; + } + } + + public void setFileName(String data){ + try { + EscherComplexProperty prop = new EscherComplexProperty(EscherProperties.BLIP__BLIPFILENAME, true, data.getBytes("UTF-16LE")); + setPropertyValue(prop); + } catch (UnsupportedEncodingException e) { + System.out.println("Unsupported encoding: UTF-16LE"); + } + } + + @Override + public void setShapeType(int shapeType) { + throw new IllegalStateException("Shape type can not be changed in "+this.getClass().getSimpleName()); + } + + @Override + protected HSSFShape cloneShape() { + EscherContainerRecord spContainer = new EscherContainerRecord(); + byte [] inSp = getEscherContainer().serialize(); + spContainer.fillFields(inSp, 0, new DefaultEscherRecordFactory()); + ObjRecord obj = (ObjRecord) getObjRecord().cloneViaReserialise(); + return new HSSFPicture(spContainer, obj); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java index 6027fb8be..98936f90c 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPolygon.java @@ -17,45 +17,184 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; +import org.apache.poi.util.LittleEndian; + /** * @author Glen Stampoultzis (glens at superlinksoftware.com) */ -public class HSSFPolygon - extends HSSFShape -{ - int[] xPoints; - int[] yPoints; - int drawAreaWidth = 100; - int drawAreaHeight = 100; +public class HSSFPolygon extends HSSFSimpleShape { - HSSFPolygon( HSSFShape parent, HSSFAnchor anchor ) - { - super( parent, anchor ); + public final static short OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING = 0x1E; + + public HSSFPolygon(EscherContainerRecord spContainer, ObjRecord objRecord, TextObjectRecord _textObjectRecord) { + super(spContainer, objRecord, _textObjectRecord); } - public int[] getXPoints() - { - return xPoints; + public HSSFPolygon(EscherContainerRecord spContainer, ObjRecord objRecord) { + super(spContainer, objRecord); } - public int[] getYPoints() - { - return yPoints; + HSSFPolygon(HSSFShape parent, HSSFAnchor anchor) { + super(parent, anchor); } - public void setPoints(int[] xPoints, int[] yPoints) - { - this.xPoints = cloneArray(xPoints); - this.yPoints = cloneArray(yPoints); + @Override + protected TextObjectRecord createTextObjRecord() { + return null; } - private int[] cloneArray( int[] a ) - { - int[] result = new int[a.length]; - for ( int i = 0; i < a.length; i++ ) - result[i] = a[i]; + /** + * Generates the shape records for this shape. + */ + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); - return result; + spContainer.setRecordId(EscherContainerRecord.SP_CONTAINER); + spContainer.setOptions((short) 0x000F); + sp.setRecordId(EscherSpRecord.RECORD_ID); + sp.setOptions((short) ((EscherAggregate.ST_NOT_PRIMATIVE << 4) | 0x2)); + if (getParent() == null) { + sp.setFlags(EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE); + } else { + sp.setFlags(EscherSpRecord.FLAG_CHILD | EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE); + } + opt.setRecordId(EscherOptRecord.RECORD_ID); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.TRANSFORM__ROTATION, false, false, 0)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__RIGHT, false, false, 100)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__BOTTOM, false, false, 100)); + opt.setEscherProperty(new EscherShapePathProperty(EscherProperties.GEOMETRY__SHAPEPATH, EscherShapePathProperty.COMPLEX)); + + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.GEOMETRY__FILLOK, false, false, 0x00010001)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINESTARTARROWHEAD, false, false, 0x0)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDARROWHEAD, false, false, 0x0)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDCAPSTYLE, false, false, 0x0)); + + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, LINESTYLE_SOLID)); + opt.setEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080008)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, LINEWIDTH_DEFAULT)); + opt.setEscherProperty(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, FILL__FILLCOLOR_DEFAULT)); + opt.setEscherProperty(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, LINESTYLE__COLOR_DEFAULT)); + opt.setEscherProperty(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, 1)); + + opt.setEscherProperty(new EscherBoolProperty( EscherProperties.GROUPSHAPE__PRINT, 0x080000)); + + EscherRecord anchor = getAnchor().getEscherAnchor(); + clientData.setRecordId(EscherClientDataRecord.RECORD_ID); + clientData.setOptions((short) 0x0000); + + spContainer.addChildRecord(sp); + spContainer.addChildRecord(opt); + spContainer.addChildRecord(anchor); + spContainer.addChildRecord(clientData); + + return spContainer; + } + + /** + * Creates the low level OBJ record for this shape. + */ + protected ObjRecord createObjRecord() { + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType(OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING); + c.setLocked(true); + c.setPrintable(true); + c.setAutofill(true); + c.setAutoline(true); + EndSubRecord e = new EndSubRecord(); + obj.addSubRecord(c); + obj.addSubRecord(e); + return obj; + } + + @Override + protected void afterRemove(HSSFPatriarch patriarch) { + patriarch._getBoundAggregate().removeShapeToObjRecord(getEscherContainer().getChildById(EscherClientDataRecord.RECORD_ID)); + } + + /** + * @return array of x coordinates + */ + public int[] getXPoints() { + EscherArrayProperty verticesProp = getOptRecord().lookup(EscherProperties.GEOMETRY__VERTICES); + if (null == verticesProp){ + return new int[]{}; + } + int []array = new int[verticesProp.getNumberOfElementsInArray()-1]; + for (int i=0; i< verticesProp.getNumberOfElementsInArray()-1; i++){ + byte[] property = verticesProp.getElement(i); + short x = LittleEndian.getShort(property, 0); + array[i] = x; + } + return array; + } + + /** + * @return array of y coordinates + */ + public int[] getYPoints() { + EscherArrayProperty verticesProp = getOptRecord().lookup(EscherProperties.GEOMETRY__VERTICES); + if (null == verticesProp){ + return new int[]{}; + } + int []array = new int[verticesProp.getNumberOfElementsInArray()-1]; + for (int i=0; i< verticesProp.getNumberOfElementsInArray()-1; i++){ + byte[] property = verticesProp.getElement(i); + short x = LittleEndian.getShort(property, 2); + array[i] = x; + } + return array; + } + + /** + * @param xPoints - array of x coordinates + * @param yPoints - array of y coordinates + */ + public void setPoints(int[] xPoints, int[] yPoints) { + if (xPoints.length != yPoints.length){ + System.out.println("xPoint.length must be equal to yPoints.length"); + return; + } + if (xPoints.length == 0){ + System.out.println("HSSFPolygon must have at least one point"); + } + EscherArrayProperty verticesProp = new EscherArrayProperty(EscherProperties.GEOMETRY__VERTICES, false, new byte[0] ); + verticesProp.setNumberOfElementsInArray(xPoints.length+1); + verticesProp.setNumberOfElementsInMemory(xPoints.length+1); + verticesProp.setSizeOfElements(0xFFF0); + for (int i = 0; i < xPoints.length; i++) + { + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)xPoints[i]); + LittleEndian.putShort(data, 2, (short)yPoints[i]); + verticesProp.setElement(i, data); + } + int point = xPoints.length; + byte[] data = new byte[4]; + LittleEndian.putShort(data, 0, (short)xPoints[0]); + LittleEndian.putShort(data, 2, (short)yPoints[0]); + verticesProp.setElement(point, data); + setPropertyValue(verticesProp); + + EscherArrayProperty segmentsProp = new EscherArrayProperty(EscherProperties.GEOMETRY__SEGMENTINFO, false, null ); + segmentsProp.setSizeOfElements(0x0002); + segmentsProp.setNumberOfElementsInArray(xPoints.length * 2 + 4); + segmentsProp.setNumberOfElementsInMemory(xPoints.length * 2 + 4); + segmentsProp.setElement(0, new byte[] { (byte)0x00, (byte)0x40 } ); + segmentsProp.setElement(1, new byte[] { (byte)0x00, (byte)0xAC } ); + for (int i = 0; i < xPoints.length; i++) + { + segmentsProp.setElement(2 + i * 2, new byte[] { (byte)0x01, (byte)0x00 } ); + segmentsProp.setElement(3 + i * 2, new byte[] { (byte)0x00, (byte)0xAC } ); + } + segmentsProp.setElement(segmentsProp.getNumberOfElementsInArray() - 2, new byte[] { (byte)0x01, (byte)0x60 } ); + segmentsProp.setElement(segmentsProp.getNumberOfElementsInArray() - 1, new byte[] { (byte)0x00, (byte)0x80 } ); + setPropertyValue(segmentsProp); } /** @@ -63,21 +202,24 @@ public class HSSFPolygon * @param width * @param height */ - public void setPolygonDrawArea( int width, int height ) - { - this.drawAreaWidth = width; - this.drawAreaHeight = height; + public void setPolygonDrawArea(int width, int height) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.GEOMETRY__RIGHT, width)); + setPropertyValue(new EscherSimpleProperty(EscherProperties.GEOMETRY__BOTTOM, height)); } - public int getDrawAreaWidth() - { - return drawAreaWidth; + /** + * @return shape width + */ + public int getDrawAreaWidth() { + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.GEOMETRY__RIGHT); + return property == null ? 100: property.getPropertyValue(); } - public int getDrawAreaHeight() - { - return drawAreaHeight; + /** + * @return shape height + */ + public int getDrawAreaHeight() { + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.GEOMETRY__BOTTOM); + return property == null ? 100: property.getPropertyValue(); } - - } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java index 3c586340f..f20239567 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java @@ -17,6 +17,15 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.util.LittleEndian; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Iterator; + /** * An abstract shape. * @@ -25,6 +34,9 @@ package org.apache.poi.hssf.usermodel; public abstract class HSSFShape { public static final int LINEWIDTH_ONE_PT = 12700; public static final int LINEWIDTH_DEFAULT = 9525; + public static final int LINESTYLE__COLOR_DEFAULT = 0x08000040; + public static final int FILL__FILLCOLOR_DEFAULT = 0x08000009; + public static final boolean NO_FILL_DEFAULT = true; public static final int LINESTYLE_SOLID = 0; // Solid (continuous) pen public static final int LINESTYLE_DASHSYS = 1; // PS_DASH system dash style @@ -39,38 +51,98 @@ public abstract class HSSFShape { public static final int LINESTYLE_LONGDASHDOTDOTGEL = 10; // long dash short dash short dash public static final int LINESTYLE_NONE = -1; + public static final int LINESTYLE_DEFAULT = LINESTYLE_NONE; + // TODO - make all these fields private - final HSSFShape parent; + private HSSFShape parent; HSSFAnchor anchor; - HSSFPatriarch _patriarch; - private int _lineStyleColor = 0x08000040; - int _fillColor = 0x08000009; - private int _lineWidth = LINEWIDTH_DEFAULT; // 12700 = 1pt - private int _lineStyle = LINESTYLE_SOLID; - private boolean _noFill = false; + private HSSFPatriarch _patriarch; + + private final EscherContainerRecord _escherContainer; + private final ObjRecord _objRecord; + private final EscherOptRecord _optRecord; + + public final static int NO_FILLHITTEST_TRUE = 0x00110000; + public final static int NO_FILLHITTEST_FALSE = 0x00010000; + + /** + * creates shapes from existing file + * @param spContainer + * @param objRecord + */ + public HSSFShape(EscherContainerRecord spContainer, ObjRecord objRecord) { + this._escherContainer = spContainer; + this._objRecord = objRecord; + this._optRecord = spContainer.getChildById(EscherOptRecord.RECORD_ID); + this.anchor = HSSFAnchor.createAnchorFromEscher(spContainer); + } /** * Create a new shape with the specified parent and anchor. */ - HSSFShape( HSSFShape parent, HSSFAnchor anchor ) - { + public HSSFShape(HSSFShape parent, HSSFAnchor anchor) { this.parent = parent; this.anchor = anchor; + this._escherContainer = createSpContainer(); + _optRecord = _escherContainer.getChildById(EscherOptRecord.RECORD_ID); + _objRecord = createObjRecord(); + + } + + protected abstract EscherContainerRecord createSpContainer(); + + protected abstract ObjRecord createObjRecord(); + + /** + * remove escher container from the patriarch.escherAggregate + * remove obj, textObj and note records if it's necessary + * in case of ShapeGroup remove all contained shapes + * @param patriarch + */ + protected abstract void afterRemove(HSSFPatriarch patriarch); + + /** + * @param shapeId - global shapeId which must be set to EscherSpRecord + */ + void setShapeId(int shapeId){ + EscherSpRecord spRecord = _escherContainer.getChildById(EscherSpRecord.RECORD_ID); + spRecord.setShapeId(shapeId); + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) _objRecord.getSubRecords().get(0); + cod.setObjectId((short) (shapeId%1024)); + } + + /** + * @return global shapeId(from EscherSpRecord) + */ + int getShapeId(){ + return ((EscherSpRecord)_escherContainer.getChildById(EscherSpRecord.RECORD_ID)).getShapeId(); + } + + abstract void afterInsert(HSSFPatriarch patriarch); + + protected EscherContainerRecord getEscherContainer() { + return _escherContainer; + } + + protected ObjRecord getObjRecord() { + return _objRecord; + } + + protected EscherOptRecord getOptRecord() { + return _optRecord; } /** * Gets the parent shape. */ - public HSSFShape getParent() - { + public HSSFShape getParent() { return parent; } /** - * @return the anchor that is used by this shape. + * @return the anchor that is used by this shape. */ - public HSSFAnchor getAnchor() - { + public HSSFAnchor getAnchor() { return anchor; } @@ -78,26 +150,49 @@ public abstract class HSSFShape { * Sets a particular anchor. A top-level shape must have an anchor of * HSSFClientAnchor. A child anchor must have an anchor of HSSFChildAnchor * - * @param anchor the anchor to use. - * @throws IllegalArgumentException when the wrong anchor is used for - * this particular shape. - * + * @param anchor the anchor to use. + * @throws IllegalArgumentException when the wrong anchor is used for + * this particular shape. * @see HSSFChildAnchor * @see HSSFClientAnchor */ - public void setAnchor( HSSFAnchor anchor ) - { - if ( parent == null ) - { - if ( anchor instanceof HSSFChildAnchor ) - throw new IllegalArgumentException( "Must use client anchors for shapes directly attached to sheet." ); + public void setAnchor(HSSFAnchor anchor) { + int i = 0; + int recordId = -1; + if (parent == null) { + if (anchor instanceof HSSFChildAnchor) + throw new IllegalArgumentException("Must use client anchors for shapes directly attached to sheet."); + EscherClientAnchorRecord anch = _escherContainer.getChildById(EscherClientAnchorRecord.RECORD_ID); + if (null != anch) { + for (i=0; i< _escherContainer.getChildRecords().size(); i++){ + if (_escherContainer.getChild(i).getRecordId() == EscherClientAnchorRecord.RECORD_ID){ + if (i != _escherContainer.getChildRecords().size() -1){ + recordId = _escherContainer.getChild(i+1).getRecordId(); + } + } + } + _escherContainer.removeChildRecord(anch); + } + } else { + if (anchor instanceof HSSFClientAnchor) + throw new IllegalArgumentException("Must use child anchors for shapes attached to groups."); + EscherChildAnchorRecord anch = _escherContainer.getChildById(EscherChildAnchorRecord.RECORD_ID); + if (null != anch) { + for (i=0; i< _escherContainer.getChildRecords().size(); i++){ + if (_escherContainer.getChild(i).getRecordId() == EscherChildAnchorRecord.RECORD_ID){ + if (i != _escherContainer.getChildRecords().size() -1){ + recordId = _escherContainer.getChild(i+1).getRecordId(); + } + } + } + _escherContainer.removeChildRecord(anch); + } } - else - { - if ( anchor instanceof HSSFClientAnchor ) - throw new IllegalArgumentException( "Must use child anchors for shapes attached to groups." ); + if (-1 == recordId){ + _escherContainer.addChildRecord(anchor.getEscherAnchor()); + } else { + _escherContainer.addChildBefore(anchor.getEscherAnchor(), recordId); } - this.anchor = anchor; } @@ -105,92 +200,181 @@ public abstract class HSSFShape { * The color applied to the lines of this shape. */ public int getLineStyleColor() { - return _lineStyleColor; + EscherRGBProperty rgbProperty = _optRecord.lookup(EscherProperties.LINESTYLE__COLOR); + return rgbProperty == null ? LINESTYLE__COLOR_DEFAULT : rgbProperty.getRgbColor(); } /** * The color applied to the lines of this shape. */ public void setLineStyleColor(int lineStyleColor) { - _lineStyleColor = lineStyleColor; + setPropertyValue(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, lineStyleColor)); } /** * The color applied to the lines of this shape. */ public void setLineStyleColor(int red, int green, int blue) { - this._lineStyleColor = ((blue) << 16) | ((green) << 8) | red; + int lineStyleColor = ((blue) << 16) | ((green) << 8) | red; + setPropertyValue(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, lineStyleColor)); } /** * The color used to fill this shape. */ - public int getFillColor() - { - return _fillColor; + public int getFillColor() { + EscherRGBProperty rgbProperty = _optRecord.lookup(EscherProperties.FILL__FILLCOLOR); + return rgbProperty == null ? FILL__FILLCOLOR_DEFAULT : rgbProperty.getRgbColor(); } /** * The color used to fill this shape. */ public void setFillColor(int fillColor) { - _fillColor = fillColor; + setPropertyValue(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, fillColor)); } /** * The color used to fill this shape. */ - public void setFillColor( int red, int green, int blue ) - { - this._fillColor = ((blue) << 16) | ((green) << 8) | red; + public void setFillColor(int red, int green, int blue) { + int fillColor = ((blue) << 16) | ((green) << 8) | red; + setPropertyValue(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, fillColor)); } /** - * @return returns with width of the line in EMUs. 12700 = 1 pt. + * @return returns with width of the line in EMUs. 12700 = 1 pt. */ public int getLineWidth() { - return _lineWidth; + EscherSimpleProperty property = _optRecord.lookup(EscherProperties.LINESTYLE__LINEWIDTH); + return property == null ? LINEWIDTH_DEFAULT: property.getPropertyValue(); } /** * Sets the width of the line. 12700 = 1 pt. * * @param lineWidth width in EMU's. 12700EMU's = 1 pt - * * @see HSSFShape#LINEWIDTH_ONE_PT */ public void setLineWidth(int lineWidth) { - _lineWidth = lineWidth; + setPropertyValue(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, lineWidth)); } /** * @return One of the constants in LINESTYLE_* */ public int getLineStyle() { - return _lineStyle; + EscherSimpleProperty property = _optRecord.lookup(EscherProperties.LINESTYLE__LINEDASHING); + if (null == property){ + return LINESTYLE_DEFAULT; + } + return property.getPropertyValue(); } /** * Sets the line style. * - * @param lineStyle One of the constants in LINESTYLE_* + * @param lineStyle One of the constants in LINESTYLE_* */ public void setLineStyle(int lineStyle) { - _lineStyle = lineStyle; + setPropertyValue(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, lineStyle)); + if (getLineStyle() != HSSFShape.LINESTYLE_SOLID) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEENDCAPSTYLE, 0)); + if (getLineStyle() == HSSFShape.LINESTYLE_NONE){ + setPropertyValue(new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080000)); + } else { + setPropertyValue( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080008)); + } + } } /** * @return true if this shape is not filled with a color. */ public boolean isNoFill() { - return _noFill; + EscherBoolProperty property = _optRecord.lookup(EscherProperties.FILL__NOFILLHITTEST); + return property == null ? NO_FILL_DEFAULT : property.getPropertyValue() == NO_FILLHITTEST_TRUE; } /** - * Sets whether this shape is filled or transparent. + * @param noFill sets whether this shape is filled or transparent. */ public void setNoFill(boolean noFill) { - _noFill = noFill; + setPropertyValue(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, noFill ? NO_FILLHITTEST_TRUE : NO_FILLHITTEST_FALSE)); + } + + protected void setPropertyValue(EscherProperty property){ + _optRecord.setEscherProperty(property); + } + + /** + * @param value specifies whether this shape is vertically flipped. + */ + public void setFlipVertical(boolean value){ + EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + if (value){ + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPVERT); + } else { + sp.setFlags(sp.getFlags() & (Integer.MAX_VALUE - EscherSpRecord.FLAG_FLIPVERT)); + } + } + + /** + * @param value specifies whether this shape is horizontally flipped. + */ + public void setFlipHorizontal(boolean value){ + EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + if (value){ + sp.setFlags(sp.getFlags() | EscherSpRecord.FLAG_FLIPHORIZ); + } else { + sp.setFlags(sp.getFlags() & (Integer.MAX_VALUE - EscherSpRecord.FLAG_FLIPHORIZ)); + } + } + + /** + * @return whether this shape is vertically flipped. + */ + public boolean isFlipVertical(){ + EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + return (sp.getFlags() & EscherSpRecord.FLAG_FLIPVERT) != 0; + } + + /** + * @return whether this shape is horizontally flipped. + */ + public boolean isFlipHorizontal(){ + EscherSpRecord sp = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + return (sp.getFlags() & EscherSpRecord.FLAG_FLIPHORIZ) != 0; + } + + /** + * @return the rotation, in degrees, that is applied to a shape. + */ + public int getRotationDegree(){ + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.TRANSFORM__ROTATION); + if (null == property){ + return 0; + } + try { + LittleEndian.putInt(property.getPropertyValue(), bos); + return LittleEndian.getShort(bos.toByteArray(), 2); + } catch (IOException e) { + e.printStackTrace(); + return 0; + } + } + + /** + * specifies the rotation, in degrees, that is applied to a shape. + * Positive values specify rotation in the clockwise direction. + * Negative values specify rotation in the counterclockwise direction. + * Rotation occurs around the center of the shape. + * The default value for this property is 0x00000000 + * @param value + */ + public void setRotationDegree(short value){ + setPropertyValue(new EscherSimpleProperty(EscherProperties.TRANSFORM__ROTATION , (value << 16))); } /** @@ -199,4 +383,18 @@ public abstract class HSSFShape { public int countOfAllChildren() { return 1; } + + protected abstract HSSFShape cloneShape(); + + protected void setPatriarch(HSSFPatriarch _patriarch) { + this._patriarch = _patriarch; + } + + public HSSFPatriarch getPatriarch() { + return _patriarch; + } + + protected void setParent(HSSFShape parent) { + this.parent = parent; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java index 99e6a5de6..c240519ef 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java @@ -24,11 +24,50 @@ import java.util.List; * * @author Glen Stampoultzis (glens at apache.org) */ -public interface HSSFShapeContainer +public interface HSSFShapeContainer extends Iterable { /** * @return Any children contained by this shape. */ - List getChildren(); + List getChildren(); + /** + * add shape to the list of child records + * @param shape + */ + public void addShape(HSSFShape shape); + + /** + * set coordinates of this group relative to the parent + */ + void setCoordinates( int x1, int y1, int x2, int y2 ); + + void clear(); + + /** + *@return The top left x coordinate of this group. + */ + public int getX1(); + + /** + *@return The top left y coordinate of this group. + */ + public int getY1(); + + /** + *@return The bottom right x coordinate of this group. + */ + public int getX2(); + + /** + * @return The bottom right y coordinate of this group. + */ + public int getY2(); + + /** + * remove first level shapes + * @param shape to be removed + * @return true if shape is removed else return false + */ + public boolean removeShape(HSSFShape shape); } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java new file mode 100644 index 000000000..7955da3bc --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java @@ -0,0 +1,131 @@ +/* + * 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.ddf.*; +import org.apache.poi.hssf.record.*; +import org.apache.poi.poifs.filesystem.DirectoryNode; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * @author Evgeniy Berlog + * date: 05.06.12 + */ +public class HSSFShapeFactory { + + private final static short OBJECT_TYPE_LINE = 1; + private final static short OBJECT_TYPE_RECTANGLE = 2; + private final static short OBJECT_TYPE_OVAL = 3; + private final static short OBJECT_TYPE_ARC = 4; + private final static short OBJECT_TYPE_PICTURE = 8; + + /** + * build shape tree from escher container + * @param container root escher container from which escher records must be taken + * @param agg - EscherAggregate + * @param out - shape container to which shapes must be added + * @param root - node to create HSSFObjectData shapes + */ + public static void createShapeTree(EscherContainerRecord container, EscherAggregate agg, HSSFShapeContainer out, DirectoryNode root) { + if (container.getRecordId() == EscherContainerRecord.SPGR_CONTAINER) { + ObjRecord obj = null; + EscherClientDataRecord clientData = ((EscherContainerRecord) container.getChild(0)).getChildById(EscherClientDataRecord.RECORD_ID); + if (null != clientData) { + obj = (ObjRecord) agg.getShapeToObjMapping().get(clientData); + } + HSSFShapeGroup group = new HSSFShapeGroup(container, obj); + List children = container.getChildContainers(); + // skip the first child record, it is group descriptor + for (int i = 0; i < children.size(); i++) { + EscherContainerRecord spContainer = children.get(i); + if (i != 0) { + createShapeTree(spContainer, agg, group, root); + } + } + out.addShape(group); + } else if (container.getRecordId() == EscherContainerRecord.SP_CONTAINER) { + Map shapeToObj = agg.getShapeToObjMapping(); + ObjRecord objRecord = null; + TextObjectRecord txtRecord = null; + + for (EscherRecord record : container.getChildRecords()) { + switch (record.getRecordId()) { + case EscherClientDataRecord.RECORD_ID: + objRecord = (ObjRecord) shapeToObj.get(record); + break; + case EscherTextboxRecord.RECORD_ID: + txtRecord = (TextObjectRecord) shapeToObj.get(record); + break; + } + } + if (isEmbeddedObject(objRecord)) { + HSSFObjectData objectData = new HSSFObjectData(container, objRecord, root); + out.addShape(objectData); + return; + } + CommonObjectDataSubRecord cmo = (CommonObjectDataSubRecord) objRecord.getSubRecords().get(0); + HSSFShape shape; + switch (cmo.getObjectType()) { + case CommonObjectDataSubRecord.OBJECT_TYPE_PICTURE: + shape = new HSSFPicture(container, objRecord); + break; + case CommonObjectDataSubRecord.OBJECT_TYPE_RECTANGLE: + shape = new HSSFSimpleShape(container, objRecord, txtRecord); + break; + case CommonObjectDataSubRecord.OBJECT_TYPE_LINE: + shape = new HSSFSimpleShape(container, objRecord); + break; + case CommonObjectDataSubRecord.OBJECT_TYPE_COMBO_BOX: + shape = new HSSFCombobox(container, objRecord); + break; + case CommonObjectDataSubRecord.OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING: + EscherOptRecord optRecord = container.getChildById(EscherOptRecord.RECORD_ID); + EscherProperty property = optRecord.lookup(EscherProperties.GEOMETRY__VERTICES); + if (null != property) { + shape = new HSSFPolygon(container, objRecord, txtRecord); + } else { + shape = new HSSFSimpleShape(container, objRecord, txtRecord); + } + break; + case CommonObjectDataSubRecord.OBJECT_TYPE_TEXT: + shape = new HSSFTextbox(container, objRecord, txtRecord); + break; + case CommonObjectDataSubRecord.OBJECT_TYPE_COMMENT: + shape = new HSSFComment(container, objRecord, txtRecord, agg.getNoteRecordByObj(objRecord)); + break; + default: + shape = new HSSFSimpleShape(container, objRecord, txtRecord); + } + out.addShape(shape); + } + } + + private static boolean isEmbeddedObject(ObjRecord obj) { + Iterator subRecordIter = obj.getSubRecords().iterator(); + while (subRecordIter.hasNext()) { + SubRecord sub = subRecordIter.next(); + if (sub instanceof EmbeddedObjectRefSubRecord) { + return true; + } + } + return false; + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java index 905dad3fc..9f60d6eae 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java @@ -17,7 +17,11 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; + import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Iterator; @@ -27,159 +31,357 @@ import java.util.Iterator; * * @author Glen Stampoultzis (glens at apache.org) */ -public class HSSFShapeGroup - extends HSSFShape - implements HSSFShapeContainer -{ - List shapes = new ArrayList(); - int x1 = 0; - int y1 = 0 ; - int x2 = 1023; - int y2 = 255; +public class HSSFShapeGroup extends HSSFShape implements HSSFShapeContainer { + private final List shapes = new ArrayList(); + private EscherSpgrRecord _spgrRecord; + public HSSFShapeGroup(EscherContainerRecord spgrContainer, ObjRecord objRecord) { + super(spgrContainer, objRecord); - public HSSFShapeGroup( HSSFShape parent, HSSFAnchor anchor ) - { - super( parent, anchor ); + // read internal and external coordinates from spgrContainer + EscherContainerRecord spContainer = spgrContainer.getChildContainers().get(0); + _spgrRecord = (EscherSpgrRecord) spContainer.getChild(0); + for (EscherRecord ch : spContainer.getChildRecords()) { + switch (ch.getRecordId()) { + case EscherSpgrRecord.RECORD_ID: + break; + case EscherClientAnchorRecord.RECORD_ID: + anchor = new HSSFClientAnchor((EscherClientAnchorRecord) ch); + break; + case EscherChildAnchorRecord.RECORD_ID: + anchor = new HSSFChildAnchor((EscherChildAnchorRecord) ch); + break; + } + } + } + + public HSSFShapeGroup(HSSFShape parent, HSSFAnchor anchor) { + super(parent, anchor); + _spgrRecord = ((EscherContainerRecord)getEscherContainer().getChild(0)).getChildById(EscherSpgrRecord.RECORD_ID); + } + + @Override + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spgrContainer = new EscherContainerRecord(); + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpgrRecord spgr = new EscherSpgrRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherRecord anchor; + EscherClientDataRecord clientData = new EscherClientDataRecord(); + + spgrContainer.setRecordId(EscherContainerRecord.SPGR_CONTAINER); + spgrContainer.setOptions((short) 0x000F); + spContainer.setRecordId(EscherContainerRecord.SP_CONTAINER); + spContainer.setOptions((short) 0x000F); + spgr.setRecordId(EscherSpgrRecord.RECORD_ID); + spgr.setOptions((short) 0x0001); + spgr.setRectX1(0); + spgr.setRectY1(0); + spgr.setRectX2(1023); + spgr.setRectY2(255); + sp.setRecordId(EscherSpRecord.RECORD_ID); + sp.setOptions((short) 0x0002); + if (getAnchor() instanceof HSSFClientAnchor) { + sp.setFlags(EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR); + } else { + sp.setFlags(EscherSpRecord.FLAG_GROUP | EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_CHILD); + } + opt.setRecordId(EscherOptRecord.RECORD_ID); + opt.setOptions((short) 0x0023); + opt.addEscherProperty(new EscherBoolProperty(EscherProperties.PROTECTION__LOCKAGAINSTGROUPING, 0x00040004)); + opt.addEscherProperty(new EscherBoolProperty(EscherProperties.GROUPSHAPE__PRINT, 0x00080000)); + + anchor = getAnchor().getEscherAnchor(); + clientData.setRecordId(EscherClientDataRecord.RECORD_ID); + clientData.setOptions((short) 0x0000); + + spgrContainer.addChildRecord(spContainer); + spContainer.addChildRecord(spgr); + spContainer.addChildRecord(sp); + spContainer.addChildRecord(opt); + spContainer.addChildRecord(anchor); + spContainer.addChildRecord(clientData); + return spgrContainer; + } + + @Override + protected ObjRecord createObjRecord() { + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord cmo = new CommonObjectDataSubRecord(); + cmo.setObjectType(CommonObjectDataSubRecord.OBJECT_TYPE_GROUP); + cmo.setLocked(true); + cmo.setPrintable(true); + cmo.setAutofill(true); + cmo.setAutoline(true); + GroupMarkerSubRecord gmo = new GroupMarkerSubRecord(); + EndSubRecord end = new EndSubRecord(); + obj.addSubRecord(cmo); + obj.addSubRecord(gmo); + obj.addSubRecord(end); + return obj; + } + + @Override + protected void afterRemove(HSSFPatriarch patriarch) { + patriarch._getBoundAggregate().removeShapeToObjRecord(getEscherContainer().getChildContainers().get(0) + .getChildById(EscherClientDataRecord.RECORD_ID)); + for ( int i=0; i getChildren() - { - return shapes; + public List getChildren() { + return Collections.unmodifiableList(shapes); } /** * Sets the coordinate space of this group. All children are constrained * to these coordinates. */ - public void setCoordinates( int x1, int y1, int x2, int y2 ) - { - this.x1 = x1; - this.y1 = y1; - this.x2 = x2; - this.y2 = y2; + public void setCoordinates(int x1, int y1, int x2, int y2) { + _spgrRecord.setRectX1(x1); + _spgrRecord.setRectX2(x2); + _spgrRecord.setRectY1(y1); + _spgrRecord.setRectY2(y2); + } + + public void clear() { + ArrayList copy = new ArrayList(shapes); + for (HSSFShape shape: copy){ + removeShape(shape); + } } /** * The top left x coordinate of this group. */ - public int getX1() - { - return x1; + public int getX1() { + return _spgrRecord.getRectX1(); } /** * The top left y coordinate of this group. */ - public int getY1() - { - return y1; + public int getY1() { + return _spgrRecord.getRectY1(); } /** * The bottom right x coordinate of this group. */ - public int getX2() - { - return x2; + public int getX2() { + return _spgrRecord.getRectX2(); } /** * The bottom right y coordinate of this group. */ - public int getY2() - { - return y2; + public int getY2() { + return _spgrRecord.getRectY2(); } /** * Count of all children and their childrens children. */ - public int countOfAllChildren() - { + public int countOfAllChildren() { int count = shapes.size(); - for ( Iterator iterator = shapes.iterator(); iterator.hasNext(); ) - { + for (Iterator iterator = shapes.iterator(); iterator.hasNext(); ) { HSSFShape shape = (HSSFShape) iterator.next(); count += shape.countOfAllChildren(); } return count; } + + @Override + void afterInsert(HSSFPatriarch patriarch){ + EscherAggregate agg = patriarch._getBoundAggregate(); + EscherContainerRecord containerRecord = getEscherContainer().getChildById(EscherContainerRecord.SP_CONTAINER); + agg.associateShapeToObjRecord(containerRecord.getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + } + + @Override + void setShapeId(int shapeId){ + EscherContainerRecord containerRecord = getEscherContainer().getChildById(EscherContainerRecord.SP_CONTAINER); + EscherSpRecord spRecord = containerRecord.getChildById(EscherSpRecord.RECORD_ID); + spRecord.setShapeId(shapeId); + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) getObjRecord().getSubRecords().get(0); + cod.setObjectId((short) (shapeId % 1024)); + } + + @Override + int getShapeId(){ + EscherContainerRecord containerRecord = getEscherContainer().getChildById(EscherContainerRecord.SP_CONTAINER); + return ((EscherSpRecord)containerRecord.getChildById(EscherSpRecord.RECORD_ID)).getShapeId(); + } + + @Override + protected HSSFShape cloneShape() { + throw new IllegalStateException("Use method cloneShape(HSSFPatriarch patriarch)"); + } + + protected HSSFShape cloneShape(HSSFPatriarch patriarch) { + EscherContainerRecord spgrContainer = new EscherContainerRecord(); + spgrContainer.setRecordId(EscherContainerRecord.SPGR_CONTAINER); + spgrContainer.setOptions((short) 0x000F); + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherContainerRecord cont = getEscherContainer().getChildById(EscherContainerRecord.SP_CONTAINER); + byte [] inSp = cont.serialize(); + spContainer.fillFields(inSp, 0, new DefaultEscherRecordFactory()); + + spgrContainer.addChildRecord(spContainer); + ObjRecord obj = null; + if (null != getObjRecord()){ + obj = (ObjRecord) getObjRecord().cloneViaReserialise(); + } + + HSSFShapeGroup group = new HSSFShapeGroup(spgrContainer, obj); + group.setPatriarch(patriarch); + + for (HSSFShape shape: getChildren()){ + HSSFShape newShape; + if (shape instanceof HSSFShapeGroup){ + newShape = ((HSSFShapeGroup)shape).cloneShape(patriarch); + } else { + newShape = shape.cloneShape(); + } + group.addShape(newShape); + group.onCreate(newShape); + } + return group; + } + + public boolean removeShape(HSSFShape shape) { + boolean isRemoved = getEscherContainer().removeChildRecord(shape.getEscherContainer()); + if (isRemoved){ + shape.afterRemove(this.getPatriarch()); + shapes.remove(shape); + } + return isRemoved; + } + + public Iterator iterator() { + return shapes.iterator(); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeTypes.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeTypes.java new file mode 100644 index 000000000..d72b4003d --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeTypes.java @@ -0,0 +1,224 @@ +/* ==================================================================== + 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; + +public interface HSSFShapeTypes { + public static final int NotPrimitive = 0; + public static final int Rectangle = 1; + public static final int RoundRectangle = 2; + public static final int Ellipse = 3; + public static final int Diamond = 4; + public static final int IsocelesTriangle = 5; + public static final int RightTriangle = 6; + public static final int Parallelogram = 7; + public static final int Trapezoid = 8; + public static final int Hexagon = 9; + public static final int Octagon = 10; + public static final int Plus = 11; + public static final int Star = 12; + public static final int Arrow = 13; + public static final int ThickArrow = 14; + public static final int HomePlate = 15; + public static final int Cube = 16; + public static final int Balloon = 17; + public static final int Seal = 18; + public static final int Arc = 19; + public static final int Line = 20; + public static final int Plaque = 21; + public static final int Can = 22; + public static final int Donut = 23; + public static final int TextSimple = 24; + public static final int TextOctagon = 25; + public static final int TextHexagon = 26; + public static final int TextCurve = 27; + public static final int TextWave = 28; + public static final int TextRing = 29; + public static final int TextOnCurve = 30; + public static final int TextOnRing = 31; + public static final int StraightConnector1 = 32; + public static final int BentConnector2 = 33; + public static final int BentConnector3 = 34; + public static final int BentConnector4 = 35; + public static final int BentConnector5 = 36; + public static final int CurvedConnector2 = 37; + public static final int CurvedConnector3 = 38; + public static final int CurvedConnector4 = 39; + public static final int CurvedConnector5 = 40; + public static final int Callout1 = 41; + public static final int Callout2 = 42; + public static final int Callout3 = 43; + public static final int AccentCallout1 = 44; + public static final int AccentCallout2 = 45; + public static final int AccentCallout3 = 46; + public static final int BorderCallout1 = 47; + public static final int BorderCallout2 = 48; + public static final int BorderCallout3 = 49; + public static final int AccentBorderCallout1 = 50; + public static final int AccentBorderCallout2 = 51; + public static final int AccentBorderCallout3 = 52; + public static final int Ribbon = 53; + public static final int Ribbon2 = 54; + public static final int Chevron = 55; + public static final int Pentagon = 56; + public static final int NoSmoking = 57; + public static final int Star8 = 58; + public static final int Star16 = 59; + public static final int Star32 = 60; + public static final int WedgeRectCallout = 61; + public static final int WedgeRRectCallout = 62; + public static final int WedgeEllipseCallout = 63; + public static final int Wave = 64; + public static final int FoldedCorner = 65; + public static final int LeftArrow = 66; + public static final int DownArrow = 67; + public static final int UpArrow = 68; + public static final int LeftRightArrow = 69; + public static final int UpDownArrow = 70; + public static final int IrregularSeal1 = 71; + public static final int IrregularSeal2 = 72; + public static final int LightningBolt = 73; + public static final int Heart = 74; + public static final int PictureFrame = 75; + public static final int QuadArrow = 76; + public static final int LeftArrowCallout = 77; + public static final int RightArrowCallout = 78; + public static final int UpArrowCallout = 79; + public static final int DownArrowCallout = 80; + public static final int LeftRightArrowCallout = 81; + public static final int UpDownArrowCallout = 82; + public static final int QuadArrowCallout = 83; + public static final int Bevel = 84; + public static final int LeftBracket = 85; + public static final int RightBracket = 86; + public static final int LeftBrace = 87; + public static final int RightBrace = 88; + public static final int LeftUpArrow = 89; + public static final int BentUpArrow = 90; + public static final int BentArrow = 91; + public static final int Star24 = 92; + public static final int StripedRightArrow = 93; + public static final int NotchedRightArrow = 94; + public static final int BlockArc = 95; + public static final int SmileyFace = 96; + public static final int VerticalScroll = 97; + public static final int HorizontalScroll = 98; + public static final int CircularArrow = 99; + public static final int NotchedCircularArrow = 100; + public static final int UturnArrow = 101; + public static final int CurvedRightArrow = 102; + public static final int CurvedLeftArrow = 103; + public static final int CurvedUpArrow = 104; + public static final int CurvedDownArrow = 105; + public static final int CloudCallout = 106; + public static final int EllipseRibbon = 107; + public static final int EllipseRibbon2 = 108; + public static final int FlowChartProcess = 109; + public static final int FlowChartDecision = 110; + public static final int FlowChartInputOutput = 111; + public static final int FlowChartPredefinedProcess = 112; + public static final int FlowChartInternalStorage = 113; + public static final int FlowChartDocument = 114; + public static final int FlowChartMultidocument = 115; + public static final int FlowChartTerminator = 116; + public static final int FlowChartPreparation = 117; + public static final int FlowChartManualInput = 118; + public static final int FlowChartManualOperation = 119; + public static final int FlowChartConnector = 120; + public static final int FlowChartPunchedCard = 121; + public static final int FlowChartPunchedTape = 122; + public static final int FlowChartSummingJunction = 123; + public static final int FlowChartOr = 124; + public static final int FlowChartCollate = 125; + public static final int FlowChartSort = 126; + public static final int FlowChartExtract = 127; + public static final int FlowChartMerge = 128; + public static final int FlowChartOfflineStorage = 129; + public static final int FlowChartOnlineStorage = 130; + public static final int FlowChartMagneticTape = 131; + public static final int FlowChartMagneticDisk = 132; + public static final int FlowChartMagneticDrum = 133; + public static final int FlowChartDisplay = 134; + public static final int FlowChartDelay = 135; + public static final int TextPlainText = 136; + public static final int TextStop = 137; + public static final int TextTriangle = 138; + public static final int TextTriangleInverted = 139; + public static final int TextChevron = 140; + public static final int TextChevronInverted = 141; + public static final int TextRingInside = 142; + public static final int TextRingOutside = 143; + public static final int TextArchUpCurve = 144; + public static final int TextArchDownCurve = 145; + public static final int TextCircleCurve = 146; + public static final int TextButtonCurve = 147; + public static final int TextArchUpPour = 148; + public static final int TextArchDownPour = 149; + public static final int TextCirclePour = 150; + public static final int TextButtonPour = 151; + public static final int TextCurveUp = 152; + public static final int TextCurveDown = 153; + public static final int TextCascadeUp = 154; + public static final int TextCascadeDown = 155; + public static final int TextWave1 = 156; + public static final int TextWave2 = 157; + public static final int TextWave3 = 158; + public static final int TextWave4 = 159; + public static final int TextInflate = 160; + public static final int TextDeflate = 161; + public static final int TextInflateBottom = 162; + public static final int TextDeflateBottom = 163; + public static final int TextInflateTop = 164; + public static final int TextDeflateTop = 165; + public static final int TextDeflateInflate = 166; + public static final int TextDeflateInflateDeflate = 167; + public static final int TextFadeRight = 168; + public static final int TextFadeLeft = 169; + public static final int TextFadeUp = 170; + public static final int TextFadeDown = 171; + public static final int TextSlantUp = 172; + public static final int TextSlantDown = 173; + public static final int TextCanUp = 174; + public static final int TextCanDown = 175; + public static final int FlowChartAlternateProcess = 176; + public static final int FlowChartOffpageConnector = 177; + public static final int Callout90 = 178; + public static final int AccentCallout90 = 179; + public static final int BorderCallout90 = 180; + public static final int AccentBorderCallout90 = 181; + public static final int LeftRightUpArrow = 182; + public static final int Sun = 183; + public static final int Moon = 184; + public static final int BracketPair = 185; + public static final int BracePair = 186; + public static final int Star4 = 187; + public static final int DoubleWave = 188; + public static final int ActionButtonBlank = 189; + public static final int ActionButtonHome = 190; + public static final int ActionButtonHelp = 191; + public static final int ActionButtonInformation = 192; + public static final int ActionButtonForwardNext = 193; + public static final int ActionButtonBackPrevious = 194; + public static final int ActionButtonEnd = 195; + public static final int ActionButtonBeginning = 196; + public static final int ActionButtonReturn = 197; + public static final int ActionButtonDocument = 198; + public static final int ActionButtonSound = 199; + public static final int ActionButtonMovie = 200; + public static final int HostControl = 201; + public static final int TextBox = 202; +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index bcb71781c..bb3c92ced 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -24,6 +24,7 @@ import java.util.List; import java.util.TreeMap; import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.model.HSSFFormulaParser; import org.apache.poi.hssf.model.InternalSheet; import org.apache.poi.hssf.model.InternalWorkbook; @@ -54,14 +55,15 @@ import org.apache.poi.util.POILogger; /** * High level representation of a worksheet. - * @author Andrew C. Oliver (acoliver at apache dot org) - * @author Glen Stampoultzis (glens at apache.org) - * @author Libin Roman (romal at vistaportal.com) - * @author Shawn Laubach (slaubach at apache dot org) (Just a little) - * @author Jean-Pierre Paris (jean-pierre.paris at m4x dot org) (Just a little, too) - * @author Yegor Kozlov (yegor at apache.org) (Autosizing columns) - * @author Josh Micich - * @author Petr Udalau(Petr.Udalau at exigenservices.com) - set/remove array formulas + * + * @author Andrew C. Oliver (acoliver at apache dot org) + * @author Glen Stampoultzis (glens at apache.org) + * @author Libin Roman (romal at vistaportal.com) + * @author Shawn Laubach (slaubach at apache dot org) (Just a little) + * @author Jean-Pierre Paris (jean-pierre.paris at m4x dot org) (Just a little, too) + * @author Yegor Kozlov (yegor at apache.org) (Autosizing columns) + * @author Josh Micich + * @author Petr Udalau(Petr.Udalau at exigenservices.com) - set/remove array formulas */ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { private static final POILogger log = POILogFactory.getLogger(HSSFSheet.class); @@ -78,7 +80,9 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * reference to the low level {@link InternalSheet} object */ private final InternalSheet _sheet; - /** stores rows by zero-based row number */ + /** + * stores rows by zero-based row number + */ private final TreeMap _rows; protected final InternalWorkbook _book; protected final HSSFWorkbook _workbook; @@ -105,7 +109,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * called by HSSFWorkbook when reading in an exisiting file. * * @param workbook - The HSSF Workbook object associated with the sheet. - * @param sheet - lowlevel Sheet object this sheet will represent + * @param sheet - lowlevel Sheet object this sheet will represent * @see org.apache.poi.hssf.usermodel.HSSFWorkbook#createSheet() */ protected HSSFSheet(HSSFWorkbook workbook, InternalSheet sheet) { @@ -117,7 +121,19 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { } HSSFSheet cloneSheet(HSSFWorkbook workbook) { - return new HSSFSheet(workbook, _sheet.cloneSheet()); + this.getDrawingPatriarch();/**Aggregate drawing records**/ + HSSFSheet sheet = new HSSFSheet(workbook, _sheet.cloneSheet()); + int pos = sheet._sheet.findFirstRecordLocBySid(DrawingRecord.sid); + DrawingRecord dr = (DrawingRecord) sheet._sheet.findFirstRecordBySid(DrawingRecord.sid); + if (null != dr) { + sheet._sheet.getRecords().remove(dr); + } + if (getDrawingPatriarch() != null) { + HSSFPatriarch patr = HSSFPatriarch.createPatriarch(this.getDrawingPatriarch(), sheet); + sheet._sheet.getRecords().add(pos, patr._getBoundAggregate()); + sheet._patriarch = patr; + } + return sheet; } /** @@ -125,7 +141,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * * @return the parent workbook */ - public HSSFWorkbook getWorkbook(){ + public HSSFWorkbook getWorkbook() { return _workbook; } @@ -135,7 +151,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { private void setPropertiesFromSheet(InternalSheet sheet) { RowRecord row = sheet.getNextRow(); - boolean rowRecordsAlreadyPresent = row!=null; + boolean rowRecordsAlreadyPresent = row != null; while (row != null) { createRowFromRecord(row); @@ -146,9 +162,9 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { Iterator iter = sheet.getCellValueIterator(); long timestart = System.currentTimeMillis(); - if (log.check( POILogger.DEBUG )) + if (log.check(POILogger.DEBUG)) log.log(DEBUG, "Time at start of cell creating in HSSF sheet = ", - Long.valueOf(timestart)); + Long.valueOf(timestart)); HSSFRow lastrow = null; // Add every cell to its row @@ -159,7 +175,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { HSSFRow hrow = lastrow; if (hrow == null || hrow.getRowNum() != cval.getRow()) { - hrow = getRow( cval.getRow() ); + hrow = getRow(cval.getRow()); lastrow = hrow; if (hrow == null) { // Some tools (like Perl module Spreadsheet::WriteExcel - bug 41187) skip the RowRecords @@ -174,29 +190,28 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { hrow = createRowFromRecord(rowRec); } } - if (log.check( POILogger.DEBUG )) - log.log( DEBUG, "record id = " + Integer.toHexString( ( (Record) cval ).getSid() ) ); - hrow.createCellFromRecord( cval ); - if (log.check( POILogger.DEBUG )) - log.log( DEBUG, "record took ", - Long.valueOf( System.currentTimeMillis() - cellstart ) ); + if (log.check(POILogger.DEBUG)) + log.log(DEBUG, "record id = " + Integer.toHexString(((Record) cval).getSid())); + hrow.createCellFromRecord(cval); + if (log.check(POILogger.DEBUG)) + log.log(DEBUG, "record took ", + Long.valueOf(System.currentTimeMillis() - cellstart)); } - if (log.check( POILogger.DEBUG )) + if (log.check(POILogger.DEBUG)) log.log(DEBUG, "total sheet cell creation took ", - Long.valueOf(System.currentTimeMillis() - timestart)); + Long.valueOf(System.currentTimeMillis() - timestart)); } /** * Create a new row within the sheet and return the high level representation * - * @param rownum row number + * @param rownum row number * @return High level HSSFRow object representing a row in the sheet * @see org.apache.poi.hssf.usermodel.HSSFRow * @see #removeRow(org.apache.poi.ss.usermodel.Row) */ - public HSSFRow createRow(int rownum) - { + public HSSFRow createRow(int rownum) { HSSFRow row = new HSSFRow(_workbook, this, rownum); // new rows inherit default height from the sheet row.setHeight(getDefaultRowHeight()); @@ -208,12 +223,12 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Used internally to create a high level Row object from a low level row object. * USed when reading an existing file - * @param row low level record to represent as a high level Row and add to sheet + * + * @param row low level record to represent as a high level Row and add to sheet * @return HSSFRow high level representation */ - private HSSFRow createRowFromRecord(RowRecord row) - { + private HSSFRow createRowFromRecord(RowRecord row) { HSSFRow hrow = new HSSFRow(_workbook, this, row); addRow(hrow, false); @@ -223,17 +238,17 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Remove a row from this sheet. All cells contained in the row are removed as well * - * @param row representing a row to remove. + * @param row representing a row to remove. */ public void removeRow(Row row) { HSSFRow hrow = (HSSFRow) row; if (row.getSheet() != this) { throw new IllegalArgumentException("Specified row does not belong to this sheet"); } - for(Cell cell : row) { - HSSFCell xcell = (HSSFCell)cell; - if(xcell.isPartOfArrayFormulaGroup()){ - String msg = "Row[rownum="+row.getRowNum()+"] contains cell(s) included in a multi-cell array formula. You cannot change part of an array."; + for (Cell cell : row) { + HSSFCell xcell = (HSSFCell) cell; + if (xcell.isPartOfArrayFormulaGroup()) { + String msg = "Row[rownum=" + row.getRowNum() + "] contains cell(s) included in a multi-cell array formula. You cannot change part of an array."; xcell.notifyArrayFormulaChanging(msg); } } @@ -245,12 +260,10 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { //should not happen if the input argument is valid throw new IllegalArgumentException("Specified row does not belong to this sheet"); } - if (hrow.getRowNum() == getLastRowNum()) - { + if (hrow.getRowNum() == getLastRowNum()) { _lastrow = findLastRow(_lastrow); } - if (hrow.getRowNum() == getFirstRowNum()) - { + if (hrow.getRowNum() == getFirstRowNum()) { _firstrow = findFirstRow(_firstrow); } _sheet.removeRow(hrow.getRowRecord()); @@ -280,13 +293,11 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * used internally to refresh the "first row" when the first row is removed. */ - private int findFirstRow(int firstrow) - { + private int findFirstRow(int firstrow) { int rownum = firstrow + 1; HSSFRow r = getRow(rownum); - while (r == null && rownum <= getLastRowNum()) - { + while (r == null && rownum <= getLastRowNum()) { r = getRow(++rownum); } @@ -302,20 +313,16 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @param addLow whether to add the row to the low level model - false if its already there */ - private void addRow(HSSFRow row, boolean addLow) - { + private void addRow(HSSFRow row, boolean addLow) { _rows.put(Integer.valueOf(row.getRowNum()), row); - if (addLow) - { + if (addLow) { _sheet.addRow(row.getRowRecord()); } boolean firstRow = _rows.size() == 1; - if (row.getRowNum() > getLastRowNum() || firstRow) - { + if (row.getRowNum() > getLastRowNum() || firstRow) { _lastrow = row.getRowNum(); } - if (row.getRowNum() < getFirstRowNum() || firstRow) - { + if (row.getRowNum() < getFirstRowNum() || firstRow) { _firstrow = row.getRowNum(); } } @@ -323,7 +330,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Returns the logical row (not physical) 0-based. If you ask for a row that is not * defined you get a null. This is to say row 4 represents the fifth row on a sheet. - * @param rowIndex row to get + * + * @param rowIndex row to get * @return HSSFRow representing the row number or null if its not defined on the sheet */ public HSSFRow getRow(int rowIndex) { @@ -339,6 +347,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Gets the first row on the sheet + * * @return the number of the first logical row on the sheet, zero based */ public int getFirstRowNum() { @@ -348,13 +357,14 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Gets the number last row on the sheet. * Owing to idiosyncrasies in the excel file - * format, if the result of calling this method - * is zero, you can't tell if that means there - * are zero rows on the sheet, or one at - * position zero. For that case, additionally - * call {@link #getPhysicalNumberOfRows()} to - * tell if there is a row at position zero - * or not. + * format, if the result of calling this method + * is zero, you can't tell if that means there + * are zero rows on the sheet, or one at + * position zero. For that case, additionally + * call {@link #getPhysicalNumberOfRows()} to + * tell if there is a row at position zero + * or not. + * * @return the number of the last row contained in this sheet, zero based. */ public int getLastRowNum() { @@ -363,17 +373,18 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Creates a data validation object + * * @param dataValidation The Data validation object settings */ public void addValidationData(DataValidation dataValidation) { - if (dataValidation == null) { - throw new IllegalArgumentException("objValidation must not be null"); - } - HSSFDataValidation hssfDataValidation = (HSSFDataValidation)dataValidation; - DataValidityTable dvt = _sheet.getOrCreateDataValidityTable(); + if (dataValidation == null) { + throw new IllegalArgumentException("objValidation must not be null"); + } + HSSFDataValidation hssfDataValidation = (HSSFDataValidation) dataValidation; + DataValidityTable dvt = _sheet.getOrCreateDataValidityTable(); - DVRecord dvRecord = hssfDataValidation.createDVRecord(this); - dvt.addDataValidation(dvRecord); + DVRecord dvRecord = hssfDataValidation.createDVRecord(this); + dvt.addDataValidation(dvRecord); } @@ -402,7 +413,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @deprecated (Sep 2008) use {@link #getColumnWidth(int)} */ public short getColumnWidth(short columnIndex) { - return (short)getColumnWidth(columnIndex & 0xFFFF); + return (short) getColumnWidth(columnIndex & 0xFFFF); } /** @@ -414,8 +425,9 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Get the visibility state for a given column. + * * @param columnIndex - the column to get (0-based) - * @param hidden - the visiblity state of the column + * @param hidden - the visiblity state of the column */ public void setColumnHidden(int columnIndex, boolean hidden) { _sheet.setColumnHidden(columnIndex, hidden); @@ -423,6 +435,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Get the hidden state for a given column. + * * @param columnIndex - the column to set (0-based) * @return hidden - false if the column is visible */ @@ -432,13 +445,13 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Set the width (in units of 1/256th of a character width) - * + *

*

* The maximum column width for an individual cell is 255 characters. * This value represents the number of characters that can be displayed * in a cell that is formatted with the standard font (first font in the workbook). *

- * + *

*

* Character width is defined as the maximum digit width * of the numbers 0, 1, 2, ... 9 as rendered @@ -447,7 +460,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * Unless you are using a very special font, the default character is '0' (zero), * this is true for Arial (default font font in HSSF) and Calibri (default font in XSSF) *

- * + *

*

* Please note, that the width set by this method includes 4 pixels of margin padding (two on each side), * plus 1 pixel padding for the gridlines (Section 3.3.1.12 of the OOXML spec). @@ -455,23 +468,23 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { *

*

* To compute the actual number of visible characters, - * Excel uses the following formula (Section 3.3.1.12 of the OOXML spec): + * Excel uses the following formula (Section 3.3.1.12 of the OOXML spec): *

* - * width = Truncate([{Number of Visible Characters} * - * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256 + * width = Truncate([{Number of Visible Characters} * + * {Maximum Digit Width} + {5 pixel padding}]/{Maximum Digit Width}*256)/256 * *

Using the Calibri font as an example, the maximum digit width of 11 point font size is 7 pixels (at 96 dpi). - * If you set a column width to be eight characters wide, e.g. setColumnWidth(columnIndex, 8*256), - * then the actual value of visible characters (the value shown in Excel) is derived from the following equation: - * - Truncate([numChars*7+5]/7*256)/256 = 8; - * - * - * which gives 7.29. + * If you set a column width to be eight characters wide, e.g. setColumnWidth(columnIndex, 8*256), + * then the actual value of visible characters (the value shown in Excel) is derived from the following equation: + * + * Truncate([numChars*7+5]/7*256)/256 = 8; + * + *

+ * which gives 7.29. * * @param columnIndex - the column to set (0-based) - * @param width - the width in units of 1/256th of a character width + * @param width - the width in units of 1/256th of a character width * @throws IllegalArgumentException if width > 255*256 (the maximum column width in Excel is 255 characters) */ public void setColumnWidth(int columnIndex, int width) { @@ -480,6 +493,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get the width (in units of 1/256th of a character width ) + * * @param columnIndex - the column to set (0-based) * @return width - the width in units of 1/256th of a character width */ @@ -490,14 +504,17 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get the default column width for the sheet (if the columns do not define their own width) in * characters + * * @return default column width */ public int getDefaultColumnWidth() { return _sheet.getDefaultColumnWidth(); } + /** * set the default column width for the sheet (if the columns do not define their own width) in * characters + * * @param width default column width */ public void setDefaultColumnWidth(int width) { @@ -508,7 +525,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get the default row height for the sheet (if the rows do not define their own height) in * twips (1/20 of a point) - * @return default row height + * + * @return default row height */ public short getDefaultRowHeight() { return _sheet.getDefaultRowHeight(); @@ -517,45 +535,45 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get the default row height for the sheet (if the rows do not define their own height) in * points. - * @return default row height in points + * + * @return default row height in points */ - public float getDefaultRowHeightInPoints() - { - return ((float)_sheet.getDefaultRowHeight() / 20); + public float getDefaultRowHeightInPoints() { + return ((float) _sheet.getDefaultRowHeight() / 20); } /** * set the default row height for the sheet (if the rows do not define their own height) in * twips (1/20 of a point) - * @param height default row height + * + * @param height default row height */ - public void setDefaultRowHeight(short height) - { + public void setDefaultRowHeight(short height) { _sheet.setDefaultRowHeight(height); } /** * set the default row height for the sheet (if the rows do not define their own height) in * points + * * @param height default row height */ - public void setDefaultRowHeightInPoints(float height) - { + public void setDefaultRowHeightInPoints(float height) { _sheet.setDefaultRowHeight((short) (height * 20)); } /** * Returns the HSSFCellStyle that applies to the given - * (0 based) column, or null if no style has been - * set for that column + * (0 based) column, or null if no style has been + * set for that column */ public HSSFCellStyle getColumnStyle(int column) { - short styleIndex = _sheet.getXFIndexForColAt((short)column); + short styleIndex = _sheet.getXFIndexForColAt((short) column); - if(styleIndex == 0xf) { + if (styleIndex == 0xf) { // None set return null; } @@ -566,55 +584,55 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get whether gridlines are printed. + * * @return true if printed */ - public boolean isGridsPrinted() - { + public boolean isGridsPrinted() { return _sheet.isGridsPrinted(); } /** * set whether gridlines printed. - * @param value false if not printed. + * + * @param value false if not printed. */ - public void setGridsPrinted(boolean value) - { + public void setGridsPrinted(boolean value) { _sheet.setGridsPrinted(value); } /** * @deprecated (Aug-2008) use CellRangeAddress instead of Region */ - public int addMergedRegion(org.apache.poi.ss.util.Region region) - { - return _sheet.addMergedRegion( region.getRowFrom(), + public int addMergedRegion(org.apache.poi.ss.util.Region region) { + return _sheet.addMergedRegion(region.getRowFrom(), region.getColumnFrom(), //(short) region.getRowTo(), region.getRowTo(), region.getColumnTo()); } + /** * adds a merged region of cells (hence those cells form one) + * * @param region (rowfrom/colfrom-rowto/colto) to merge * @return index of this region */ - public int addMergedRegion(CellRangeAddress region) - { + public int addMergedRegion(CellRangeAddress region) { region.validate(SpreadsheetVersion.EXCEL97); // throw IllegalStateException if the argument CellRangeAddress intersects with // a multi-cell array formula defined in this sheet validateArrayFormulas(region); - return _sheet.addMergedRegion( region.getFirstRow(), + return _sheet.addMergedRegion(region.getFirstRow(), region.getFirstColumn(), region.getLastRow(), region.getLastColumn()); } - private void validateArrayFormulas(CellRangeAddress region){ + private void validateArrayFormulas(CellRangeAddress region) { int firstRow = region.getFirstRow(); int firstColumn = region.getFirstColumn(); int lastRow = region.getLastRow(); @@ -625,13 +643,13 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { if (row == null) continue; HSSFCell cell = row.getCell(colIn); - if(cell == null) continue; + if (cell == null) continue; - if(cell.isPartOfArrayFormulaGroup()){ + if (cell.isPartOfArrayFormulaGroup()) { CellRangeAddress arrayRange = cell.getArrayFormulaRange(); if (arrayRange.getNumberOfCells() > 1 && - ( arrayRange.isInRange(region.getFirstRow(), region.getFirstColumn()) || - arrayRange.isInRange(region.getFirstRow(), region.getFirstColumn())) ){ + (arrayRange.isInRange(region.getFirstRow(), region.getFirstColumn()) || + arrayRange.isInRange(region.getFirstRow(), region.getFirstColumn()))) { String msg = "The range " + region.formatAsString() + " intersects with a multi-cell array formula. " + "You cannot merge cells of an array."; throw new IllegalStateException(msg); @@ -645,50 +663,51 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Control if Excel should be asked to recalculate all formulas on this sheet * when the workbook is opened. + *

+ *

+ * Calculating the formula values with {@link org.apache.poi.ss.usermodel.FormulaEvaluator} is the + * recommended solution, but this may be used for certain cases where + * evaluation in POI is not possible. + *

+ *

+ *

+ * It is recommended to force recalcuation of formulas on workbook level using + * {@link org.apache.poi.ss.usermodel.Workbook#setForceFormulaRecalculation(boolean)} + * to ensure that all cross-worksheet formuals and external dependencies are updated. + *

* - *

- * Calculating the formula values with {@link org.apache.poi.ss.usermodel.FormulaEvaluator} is the - * recommended solution, but this may be used for certain cases where - * evaluation in POI is not possible. - *

- * - *

- * It is recommended to force recalcuation of formulas on workbook level using - * {@link org.apache.poi.ss.usermodel.Workbook#setForceFormulaRecalculation(boolean)} - * to ensure that all cross-worksheet formuals and external dependencies are updated. - *

* @param value true if the application will perform a full recalculation of - * this worksheet values when the workbook is opened - * + * this worksheet values when the workbook is opened * @see org.apache.poi.ss.usermodel.Workbook#setForceFormulaRecalculation(boolean) */ - public void setForceFormulaRecalculation(boolean value) - { + public void setForceFormulaRecalculation(boolean value) { _sheet.setUncalced(value); } + /** * Whether a record must be inserted or not at generation to indicate that * formula must be recalculated when workbook is opened. + * * @return true if an uncalced record must be inserted or not at generation */ - public boolean getForceFormulaRecalculation() - { + public boolean getForceFormulaRecalculation() { return _sheet.getUncalced(); } /** * determines whether the output is vertically centered on the page. + * * @param value true to vertically center, false otherwise. */ - public void setVerticallyCenter(boolean value) - { + public void setVerticallyCenter(boolean value) { _sheet.getPageSettings().getVCenter().setVCenter(value); } /** * TODO: Boolean not needed, remove after next release + * * @deprecated (Mar-2008) use getVerticallyCenter() instead */ public boolean getVerticallyCenter(boolean value) { @@ -698,18 +717,17 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Determine whether printed output for this sheet will be vertically centered. */ - public boolean getVerticallyCenter() - { + public boolean getVerticallyCenter() { return _sheet.getPageSettings().getVCenter().getVCenter(); } /** * determines whether the output is horizontally centered on the page. + * * @param value true to horizontally center, false otherwise. */ - public void setHorizontallyCenter(boolean value) - { + public void setHorizontallyCenter(boolean value) { _sheet.getPageSettings().getHCenter().setHCenter(value); } @@ -717,8 +735,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * Determine whether printed output for this sheet will be horizontally centered. */ - public boolean getHorizontallyCenter() - { + public boolean getHorizontallyCenter() { return _sheet.getPageSettings().getHCenter().getHCenter(); } @@ -728,8 +745,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * * @param value true for right to left, false otherwise. */ - public void setRightToLeft(boolean value) - { + public void setRightToLeft(boolean value) { _sheet.getWindowTwo().setArabic(value); } @@ -738,28 +754,27 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * * @return whether the text is displayed in right-to-left mode in the window */ - public boolean isRightToLeft() - { + public boolean isRightToLeft() { return _sheet.getWindowTwo().getArabic(); } /** * removes a merged region of cells (hence letting them free) + * * @param index of the region to unmerge */ - public void removeMergedRegion(int index) - { + public void removeMergedRegion(int index) { _sheet.removeMergedRegion(index); } /** * returns the number of merged regions + * * @return number of merged regions */ - public int getNumMergedRegions() - { + public int getNumMergedRegions() { return _sheet.getNumMergedRegions(); } @@ -769,9 +784,10 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { public org.apache.poi.hssf.util.Region getMergedRegionAt(int index) { CellRangeAddress cra = getMergedRegion(index); - return new org.apache.poi.hssf.util.Region(cra.getFirstRow(), (short)cra.getFirstColumn(), - cra.getLastRow(), (short)cra.getLastColumn()); + return new org.apache.poi.hssf.util.Region(cra.getFirstRow(), (short) cra.getFirstColumn(), + cra.getLastRow(), (short) cra.getLastColumn()); } + /** * @return the merged region at the specified index */ @@ -781,17 +797,18 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * @return an iterator of the PHYSICAL rows. Meaning the 3rd element may not - * be the third row if say for instance the second row is undefined. - * Call getRowNum() on each row if you care which one it is. + * be the third row if say for instance the second row is undefined. + * Call getRowNum() on each row if you care which one it is. */ public Iterator rowIterator() { @SuppressWarnings("unchecked") // can this clumsy generic syntax be improved? - Iterator result = (Iterator)(Iterator)_rows.values().iterator(); + Iterator result = (Iterator) (Iterator) _rows.values().iterator(); return result; } + /** * Alias for {@link #rowIterator()} to allow - * foreach loops + * foreach loops */ public Iterator iterator() { return rowIterator(); @@ -801,6 +818,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * used internally in the API to get the low level Sheet record represented by this * Object. + * * @return Sheet - low level representation of this HSSFSheet. */ InternalSheet getSheet() { @@ -809,7 +827,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * whether alternate expression evaluation is on - * @param b alternative expression evaluation or not + * + * @param b alternative expression evaluation or not */ public void setAlternativeExpression(boolean b) { WSBoolRecord record = @@ -820,7 +839,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * whether alternative formula entry is on - * @param b alternative formulas or not + * + * @param b alternative formulas or not */ public void setAlternativeFormula(boolean b) { WSBoolRecord record = @@ -831,7 +851,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * show automatic page breaks or not - * @param b whether to show auto page breaks + * + * @param b whether to show auto page breaks */ public void setAutobreaks(boolean b) { WSBoolRecord record = @@ -842,7 +863,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * set whether sheet is a dialog sheet or not - * @param b isDialog or not + * + * @param b isDialog or not */ public void setDialog(boolean b) { WSBoolRecord record = @@ -854,7 +876,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * set whether to display the guts or not * - * @param b guts or no guts (or glory) + * @param b guts or no guts (or glory) */ public void setDisplayGuts(boolean b) { WSBoolRecord record = @@ -865,7 +887,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * fit to page option is on - * @param b fit or not + * + * @param b fit or not */ public void setFitToPage(boolean b) { WSBoolRecord record = @@ -876,7 +899,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * set if row summaries appear below detail in the outline - * @param b below or not + * + * @param b below or not */ public void setRowSumsBelow(boolean b) { WSBoolRecord record = @@ -889,7 +913,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * set if col summaries appear right of the detail in the outline - * @param b right or not + * + * @param b right or not */ public void setRowSumsRight(boolean b) { WSBoolRecord record = @@ -900,6 +925,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * whether alternate expression evaluation is on + * * @return alternative expression evaluation or not */ public boolean getAlternateExpression() { @@ -909,6 +935,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * whether alternative formula entry is on + * * @return alternative formulas or not */ public boolean getAlternateFormula() { @@ -918,6 +945,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * show automatic page breaks or not + * * @return whether to show auto page breaks */ public boolean getAutobreaks() { @@ -927,6 +955,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get whether sheet is a dialog sheet or not + * * @return isDialog or not */ public boolean getDialog() { @@ -951,9 +980,10 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { *

* In Excel 2003 this option can be changed in the Options dialog on the View tab. *

+ * * @return whether all zero values on the worksheet are displayed */ - public boolean isDisplayZeros(){ + public boolean isDisplayZeros() { return _sheet.getWindowTwo().getDisplayZeros(); } @@ -963,14 +993,16 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { *

* In Excel 2003 this option can be set in the Options dialog on the View tab. *

+ * * @param value whether to display or hide all zero values on the worksheet */ - public void setDisplayZeros(boolean value){ + public void setDisplayZeros(boolean value) { _sheet.getWindowTwo().setDisplayZeros(value); } /** * fit to page option is on + * * @return fit or not */ public boolean getFitToPage() { @@ -980,6 +1012,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get if row summaries appear below detail in the outline + * * @return below or not */ public boolean getRowSumsBelow() { @@ -989,6 +1022,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get if col summaries appear right of the detail in the outline + * * @return right or not */ public boolean getRowSumsRight() { @@ -998,6 +1032,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Returns whether gridlines are printed. + * * @return Gridlines are printed */ public boolean isPrintGridlines() { @@ -1006,8 +1041,9 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Turns on or off the printing of gridlines. + * * @param newPrintGridlines boolean to turn on or off the printing of - * gridlines + * gridlines */ public void setPrintGridlines(boolean newPrintGridlines) { getSheet().getPrintGridlines().setPrintGridlines(newPrintGridlines); @@ -1015,6 +1051,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Gets the print setup object. + * * @return The user model for the print setup object. */ public HSSFPrintSetup getPrintSetup() { @@ -1031,26 +1068,32 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Note - this is not the same as whether the sheet is focused (isActive) + * * @return true if this sheet is currently selected */ public boolean isSelected() { return getSheet().getWindowTwo().getSelected(); } + /** * Sets whether sheet is selected. + * * @param sel Whether to select the sheet or deselect the sheet. */ public void setSelected(boolean sel) { getSheet().getWindowTwo().setSelected(sel); } + /** * @return true if this sheet is currently focused */ public boolean isActive() { return getSheet().getWindowTwo().isActive(); } + /** * Sets whether sheet is selected. + * * @param sel Whether to select the sheet or deselect the sheet. */ public void setActive(boolean sel) { @@ -1059,11 +1102,12 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Gets the size of the margin in inches. + * * @param margin which margin to get * @return the size of the margin */ public double getMargin(short margin) { - switch (margin){ + switch (margin) { case FooterMargin: return _sheet.getPageSettings().getPrintSetup().getFooterMargin(); case HeaderMargin: @@ -1075,11 +1119,12 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Sets the size of the margin in inches. + * * @param margin which margin to get - * @param size the size of the margin + * @param size the size of the margin */ public void setMargin(short margin, double size) { - switch (margin){ + switch (margin) { case FooterMargin: _sheet.getPageSettings().getPrintSetup().setFooterMargin(size); break; @@ -1094,8 +1139,10 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { private WorksheetProtectionBlock getProtectionBlock() { return _sheet.getProtectionBlock(); } + /** * Answer whether protection is enabled or disabled + * * @return true => protection enabled; false => protection disabled */ public boolean getProtect() { @@ -1106,11 +1153,12 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @return hashed password */ public short getPassword() { - return (short)getProtectionBlock().getPasswordHash(); + return (short) getProtectionBlock().getPasswordHash(); } /** * Answer whether object protection is enabled or disabled + * * @return true => protection enabled; false => protection disabled */ public boolean getObjectProtect() { @@ -1119,13 +1167,16 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Answer whether scenario protection is enabled or disabled + * * @return true => protection enabled; false => protection disabled */ public boolean getScenarioProtect() { return getProtectionBlock().isScenarioProtected(); } + /** * Sets the protection enabled as well as the password + * * @param password to set for protection. Pass null to remove protection */ public void protectSheet(String password) { @@ -1137,25 +1188,25 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * fraction. For example to express a zoom of 75% use 3 for the numerator * and 4 for the denominator. * - * @param numerator The numerator for the zoom magnification. - * @param denominator The denominator for the zoom magnification. + * @param numerator The numerator for the zoom magnification. + * @param denominator The denominator for the zoom magnification. */ - public void setZoom( int numerator, int denominator) - { + public void setZoom(int numerator, int denominator) { if (numerator < 1 || numerator > 65535) throw new IllegalArgumentException("Numerator must be greater than 1 and less than 65536"); if (denominator < 1 || denominator > 65535) throw new IllegalArgumentException("Denominator must be greater than 1 and less than 65536"); SCLRecord sclRecord = new SCLRecord(); - sclRecord.setNumerator((short)numerator); - sclRecord.setDenominator((short)denominator); + sclRecord.setNumerator((short) numerator); + sclRecord.setDenominator((short) denominator); getSheet().setSCLRecord(sclRecord); } /** * The top row in the visible view when the sheet is * first viewed after opening it in a viewer + * * @return short indicating the rownum (0 based) of the top row */ public short getTopRow() { @@ -1165,6 +1216,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * The left col in the visible view when the sheet is * first viewed after opening it in a viewer + * * @return short indicating the rownum (0 based) of the top row */ public short getLeftCol() { @@ -1174,18 +1226,20 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Sets desktop window pane display area, when the * file is first opened in a viewer. - * @param toprow the top row to show in desktop window pane + * + * @param toprow the top row to show in desktop window pane * @param leftcol the left column to show in desktop window pane */ - public void showInPane(short toprow, short leftcol){ + public void showInPane(short toprow, short leftcol) { _sheet.setTopRow(toprow); _sheet.setLeftCol(leftcol); } /** * Shifts the merged regions left or right depending on mode - *

+ *

* TODO: MODE , this is only row specific + * * @param startRow * @param endRow * @param n @@ -1195,26 +1249,26 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { List shiftedRegions = new ArrayList(); //move merged regions completely if they fall within the new region boundaries when they are shifted for (int i = 0; i < getNumMergedRegions(); i++) { - CellRangeAddress merged = getMergedRegion(i); + CellRangeAddress merged = getMergedRegion(i); - boolean inStart= (merged.getFirstRow() >= startRow || merged.getLastRow() >= startRow); - boolean inEnd = (merged.getFirstRow() <= endRow || merged.getLastRow() <= endRow); + boolean inStart = (merged.getFirstRow() >= startRow || merged.getLastRow() >= startRow); + boolean inEnd = (merged.getFirstRow() <= endRow || merged.getLastRow() <= endRow); - //don't check if it's not within the shifted area - if (!inStart || !inEnd) { + //don't check if it's not within the shifted area + if (!inStart || !inEnd) { continue; - } + } - //only shift if the region outside the shifted rows is not merged too - if (!SheetUtil.containsCell(merged, startRow-1, 0) && - !SheetUtil.containsCell(merged, endRow+1, 0)){ - merged.setFirstRow(merged.getFirstRow()+n); - merged.setLastRow(merged.getLastRow()+n); - //have to remove/add it back - shiftedRegions.add(merged); - removeMergedRegion(i); - i = i -1; // we have to back up now since we removed one - } + //only shift if the region outside the shifted rows is not merged too + if (!SheetUtil.containsCell(merged, startRow - 1, 0) && + !SheetUtil.containsCell(merged, endRow + 1, 0)) { + merged.setFirstRow(merged.getFirstRow() + n); + merged.setLastRow(merged.getLastRow() + n); + //have to remove/add it back + shiftedRegions.add(merged); + removeMergedRegion(i); + i = i - 1; // we have to back up now since we removed one + } } //read so it doesn't get shifted again @@ -1230,17 +1284,18 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * Shifts rows between startRow and endRow n number of rows. * If you use a negative number, it will shift rows up. * Code ensures that rows don't wrap around. - * + *

* Calls shiftRows(startRow, endRow, n, false, false); - * - *

+ *

+ *

* Additionally shifts merged regions that are completely defined in these * rows (ie. merged 2 cells on a row to be shifted). + * * @param startRow the row to start shifting - * @param endRow the row to end shifting - * @param n the number of rows to shift + * @param endRow the row to end shifting + * @param n the number of rows to shift */ - public void shiftRows( int startRow, int endRow, int n ) { + public void shiftRows(int startRow, int endRow, int n) { shiftRows(startRow, endRow, n, false, false); } @@ -1248,19 +1303,20 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * Shifts rows between startRow and endRow n number of rows. * If you use a negative number, it will shift rows up. * Code ensures that rows don't wrap around - * - *

+ *

+ *

* Additionally shifts merged regions that are completely defined in these * rows (ie. merged 2 cells on a row to be shifted). - *

+ *

* TODO Might want to add bounds checking here - * @param startRow the row to start shifting - * @param endRow the row to end shifting - * @param n the number of rows to shift - * @param copyRowHeight whether to copy the row height during the shift + * + * @param startRow the row to start shifting + * @param endRow the row to end shifting + * @param n the number of rows to shift + * @param copyRowHeight whether to copy the row height during the shift * @param resetOriginalRowHeight whether to set the original row's height to the default */ - public void shiftRows( int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight) { + public void shiftRows(int startRow, int endRow, int n, boolean copyRowHeight, boolean resetOriginalRowHeight) { shiftRows(startRow, endRow, n, copyRowHeight, resetOriginalRowHeight, true); } @@ -1268,21 +1324,22 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * Shifts rows between startRow and endRow n number of rows. * If you use a negative number, it will shift rows up. * Code ensures that rows don't wrap around - * - *

+ *

+ *

* Additionally shifts merged regions that are completely defined in these * rows (ie. merged 2 cells on a row to be shifted). - *

+ *

* TODO Might want to add bounds checking here - * @param startRow the row to start shifting - * @param endRow the row to end shifting - * @param n the number of rows to shift - * @param copyRowHeight whether to copy the row height during the shift + * + * @param startRow the row to start shifting + * @param endRow the row to end shifting + * @param n the number of rows to shift + * @param copyRowHeight whether to copy the row height during the shift * @param resetOriginalRowHeight whether to set the original row's height to the default - * @param moveComments whether to move comments at the same time as the cells they are attached to + * @param moveComments whether to move comments at the same time as the cells they are attached to */ public void shiftRows(int startRow, int endRow, int n, - boolean copyRowHeight, boolean resetOriginalRowHeight, boolean moveComments) { + boolean copyRowHeight, boolean resetOriginalRowHeight, boolean moveComments) { int s, inc; if (n < 0) { s = startRow; @@ -1291,10 +1348,10 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { s = endRow; inc = -1; } else { - // Nothing to do - return; + // Nothing to do + return; } - + NoteRecord[] noteRecs; if (moveComments) { noteRecs = _sheet.getNoteRecords(); @@ -1305,16 +1362,16 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { shiftMerged(startRow, endRow, n, true); _sheet.getPageSettings().shiftRowBreaks(startRow, endRow, n); - for ( int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc ) { - HSSFRow row = getRow( rowNum ); + for (int rowNum = s; rowNum >= startRow && rowNum <= endRow && rowNum >= 0 && rowNum < 65536; rowNum += inc) { + HSSFRow row = getRow(rowNum); // notify all cells in this row that we are going to shift them, // it can throw IllegalStateException if the operation is not allowed, for example, // if the row contains cells included in a multi-cell array formula - if(row != null) notifyRowShifting(row); + if (row != null) notifyRowShifting(row); - HSSFRow row2Replace = getRow( rowNum + n ); - if ( row2Replace == null ) - row2Replace = createRow( rowNum + n ); + HSSFRow row2Replace = getRow(rowNum + n); + if (row2Replace == null) + row2Replace = createRow(rowNum + n); // Remove all the old cells from the row we'll @@ -1333,21 +1390,21 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { row2Replace.setHeight(row.getHeight()); } if (resetOriginalRowHeight) { - row.setHeight((short)0xff); + row.setHeight((short) 0xff); } // Copy each cell from the source row to // the destination row - for(Iterator cells = row.cellIterator(); cells.hasNext(); ) { - HSSFCell cell = (HSSFCell)cells.next(); - row.removeCell( cell ); + for (Iterator cells = row.cellIterator(); cells.hasNext(); ) { + HSSFCell cell = (HSSFCell) cells.next(); + row.removeCell(cell); CellValueRecordInterface cellRecord = cell.getCellValueRecord(); - cellRecord.setRow( rowNum + n ); - row2Replace.createCellFromRecord( cellRecord ); - _sheet.addValueRecord( rowNum + n, cellRecord ); + cellRecord.setRow(rowNum + n); + row2Replace.createCellFromRecord(cellRecord); + _sheet.addValueRecord(rowNum + n, cellRecord); HSSFHyperlink link = cell.getHyperlink(); - if(link != null){ + if (link != null) { link.setFirstRow(link.getFirstRow() + n); link.setLastRow(link.getLastRow() + n); } @@ -1358,52 +1415,54 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { // Move comments from the source row to the // destination row. Note that comments can // exist for cells which are null - if(moveComments) { + if (moveComments) { // This code would get simpler if NoteRecords could be organised by HSSFRow. - for(int i=noteRecs.length-1; i>=0; i--) { - NoteRecord nr = noteRecs[i]; - if (nr.getRow() != rowNum) { + HSSFPatriarch patriarch = createDrawingPatriarch(); + for (int i = patriarch.getChildren().size() - 1; i >= 0; i--) { + HSSFShape shape = patriarch.getChildren().get(i); + if (!(shape instanceof HSSFComment)) { continue; } - HSSFComment comment = getCellComment(rowNum, nr.getColumn()); - if (comment != null) { - comment.setRow(rowNum + n); + HSSFComment comment = (HSSFComment) shape; + if (comment.getRow() != rowNum) { + continue; } + comment.setRow(rowNum + n); } } } - + // Re-compute the first and last rows of the sheet as needed - if(n > 0) { - // Rows are moving down - if ( startRow == _firstrow ) { - // Need to walk forward to find the first non-blank row - _firstrow = Math.max( startRow + n, 0 ); - for( int i=startRow+1; i < startRow+n; i++ ) { - if (getRow(i) != null) { - _firstrow = i; - break; - } - } - } - if ( endRow + n > _lastrow ) { - _lastrow = Math.min( endRow + n, SpreadsheetVersion.EXCEL97.getLastRowIndex() ); - } + if (n > 0) { + // Rows are moving down + if (startRow == _firstrow) { + // Need to walk forward to find the first non-blank row + _firstrow = Math.max(startRow + n, 0); + for (int i = startRow + 1; i < startRow + n; i++) { + if (getRow(i) != null) { + _firstrow = i; + break; + } + } + } + if (endRow + n > _lastrow) { + _lastrow = Math.min(endRow + n, SpreadsheetVersion.EXCEL97.getLastRowIndex()); + } } else { - // Rows are moving up - if ( startRow + n < _firstrow ) { - _firstrow = Math.max( startRow + n, 0 ); - } - if ( endRow == _lastrow ) { - // Need to walk backward to find the last non-blank row - _lastrow = Math.min( endRow + n, SpreadsheetVersion.EXCEL97.getLastRowIndex() ); - for (int i=endRow-1; i > endRow+n; i++) { - if (getRow(i) != null) { - _lastrow = i; - break; - } - } - } + // Rows are moving up + if (startRow + n < _firstrow) { + _firstrow = Math.max(startRow + n, 0); + } + if (endRow == _lastrow) { + // Need to walk backward to find the last non-blank row + _lastrow = Math.min(endRow + n, SpreadsheetVersion.EXCEL97.getLastRowIndex()); + for (int i = endRow - 1; i > endRow + n; i++) { + if (getRow(i) != null) { + _lastrow = i; + break; + } + } + } } // Update any formulas on this sheet that point to @@ -1414,7 +1473,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { _sheet.updateFormulasAfterCellShift(shifter, externSheetIndex); int nSheets = _workbook.getNumberOfSheets(); - for(int i=0; i *

- * If both colSplit and rowSplit are zero then the existing freeze pane is removed + * If both colSplit and rowSplit are zero then the existing freeze pane is removed *

* - * @param colSplit Horizonatal position of split. - * @param rowSplit Vertical position of split. - * @param leftmostColumn Left column visible in right pane. - * @param topRow Top row visible in bottom pane + * @param colSplit Horizonatal position of split. + * @param rowSplit Vertical position of split. + * @param leftmostColumn Left column visible in right pane. + * @param topRow Top row visible in bottom pane */ public void createFreezePane(int colSplit, int rowSplit, int leftmostColumn, int topRow) { validateColumn(colSplit); validateRow(rowSplit); - if (leftmostColumn < colSplit) throw new IllegalArgumentException("leftmostColumn parameter must not be less than colSplit parameter"); - if (topRow < rowSplit) throw new IllegalArgumentException("topRow parameter must not be less than leftmostColumn parameter"); - getSheet().createFreezePane( colSplit, rowSplit, topRow, leftmostColumn ); + if (leftmostColumn < colSplit) + throw new IllegalArgumentException("leftmostColumn parameter must not be less than colSplit parameter"); + if (topRow < rowSplit) + throw new IllegalArgumentException("topRow parameter must not be less than leftmostColumn parameter"); + getSheet().createFreezePane(colSplit, rowSplit, topRow, leftmostColumn); } /** * Creates a split (freezepane). Any existing freezepane or split pane is overwritten. - * + *

*

- * If both colSplit and rowSplit are zero then the existing freeze pane is removed + * If both colSplit and rowSplit are zero then the existing freeze pane is removed *

* - * @param colSplit Horizonatal position of split. - * @param rowSplit Vertical position of split. + * @param colSplit Horizonatal position of split. + * @param rowSplit Vertical position of split. */ public void createFreezePane(int colSplit, int rowSplit) { createFreezePane(colSplit, rowSplit, colSplit, rowSplit); @@ -1477,23 +1538,25 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Creates a split pane. Any existing freezepane or split pane is overwritten. + * * @param xSplitPos Horizonatal position of split (in 1/20th of a point). * @param ySplitPos Vertical position of split (in 1/20th of a point). - * @param topRow Top row visible in bottom pane - * @param leftmostColumn Left column visible in right pane. - * @param activePane Active pane. One of: PANE_LOWER_RIGHT, - * PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT + * @param topRow Top row visible in bottom pane + * @param leftmostColumn Left column visible in right pane. + * @param activePane Active pane. One of: PANE_LOWER_RIGHT, + * PANE_UPPER_RIGHT, PANE_LOWER_LEFT, PANE_UPPER_LEFT * @see #PANE_LOWER_LEFT * @see #PANE_LOWER_RIGHT * @see #PANE_UPPER_LEFT * @see #PANE_UPPER_RIGHT */ public void createSplitPane(int xSplitPos, int ySplitPos, int leftmostColumn, int topRow, int activePane) { - getSheet().createSplitPane( xSplitPos, ySplitPos, topRow, leftmostColumn, activePane ); + getSheet().createSplitPane(xSplitPos, ySplitPos, topRow, leftmostColumn, activePane); } /** * Returns the information regarding the currently configured pane (split or freeze). + * * @return null if no pane configured, or the pane information. */ public PaneInformation getPaneInformation() { @@ -1502,6 +1565,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Sets whether the gridlines are shown in a viewer. + * * @param show whether to show gridlines or not */ public void setDisplayGridlines(boolean show) { @@ -1510,14 +1574,16 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Returns if gridlines are displayed. + * * @return whether gridlines are displayed */ public boolean isDisplayGridlines() { - return _sheet.isDisplayGridlines(); + return _sheet.isDisplayGridlines(); } /** * Sets whether the formulas are shown in a viewer. + * * @param show whether to show formulas or not */ public void setDisplayFormulas(boolean show) { @@ -1526,6 +1592,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Returns if formulas are displayed. + * * @return whether formulas are displayed */ public boolean isDisplayFormulas() { @@ -1534,6 +1601,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Sets whether the RowColHeadings are shown in a viewer. + * * @param show whether to show RowColHeadings or not */ public void setDisplayRowColHeadings(boolean show) { @@ -1542,6 +1610,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Returns if RowColHeadings are displayed. + * * @return whether RowColHeadings are displayed */ public boolean isDisplayRowColHeadings() { @@ -1551,7 +1620,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Sets a page break at the indicated row * Breaks occur above the specified row and left of the specified column inclusive. - * + *

* For example, sheet.setColumnBreak(2); breaks the sheet into two parts * with columns A,B,C in the first and D,E,... in the second. Simuilar, sheet.setRowBreak(2); * breaks the sheet into two parts with first three rows (rownum=1...3) in the first part @@ -1561,7 +1630,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { */ public void setRowBreak(int row) { validateRow(row); - _sheet.getPageSettings().setRowBreak(row, (short)0, (short)255); + _sheet.getPageSettings().setRowBreak(row, (short) 0, (short) 255); } /** @@ -1598,7 +1667,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Sets a page break at the indicated column. * Breaks occur above the specified row and left of the specified column inclusive. - * + *

* For example, sheet.setColumnBreak(2); breaks the sheet into two parts * with columns A,B,C in the first and D,E,... in the second. Simuilar, sheet.setRowBreak(2); * breaks the sheet into two parts with first three rows (rownum=1...3) in the first part @@ -1607,12 +1676,13 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * @param column the column to break, inclusive */ public void setColumnBreak(int column) { - validateColumn((short)column); - _sheet.getPageSettings().setColumnBreak((short)column, (short)0, (short) SpreadsheetVersion.EXCEL97.getLastRowIndex()); + validateColumn((short) column); + _sheet.getPageSettings().setColumnBreak((short) column, (short) 0, (short) SpreadsheetVersion.EXCEL97.getLastRowIndex()); } /** * Determines if there is a page break at the indicated column + * * @param column FIXME: Document this! * @return FIXME: Document this! */ @@ -1622,6 +1692,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Removes a page break at the indicated column + * * @param column */ public void removeColumnBreak(int column) { @@ -1630,6 +1701,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Runs a bounds check for row numbers + * * @param row */ protected void validateRow(int row) { @@ -1640,12 +1712,13 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Runs a bounds check for column numbers + * * @param column */ protected void validateColumn(int column) { int maxcol = SpreadsheetVersion.EXCEL97.getLastColumnIndex(); if (column > maxcol) throw new IllegalArgumentException("Maximum column number is " + maxcol); - if (column < 0) throw new IllegalArgumentException("Minimum column number is 0"); + if (column < 0) throw new IllegalArgumentException("Minimum column number is 0"); } /** @@ -1658,7 +1731,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { EscherAggregate r = (EscherAggregate) getSheet().findFirstRecordBySid(EscherAggregate.sid); List escherRecords = r.getEscherRecords(); PrintWriter w = new PrintWriter(System.out); - for (Iterator iterator = escherRecords.iterator(); iterator.hasNext();) { + for (Iterator iterator = escherRecords.iterator(); iterator.hasNext(); ) { EscherRecord escherRecord = iterator.next(); if (fat) { System.out.println(escherRecord.toString()); @@ -1669,49 +1742,23 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { w.flush(); } - /** - * Creates the top-level drawing patriarch. This will have - * the effect of removing any existing drawings on this - * sheet. - * This may then be used to add graphics or charts - * @return The new patriarch. - */ - public HSSFPatriarch createDrawingPatriarch() { - if(_patriarch == null){ - // Create the drawing group if it doesn't already exist. - _workbook.initDrawings(); - - if(_patriarch == null){ - _sheet.aggregateDrawingRecords(_book.getDrawingManager(), true); - EscherAggregate agg = (EscherAggregate) _sheet.findFirstRecordBySid(EscherAggregate.sid); - _patriarch = new HSSFPatriarch(this, agg); - agg.setPatriarch(_patriarch); - } - } - return _patriarch; - } - /** * Returns the agregate escher records for this sheet, - * it there is one. - * WARNING - calling this will trigger a parsing of the - * associated escher records. Any that aren't supported - * (such as charts and complex drawing types) will almost - * certainly be lost or corrupted when written out. + * it there is one. */ public EscherAggregate getDrawingEscherAggregate() { _book.findDrawingGroup(); // If there's now no drawing manager, then there's // no drawing escher records on the workbook - if(_book.getDrawingManager() == null) { + if (_book.getDrawingManager() == null) { return null; } int found = _sheet.aggregateDrawingRecords( _book.getDrawingManager(), false ); - if(found == -1) { + if (found == -1) { // Workbook has drawing stuff, but this sheet doesn't return null; } @@ -1722,47 +1769,75 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { } /** - * Returns the top-level drawing patriach, if there is - * one. * This will hold any graphics or charts for the sheet. - * WARNING - calling this will trigger a parsing of the - * associated escher records. Any that aren't supported - * (such as charts and complex drawing types) will almost - * certainly be lost or corrupted when written out. Only - * use this with simple drawings, otherwise call - * {@link HSSFSheet#createDrawingPatriarch()} and - * start from scratch! + * + * @return the top-level drawing patriarch, if there is one, else returns null */ public HSSFPatriarch getDrawingPatriarch() { - if(_patriarch != null) return _patriarch; - - EscherAggregate agg = getDrawingEscherAggregate(); - if(agg == null) return null; - - _patriarch = new HSSFPatriarch(this, agg); - agg.setPatriarch(_patriarch); - - // Have it process the records into high level objects - // as best it can do (this step may eat anything - // that isn't supported, you were warned...) - agg.convertRecordsToUserModel(); - - // Return what we could cope with + _patriarch = getPatriarch(false); return _patriarch; } + /** + * Creates the top-level drawing patriarch. This will have + * the effect of removing any existing drawings on this + * sheet. + * This may then be used to add graphics or charts + * + * @return The new patriarch. + */ + public HSSFPatriarch createDrawingPatriarch() { + _patriarch = getPatriarch(true); + return _patriarch; + } + + private HSSFPatriarch getPatriarch(boolean createIfMissing) { + HSSFPatriarch patriarch = null; + if (_patriarch != null) { + return _patriarch; + } + DrawingManager2 dm = _book.findDrawingGroup(); + if (null == dm) { + if (!createIfMissing) { + return null; + } else { + _book.createDrawingGroup(); + dm = _book.getDrawingManager(); + } + } + EscherAggregate agg = (EscherAggregate) _sheet.findFirstRecordBySid(EscherAggregate.sid); + if (null == agg) { + int pos = _sheet.aggregateDrawingRecords(dm, false); + if (-1 == pos) { + if (createIfMissing) { + pos = _sheet.aggregateDrawingRecords(dm, true); + agg = (EscherAggregate) _sheet.getRecords().get(pos); + patriarch = new HSSFPatriarch(this, agg); + patriarch.afterCreate(); + return patriarch; + } else { + return null; + } + } + agg = (EscherAggregate) _sheet.getRecords().get(pos); + } + return new HSSFPatriarch(this, agg); + } + /** * @deprecated (Sep 2008) use {@link #setColumnGroupCollapsed(int, boolean)} */ public void setColumnGroupCollapsed(short columnNumber, boolean collapsed) { setColumnGroupCollapsed(columnNumber & 0xFFFF, collapsed); } + /** * @deprecated (Sep 2008) use {@link #groupColumn(int, int)} */ public void groupColumn(short fromColumn, short toColumn) { groupColumn(fromColumn & 0xFFFF, toColumn & 0xFFFF); } + /** * @deprecated (Sep 2008) use {@link #ungroupColumn(int, int)} */ @@ -1773,8 +1848,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Expands or collapses a column group. * - * @param columnNumber One of the columns in the group. - * @param collapsed true = collapse group, false = expand group. + * @param columnNumber One of the columns in the group. + * @param collapsed true = collapse group, false = expand group. */ public void setColumnGroupCollapsed(int columnNumber, boolean collapsed) { _sheet.setColumnGroupCollapsed(columnNumber, collapsed); @@ -1783,8 +1858,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Create an outline for the provided column range. * - * @param fromColumn beginning of the column range. - * @param toColumn end of the column range. + * @param fromColumn beginning of the column range. + * @param toColumn end of the column range. */ public void groupColumn(int fromColumn, int toColumn) { _sheet.groupColumnRange(fromColumn, toColumn, true); @@ -1797,8 +1872,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Tie a range of cell together so that they can be collapsed or expanded * - * @param fromRow start row (0-based) - * @param toRow end row (0-based) + * @param fromRow start row (0-based) + * @param toRow end row (0-based) */ public void groupRow(int fromRow, int toRow) { _sheet.groupRowRange(fromRow, toRow, true); @@ -1820,18 +1895,18 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * Sets the default column style for a given column. POI will only apply this style to new cells added to the sheet. * * @param column the column index - * @param style the style to set + * @param style the style to set */ public void setDefaultColumnStyle(int column, CellStyle style) { - _sheet.setDefaultColumnStyle(column, ((HSSFCellStyle)style).getIndex()); + _sheet.setDefaultColumnStyle(column, ((HSSFCellStyle) style).getIndex()); } /** * Adjusts the column width to fit the contents. - * + *

* This process can be relatively slow on large sheets, so this should - * normally only be called once per column, at the end of your - * processing. + * normally only be called once per column, at the end of your + * processing. * * @param column the column index */ @@ -1841,15 +1916,15 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Adjusts the column width to fit the contents. - * + *

* This process can be relatively slow on large sheets, so this should - * normally only be called once per column, at the end of your - * processing. - * + * normally only be called once per column, at the end of your + * processing. + *

* You can specify whether the content of merged cells should be considered or ignored. - * Default is to ignore merged cells. + * Default is to ignore merged cells. * - * @param column the column index + * @param column the column index * @param useMergedCells whether to use the contents of merged cells when calculating the width of the column */ public void autoSizeColumn(int column, boolean useMergedCells) { @@ -1857,11 +1932,11 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { if (width != -1) { width *= 256; - int maxColumnWidth = 255*256; // The maximum column width for an individual cell is 255 characters + int maxColumnWidth = 255 * 256; // The maximum column width for an individual cell is 255 characters if (width > maxColumnWidth) { width = maxColumnWidth; } - setColumnWidth(column, (int)(width)); + setColumnWidth(column, (int) (width)); } } @@ -1871,21 +1946,8 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * * @return cell comment or null if not found */ - public HSSFComment getCellComment(int row, int column) { - // Don't call findCellComment directly, otherwise - // two calls to this method will result in two - // new HSSFComment instances, which is bad - HSSFRow r = getRow(row); - if(r != null) { - HSSFCell c = r.getCell(column); - if(c != null) { - return c.getCellComment(); - } - // No cell, so you will get new - // objects every time, sorry... - return HSSFCell.findCellComment(_sheet, row, column); - } - return null; + public HSSFComment getCellComment(int row, int column) { + return findCellComment(row, column); } public HSSFSheetConditionalFormatting getSheetConditionalFormatting() { @@ -1913,7 +1975,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { int lastColumn = range.getLastColumn(); int height = lastRow - firstRow + 1; int width = lastColumn - firstColumn + 1; - List temp = new ArrayList(height*width); + List temp = new ArrayList(height * width); for (int rowIn = firstRow; rowIn <= lastRow; rowIn++) { for (int colIn = firstColumn; colIn <= lastColumn; colIn++) { HSSFRow row = getRow(rowIn); @@ -1940,7 +2002,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { c.setCellArrayFormula(range); } HSSFCell mainArrayFormulaCell = cells.getTopLeftCell(); - FormulaRecordAggregate agg = (FormulaRecordAggregate)mainArrayFormulaCell.getCellValueRecord(); + FormulaRecordAggregate agg = (FormulaRecordAggregate) mainArrayFormulaCell.getCellValueRecord(); agg.setArrayFormula(range, ptgs); return cells; } @@ -1966,20 +2028,20 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { return result; } - public DataValidationHelper getDataValidationHelper() { - return new HSSFDataValidationHelper(this); - } - + public DataValidationHelper getDataValidationHelper() { + return new HSSFDataValidationHelper(this); + } + public HSSFAutoFilter setAutoFilter(CellRangeAddress range) { InternalWorkbook workbook = _workbook.getWorkbook(); int sheetIndex = _workbook.getSheetIndex(this); - NameRecord name = workbook.getSpecificBuiltinRecord(NameRecord.BUILTIN_FILTER_DB, sheetIndex+1); + NameRecord name = workbook.getSpecificBuiltinRecord(NameRecord.BUILTIN_FILTER_DB, sheetIndex + 1); if (name == null) { - name = workbook.createBuiltInName(NameRecord.BUILTIN_FILTER_DB, sheetIndex+1); + name = workbook.createBuiltInName(NameRecord.BUILTIN_FILTER_DB, sheetIndex + 1); } // The built-in name must consist of a single Area3d Ptg. @@ -1991,173 +2053,201 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { AutoFilterInfoRecord r = new AutoFilterInfoRecord(); // the number of columns that have AutoFilter enabled. int numcols = 1 + range.getLastColumn() - range.getFirstColumn(); - r.setNumEntries((short)numcols); + r.setNumEntries((short) numcols); int idx = _sheet.findFirstRecordLocBySid(DimensionsRecord.sid); _sheet.getRecords().add(idx, r); //create a combobox control for each column HSSFPatriarch p = createDrawingPatriarch(); - for(int col = range.getFirstColumn(); col <= range.getLastColumn(); col++){ - p.createComboBox(new HSSFClientAnchor(0,0,0,0, - (short)col, range.getFirstRow(), (short)(col+1), range.getFirstRow()+1)); + for (int col = range.getFirstColumn(); col <= range.getLastColumn(); col++) { + p.createComboBox(new HSSFClientAnchor(0, 0, 0, 0, + (short) col, range.getFirstRow(), (short) (col + 1), range.getFirstRow() + 1)); } - + return new HSSFAutoFilter(this); } - - public CellRangeAddress getRepeatingRows() { - return getRepeatingRowsOrColums(true); - } - - - public CellRangeAddress getRepeatingColumns() { - return getRepeatingRowsOrColums(false); - } - - - public void setRepeatingRows(CellRangeAddress rowRangeRef) { - CellRangeAddress columnRangeRef = getRepeatingColumns(); - setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef); - } - - - public void setRepeatingColumns(CellRangeAddress columnRangeRef) { - CellRangeAddress rowRangeRef = getRepeatingRows(); - setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef); - } - - - private void setRepeatingRowsAndColumns( - CellRangeAddress rowDef, CellRangeAddress colDef) { - int sheetIndex = _workbook.getSheetIndex(this); - int maxRowIndex = SpreadsheetVersion.EXCEL97.getLastRowIndex(); - int maxColIndex = SpreadsheetVersion.EXCEL97.getLastColumnIndex(); - - int col1 = -1; - int col2 = -1; - int row1 = -1; - int row2 = -1; - - if (rowDef != null) { - row1 = rowDef.getFirstRow(); - row2 = rowDef.getLastRow(); - if ((row1 == -1 && row2 != -1) || (row1 > row2) - || (row1 < 0 || row1 > maxRowIndex) - || (row2 < 0 || row2 > maxRowIndex)) { - throw new IllegalArgumentException("Invalid row range specification"); - } - } - if (colDef != null) { - col1 = colDef.getFirstColumn(); - col2 = colDef.getLastColumn(); - if ((col1 == -1 && col2 != -1) || (col1 > col2) - || (col1 < 0 || col1 > maxColIndex) - || (col2 < 0 || col2 > maxColIndex)) { - throw new IllegalArgumentException("Invalid column range specification"); - } - } - - short externSheetIndex = - _workbook.getWorkbook().checkExternSheet(sheetIndex); - - boolean setBoth = rowDef != null && colDef != null; - boolean removeAll = rowDef == null && colDef == null; - - HSSFName name = _workbook.getBuiltInName( - NameRecord.BUILTIN_PRINT_TITLE, sheetIndex); - if (removeAll) { - if (name != null) { - _workbook.removeName(name); + protected HSSFComment findCellComment(int row, int column) { + HSSFPatriarch patriarch = getDrawingPatriarch(); + if (null == patriarch) { + patriarch = createDrawingPatriarch(); } - return; - } - if (name == null) { - name = _workbook.createBuiltInName( - NameRecord.BUILTIN_PRINT_TITLE, sheetIndex); - } - - List ptgList = new ArrayList(); - if (setBoth) { - final int exprsSize = 2 * 11 + 1; // 2 * Area3DPtg.SIZE + UnionPtg.SIZE - ptgList.add(new MemFuncPtg(exprsSize)); - } - if (colDef != null) { - Area3DPtg colArea = new Area3DPtg(0, maxRowIndex, col1, col2, - false, false, false, false, externSheetIndex); - ptgList.add(colArea); - } - if (rowDef != null) { - Area3DPtg rowArea = new Area3DPtg(row1, row2, 0, maxColIndex, - false, false, false, false, externSheetIndex); - ptgList.add(rowArea); - } - if (setBoth) { - ptgList.add(UnionPtg.instance); + return lookForComment(patriarch, row, column); } - Ptg[] ptgs = new Ptg[ptgList.size()]; - ptgList.toArray(ptgs); - name.setNameDefinition(ptgs); - - HSSFPrintSetup printSetup = getPrintSetup(); - printSetup.setValidSettings(false); - setActive(true); - } - - - private CellRangeAddress getRepeatingRowsOrColums(boolean rows) { - NameRecord rec = getBuiltinNameRecord(NameRecord.BUILTIN_PRINT_TITLE); - if (rec == null) { - return null; - } - - Ptg[] nameDefinition = rec.getNameDefinition(); - if (nameDefinition == null) { - return null; - } - - int maxRowIndex = SpreadsheetVersion.EXCEL97.getLastRowIndex(); - int maxColIndex = SpreadsheetVersion.EXCEL97.getLastColumnIndex(); - - for (Ptg ptg :nameDefinition) { - - if (ptg instanceof Area3DPtg) { - Area3DPtg areaPtg = (Area3DPtg) ptg; - - if (areaPtg.getFirstColumn() == 0 - && areaPtg.getLastColumn() == maxColIndex) { - if (rows) { - CellRangeAddress rowRange = new CellRangeAddress( - areaPtg.getFirstRow(), areaPtg.getLastRow(), -1, -1); - return rowRange; - } - } else if (areaPtg.getFirstRow() == 0 - && areaPtg.getLastRow() == maxRowIndex) { - if (!rows) { - CellRangeAddress columnRange = new CellRangeAddress(-1, -1, - areaPtg.getFirstColumn(), areaPtg.getLastColumn()); - return columnRange; - } + private HSSFComment lookForComment(HSSFShapeContainer container, int row, int column) { + for (Object object : container.getChildren()) { + HSSFShape shape = (HSSFShape) object; + if (shape instanceof HSSFShapeGroup) { + HSSFShape res = lookForComment((HSSFShapeContainer) shape, row, column); + if (null != res) { + return (HSSFComment) res; + } + continue; + } + if (shape instanceof HSSFComment) { + HSSFComment comment = (HSSFComment) shape; + if (comment.getColumn() == column && comment.getRow() == row) { + return comment; + } + } } - - } - + return null; } - - return null; - } - private NameRecord getBuiltinNameRecord(byte builtinCode) { - int sheetIndex = _workbook.getSheetIndex(this); - int recIndex = - _workbook.findExistingBuiltinNameRecordIdx(sheetIndex, builtinCode); - if (recIndex == -1) { - return null; + public CellRangeAddress getRepeatingRows() { + return getRepeatingRowsOrColums(true); } - return _workbook.getNameRecord(recIndex); - } - + + public CellRangeAddress getRepeatingColumns() { + return getRepeatingRowsOrColums(false); + } + + + public void setRepeatingRows(CellRangeAddress rowRangeRef) { + CellRangeAddress columnRangeRef = getRepeatingColumns(); + setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef); + } + + + public void setRepeatingColumns(CellRangeAddress columnRangeRef) { + CellRangeAddress rowRangeRef = getRepeatingRows(); + setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef); + } + + + private void setRepeatingRowsAndColumns( + CellRangeAddress rowDef, CellRangeAddress colDef) { + int sheetIndex = _workbook.getSheetIndex(this); + int maxRowIndex = SpreadsheetVersion.EXCEL97.getLastRowIndex(); + int maxColIndex = SpreadsheetVersion.EXCEL97.getLastColumnIndex(); + + int col1 = -1; + int col2 = -1; + int row1 = -1; + int row2 = -1; + + if (rowDef != null) { + row1 = rowDef.getFirstRow(); + row2 = rowDef.getLastRow(); + if ((row1 == -1 && row2 != -1) || (row1 > row2) + || (row1 < 0 || row1 > maxRowIndex) + || (row2 < 0 || row2 > maxRowIndex)) { + throw new IllegalArgumentException("Invalid row range specification"); + } + } + if (colDef != null) { + col1 = colDef.getFirstColumn(); + col2 = colDef.getLastColumn(); + if ((col1 == -1 && col2 != -1) || (col1 > col2) + || (col1 < 0 || col1 > maxColIndex) + || (col2 < 0 || col2 > maxColIndex)) { + throw new IllegalArgumentException("Invalid column range specification"); + } + } + + short externSheetIndex = + _workbook.getWorkbook().checkExternSheet(sheetIndex); + + boolean setBoth = rowDef != null && colDef != null; + boolean removeAll = rowDef == null && colDef == null; + + HSSFName name = _workbook.getBuiltInName( + NameRecord.BUILTIN_PRINT_TITLE, sheetIndex); + if (removeAll) { + if (name != null) { + _workbook.removeName(name); + } + return; + } + if (name == null) { + name = _workbook.createBuiltInName( + NameRecord.BUILTIN_PRINT_TITLE, sheetIndex); + } + + List ptgList = new ArrayList(); + if (setBoth) { + final int exprsSize = 2 * 11 + 1; // 2 * Area3DPtg.SIZE + UnionPtg.SIZE + ptgList.add(new MemFuncPtg(exprsSize)); + } + if (colDef != null) { + Area3DPtg colArea = new Area3DPtg(0, maxRowIndex, col1, col2, + false, false, false, false, externSheetIndex); + ptgList.add(colArea); + } + if (rowDef != null) { + Area3DPtg rowArea = new Area3DPtg(row1, row2, 0, maxColIndex, + false, false, false, false, externSheetIndex); + ptgList.add(rowArea); + } + if (setBoth) { + ptgList.add(UnionPtg.instance); + } + + Ptg[] ptgs = new Ptg[ptgList.size()]; + ptgList.toArray(ptgs); + name.setNameDefinition(ptgs); + + HSSFPrintSetup printSetup = getPrintSetup(); + printSetup.setValidSettings(false); + setActive(true); + } + + + private CellRangeAddress getRepeatingRowsOrColums(boolean rows) { + NameRecord rec = getBuiltinNameRecord(NameRecord.BUILTIN_PRINT_TITLE); + if (rec == null) { + return null; + } + + Ptg[] nameDefinition = rec.getNameDefinition(); + if (nameDefinition == null) { + return null; + } + + int maxRowIndex = SpreadsheetVersion.EXCEL97.getLastRowIndex(); + int maxColIndex = SpreadsheetVersion.EXCEL97.getLastColumnIndex(); + + for (Ptg ptg : nameDefinition) { + + if (ptg instanceof Area3DPtg) { + Area3DPtg areaPtg = (Area3DPtg) ptg; + + if (areaPtg.getFirstColumn() == 0 + && areaPtg.getLastColumn() == maxColIndex) { + if (rows) { + CellRangeAddress rowRange = new CellRangeAddress( + areaPtg.getFirstRow(), areaPtg.getLastRow(), -1, -1); + return rowRange; + } + } else if (areaPtg.getFirstRow() == 0 + && areaPtg.getLastRow() == maxRowIndex) { + if (!rows) { + CellRangeAddress columnRange = new CellRangeAddress(-1, -1, + areaPtg.getFirstColumn(), areaPtg.getLastColumn()); + return columnRange; + } + } + + } + + } + + return null; + } + + + private NameRecord getBuiltinNameRecord(byte builtinCode) { + int sheetIndex = _workbook.getSheetIndex(this); + int recIndex = + _workbook.findExistingBuiltinNameRecordIdx(sheetIndex, builtinCode); + if (recIndex == -1) { + return null; + } + return _workbook.getNameRecord(recIndex); + } + + } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java index ee8522929..ffbcec50e 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSimpleShape.java @@ -17,25 +17,29 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; +import org.apache.poi.ss.usermodel.RichTextString; + /** * Represents a simple shape such as a line, rectangle or oval. * * @author Glen Stampoultzis (glens at apache.org) */ -public class HSSFSimpleShape - extends HSSFShape +public class HSSFSimpleShape extends HSSFShape { // The commented out ones haven't been tested yet or aren't supported // by HSSFSimpleShape. - public final static short OBJECT_TYPE_LINE = 1; - public final static short OBJECT_TYPE_RECTANGLE = 2; - public final static short OBJECT_TYPE_OVAL = 3; -// public final static short OBJECT_TYPE_ARC = 4; -// public final static short OBJECT_TYPE_CHART = 5; + public final static short OBJECT_TYPE_LINE = HSSFShapeTypes.Line; + public final static short OBJECT_TYPE_RECTANGLE = HSSFShapeTypes.Rectangle; + public final static short OBJECT_TYPE_OVAL = HSSFShapeTypes.Ellipse; + public final static short OBJECT_TYPE_ARC = HSSFShapeTypes.Arc; + // public final static short OBJECT_TYPE_CHART = 5; // public final static short OBJECT_TYPE_TEXT = 6; // public final static short OBJECT_TYPE_BUTTON = 7; - public final static short OBJECT_TYPE_PICTURE = 8; + public final static short OBJECT_TYPE_PICTURE = HSSFShapeTypes.PictureFrame; + // public final static short OBJECT_TYPE_POLYGON = 9; // public final static short OBJECT_TYPE_CHECKBOX = 11; // public final static short OBJECT_TYPE_OPTION_BUTTON = 12; @@ -46,17 +50,156 @@ public class HSSFSimpleShape // public final static short OBJECT_TYPE_SCROLL_BAR = 17; // 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_MICROSOFT_OFFICE_DRAWING = 30; + public final static short OBJECT_TYPE_COMBO_BOX = HSSFShapeTypes.HostControl; + public final static short OBJECT_TYPE_COMMENT = HSSFShapeTypes.TextBox; + public final static short OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING = 30; - int shapeType = OBJECT_TYPE_LINE; + public final static int WRAP_SQUARE = 0; + public final static int WRAP_BY_POINTS = 1; + public final static int WRAP_NONE = 2; - public HSSFSimpleShape( HSSFShape parent, HSSFAnchor anchor ) + private TextObjectRecord _textObjectRecord; + + public HSSFSimpleShape(EscherContainerRecord spContainer, ObjRecord objRecord, TextObjectRecord textObjectRecord) { + super(spContainer, objRecord); + this._textObjectRecord = textObjectRecord; + } + + public HSSFSimpleShape(EscherContainerRecord spContainer, ObjRecord objRecord) { + super(spContainer, objRecord); + } + + public HSSFSimpleShape( HSSFShape parent, HSSFAnchor anchor) { super( parent, anchor ); + _textObjectRecord = createTextObjRecord(); } + protected TextObjectRecord getTextObjectRecord() { + return _textObjectRecord; + } + + protected TextObjectRecord createTextObjRecord(){ + TextObjectRecord obj = new TextObjectRecord(); + obj.setHorizontalTextAlignment(2); + obj.setVerticalTextAlignment(2); + obj.setTextLocked(true); + obj.setTextOrientation(TextObjectRecord.TEXT_ORIENTATION_NONE); + obj.setStr(new HSSFRichTextString("")); + return obj; + } + + @Override + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spContainer = new EscherContainerRecord(); + spContainer.setRecordId( EscherContainerRecord.SP_CONTAINER ); + spContainer.setOptions( (short) 0x000F ); + + EscherSpRecord sp = new EscherSpRecord(); + sp.setRecordId( EscherSpRecord.RECORD_ID ); + sp.setFlags( EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE ); + sp.setVersion((short) 0x2); + + EscherClientDataRecord clientData = new EscherClientDataRecord(); + clientData.setRecordId( EscherClientDataRecord.RECORD_ID ); + clientData.setOptions( (short) (0x0000) ); + + EscherOptRecord optRecord = new EscherOptRecord(); + optRecord.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, LINESTYLE_SOLID)); + optRecord.setEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080008)); +// optRecord.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, LINEWIDTH_DEFAULT)); + optRecord.setEscherProperty(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, FILL__FILLCOLOR_DEFAULT)); + optRecord.setEscherProperty(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, LINESTYLE__COLOR_DEFAULT)); + optRecord.setEscherProperty(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, NO_FILLHITTEST_FALSE)); + optRecord.setEscherProperty( new EscherBoolProperty( EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080008)); + + optRecord.setEscherProperty( new EscherShapePathProperty( EscherProperties.GEOMETRY__SHAPEPATH, EscherShapePathProperty.COMPLEX ) ); + optRecord.setEscherProperty(new EscherBoolProperty( EscherProperties.GROUPSHAPE__PRINT, 0x080000)); + optRecord.setRecordId( EscherOptRecord.RECORD_ID ); + + EscherTextboxRecord escherTextbox = new EscherTextboxRecord(); + escherTextbox.setRecordId(EscherTextboxRecord.RECORD_ID); + escherTextbox.setOptions((short) 0x0000); + + spContainer.addChildRecord(sp); + spContainer.addChildRecord(optRecord); + spContainer.addChildRecord(getAnchor().getEscherAnchor()); + spContainer.addChildRecord(clientData); + spContainer.addChildRecord(escherTextbox); + return spContainer; + } + + @Override + protected ObjRecord createObjRecord() { + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setLocked(true); + c.setPrintable(true); + c.setAutofill(true); + c.setAutoline(true); + EndSubRecord e = new EndSubRecord(); + + obj.addSubRecord(c); + obj.addSubRecord(e); + return obj; + } + + @Override + protected void afterRemove(HSSFPatriarch patriarch) { + patriarch._getBoundAggregate().removeShapeToObjRecord(getEscherContainer().getChildById(EscherClientDataRecord.RECORD_ID)); + if (null != getEscherContainer().getChildById(EscherTextboxRecord.RECORD_ID)){ + patriarch._getBoundAggregate().removeShapeToObjRecord(getEscherContainer().getChildById(EscherTextboxRecord.RECORD_ID)); + } + } + + /** + * @return the rich text string for this textbox. + */ + public HSSFRichTextString getString() { + return _textObjectRecord.getStr(); + } + + /** + * @param string Sets the rich text string used by this object. + */ + public void setString(RichTextString string) { + //TODO add other shape types which can not contain text + if (getShapeType() == 0 || getShapeType() == OBJECT_TYPE_LINE){ + throw new IllegalStateException("Cannot set text for shape type: "+getShapeType()); + } + HSSFRichTextString rtr = (HSSFRichTextString) string; + // If font is not set we must set the default one + if (rtr.numFormattingRuns() == 0) rtr.applyFont((short) 0); + _textObjectRecord.setStr(rtr); + if (string.getString() != null){ + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTID, string.getString().hashCode())); + } + } + + @Override + void afterInsert(HSSFPatriarch patriarch){ + EscherAggregate agg = patriarch._getBoundAggregate(); + agg.associateShapeToObjRecord(getEscherContainer().getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + + if (null != getTextObjectRecord()){ + agg.associateShapeToObjRecord(getEscherContainer().getChildById(EscherTextboxRecord.RECORD_ID), getTextObjectRecord()); + } + } + + @Override + protected HSSFShape cloneShape() { + TextObjectRecord txo = null; + EscherContainerRecord spContainer = new EscherContainerRecord(); + byte [] inSp = getEscherContainer().serialize(); + spContainer.fillFields(inSp, 0, new DefaultEscherRecordFactory()); + ObjRecord obj = (ObjRecord) getObjRecord().cloneViaReserialise(); + if (getTextObjectRecord() != null && getString() != null && null != getString().getString()){ + txo = (TextObjectRecord) getTextObjectRecord().cloneViaReserialise(); + } + return new HSSFSimpleShape(spContainer, obj, txo); + } + + /** * Gets the shape type. * @return One of the OBJECT_TYPE_* constants. @@ -67,19 +210,28 @@ public class HSSFSimpleShape * @see #OBJECT_TYPE_PICTURE * @see #OBJECT_TYPE_COMMENT */ - public int getShapeType() { return shapeType; } + public int getShapeType() { + EscherSpRecord spRecord = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + return spRecord.getShapeType(); + } + + public int getWrapText(){ + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.TEXT__WRAPTEXT); + return null == property ? WRAP_SQUARE : property.getPropertyValue(); + } + + public void setWrapText(int value){ + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__WRAPTEXT, false, false, value)); + } /** - * Sets the shape types. - * - * @param shapeType One of the OBJECT_TYPE_* constants. - * - * @see #OBJECT_TYPE_LINE - * @see #OBJECT_TYPE_OVAL - * @see #OBJECT_TYPE_RECTANGLE - * @see #OBJECT_TYPE_PICTURE - * @see #OBJECT_TYPE_COMMENT + * @see HSSFShapeTypes + * @param value - shapeType */ - public void setShapeType( int shapeType ){ this.shapeType = shapeType; } - + public void setShapeType(int value){ + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) getObjRecord().getSubRecords().get(0); + cod.setObjectType(OBJECT_TYPE_MICROSOFT_OFFICE_DRAWING); + EscherSpRecord spRecord = getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + spRecord.setShapeType((short) value); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java index 2548bf511..4260ead63 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFTextbox.java @@ -17,6 +17,8 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.record.*; import org.apache.poi.ss.usermodel.RichTextString; /** @@ -24,163 +26,222 @@ import org.apache.poi.ss.usermodel.RichTextString; * * @author Glen Stampoultzis (glens at apache.org) */ -public class HSSFTextbox - extends HSSFSimpleShape -{ - public final static short OBJECT_TYPE_TEXT = 6; +public class HSSFTextbox extends HSSFSimpleShape { + public final static short OBJECT_TYPE_TEXT = 6; /** * How to align text horizontally */ - public final static short HORIZONTAL_ALIGNMENT_LEFT = 1; - public final static short HORIZONTAL_ALIGNMENT_CENTERED = 2; - public final static short HORIZONTAL_ALIGNMENT_RIGHT = 3; - public final static short HORIZONTAL_ALIGNMENT_JUSTIFIED = 4; - public final static short HORIZONTAL_ALIGNMENT_DISTRIBUTED = 7; + public final static short HORIZONTAL_ALIGNMENT_LEFT = 1; + public final static short HORIZONTAL_ALIGNMENT_CENTERED = 2; + public final static short HORIZONTAL_ALIGNMENT_RIGHT = 3; + public final static short HORIZONTAL_ALIGNMENT_JUSTIFIED = 4; + public final static short HORIZONTAL_ALIGNMENT_DISTRIBUTED = 7; /** * How to align text vertically */ - public final static short VERTICAL_ALIGNMENT_TOP = 1; - public final static short VERTICAL_ALIGNMENT_CENTER = 2; - public final static short VERTICAL_ALIGNMENT_BOTTOM = 3; - public final static short VERTICAL_ALIGNMENT_JUSTIFY = 4; - public final static short VERTICAL_ALIGNMENT_DISTRIBUTED= 7; + public final static short VERTICAL_ALIGNMENT_TOP = 1; + public final static short VERTICAL_ALIGNMENT_CENTER = 2; + public final static short VERTICAL_ALIGNMENT_BOTTOM = 3; + public final static short VERTICAL_ALIGNMENT_JUSTIFY = 4; + public final static short VERTICAL_ALIGNMENT_DISTRIBUTED = 7; - - int marginLeft, marginRight, marginTop, marginBottom; - short halign, valign; + public HSSFTextbox(EscherContainerRecord spContainer, ObjRecord objRecord, TextObjectRecord textObjectRecord) { + super(spContainer, objRecord, textObjectRecord); + } HSSFRichTextString string = new HSSFRichTextString(""); /** * Construct a new textbox with the given parent and anchor. + * * @param parent - * @param anchor One of HSSFClientAnchor or HSSFChildAnchor + * @param anchor One of HSSFClientAnchor or HSSFChildAnchor */ - public HSSFTextbox( HSSFShape parent, HSSFAnchor anchor ) - { - super( parent, anchor ); - setShapeType(OBJECT_TYPE_TEXT); + public HSSFTextbox(HSSFShape parent, HSSFAnchor anchor) { + super(parent, anchor); + setHorizontalAlignment(HORIZONTAL_ALIGNMENT_LEFT); + setVerticalAlignment(VERTICAL_ALIGNMENT_TOP); + setString(new HSSFRichTextString("")); + } - halign = HORIZONTAL_ALIGNMENT_LEFT; - valign = VERTICAL_ALIGNMENT_TOP; + @Override + protected ObjRecord createObjRecord() { + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord c = new CommonObjectDataSubRecord(); + c.setObjectType(HSSFTextbox.OBJECT_TYPE_TEXT); + c.setLocked(true); + c.setPrintable(true); + c.setAutofill(true); + c.setAutoline(true); + EndSubRecord e = new EndSubRecord(); + obj.addSubRecord(c); + obj.addSubRecord(e); + return obj; + } + + @Override + protected EscherContainerRecord createSpContainer() { + EscherContainerRecord spContainer = new EscherContainerRecord(); + EscherSpRecord sp = new EscherSpRecord(); + EscherOptRecord opt = new EscherOptRecord(); + EscherClientDataRecord clientData = new EscherClientDataRecord(); + EscherTextboxRecord escherTextbox = new EscherTextboxRecord(); + + spContainer.setRecordId(EscherContainerRecord.SP_CONTAINER); + spContainer.setOptions((short) 0x000F); + sp.setRecordId(EscherSpRecord.RECORD_ID); + sp.setOptions((short) ((EscherAggregate.ST_TEXTBOX << 4) | 0x2)); + + sp.setFlags(EscherSpRecord.FLAG_HAVEANCHOR | EscherSpRecord.FLAG_HASSHAPETYPE); + opt.setRecordId(EscherOptRecord.RECORD_ID); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTID, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__WRAPTEXT, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__ANCHORTEXT, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.GROUPSHAPE__PRINT, 0x00080000)); + + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTLEFT, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTRIGHT, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTTOP, 0)); + opt.addEscherProperty(new EscherSimpleProperty(EscherProperties.TEXT__TEXTBOTTOM, 0)); + + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEDASHING, LINESTYLE_SOLID)); + opt.setEscherProperty(new EscherBoolProperty(EscherProperties.LINESTYLE__NOLINEDRAWDASH, 0x00080008)); + opt.setEscherProperty(new EscherSimpleProperty(EscherProperties.LINESTYLE__LINEWIDTH, LINEWIDTH_DEFAULT)); + opt.setEscherProperty(new EscherRGBProperty(EscherProperties.FILL__FILLCOLOR, FILL__FILLCOLOR_DEFAULT)); + opt.setEscherProperty(new EscherRGBProperty(EscherProperties.LINESTYLE__COLOR, LINESTYLE__COLOR_DEFAULT)); + opt.setEscherProperty(new EscherBoolProperty(EscherProperties.FILL__NOFILLHITTEST, NO_FILLHITTEST_FALSE)); + opt.setEscherProperty(new EscherBoolProperty(EscherProperties.GROUPSHAPE__PRINT, 0x080000)); + + EscherRecord anchor = getAnchor().getEscherAnchor(); + clientData.setRecordId(EscherClientDataRecord.RECORD_ID); + clientData.setOptions((short) 0x0000); + escherTextbox.setRecordId(EscherTextboxRecord.RECORD_ID); + escherTextbox.setOptions((short) 0x0000); + + spContainer.addChildRecord(sp); + spContainer.addChildRecord(opt); + spContainer.addChildRecord(anchor); + spContainer.addChildRecord(clientData); + spContainer.addChildRecord(escherTextbox); + + return spContainer; + } + + @Override + void afterInsert(HSSFPatriarch patriarch) { + EscherAggregate agg = patriarch._getBoundAggregate(); + agg.associateShapeToObjRecord(getEscherContainer().getChildById(EscherClientDataRecord.RECORD_ID), getObjRecord()); + agg.associateShapeToObjRecord(getEscherContainer().getChildById(EscherTextboxRecord.RECORD_ID), getTextObjectRecord()); } /** - * @return the rich text string for this textbox. + * @return Returns the left margin within the textbox. */ - public HSSFRichTextString getString() - { - return string; - } - - /** - * @param string Sets the rich text string used by this object. - */ - public void setString( RichTextString string ) - { - HSSFRichTextString rtr = (HSSFRichTextString)string; - - // If font is not set we must set the default one - if (rtr.numFormattingRuns() == 0) rtr.applyFont((short)0); - - this.string = rtr; - } - - /** - * @return Returns the left margin within the textbox. - */ - public int getMarginLeft() - { - return marginLeft; + public int getMarginLeft() { + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.TEXT__TEXTLEFT); + return property == null ? 0 : property.getPropertyValue(); } /** * Sets the left margin within the textbox. */ - public void setMarginLeft( int marginLeft ) - { - this.marginLeft = marginLeft; + public void setMarginLeft(int marginLeft) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTLEFT, marginLeft)); } /** - * @return returns the right margin within the textbox. + * @return returns the right margin within the textbox. */ - public int getMarginRight() - { - return marginRight; + public int getMarginRight() { + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.TEXT__TEXTRIGHT); + return property == null ? 0 : property.getPropertyValue(); } /** * Sets the right margin within the textbox. */ - public void setMarginRight( int marginRight ) - { - this.marginRight = marginRight; + public void setMarginRight(int marginRight) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTRIGHT, marginRight)); } /** - * @return returns the top margin within the textbox. + * @return returns the top margin within the textbox. */ - public int getMarginTop() - { - return marginTop; + public int getMarginTop() { + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.TEXT__TEXTTOP); + return property == null ? 0 : property.getPropertyValue(); } /** * Sets the top margin within the textbox. */ - public void setMarginTop( int marginTop ) - { - this.marginTop = marginTop; + public void setMarginTop(int marginTop) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTTOP, marginTop)); } /** * Gets the bottom margin within the textbox. */ - public int getMarginBottom() - { - return marginBottom; + public int getMarginBottom() { + EscherSimpleProperty property = getOptRecord().lookup(EscherProperties.TEXT__TEXTBOTTOM); + return property == null ? 0 : property.getPropertyValue(); } /** * Sets the bottom margin within the textbox. */ - public void setMarginBottom( int marginBottom ) - { - this.marginBottom = marginBottom; + public void setMarginBottom(int marginBottom) { + setPropertyValue(new EscherSimpleProperty(EscherProperties.TEXT__TEXTBOTTOM, marginBottom)); } /** * Gets the horizontal alignment. */ - public short getHorizontalAlignment() - { - return halign; + public short getHorizontalAlignment() { + return (short) getTextObjectRecord().getHorizontalTextAlignment(); } /** * Sets the horizontal alignment. */ - public void setHorizontalAlignment( short align ) - { - this.halign = align; + public void setHorizontalAlignment(short align) { + getTextObjectRecord().setHorizontalTextAlignment(align); } /** * Gets the vertical alignment. */ - public short getVerticalAlignment() - { - return valign; + public short getVerticalAlignment() { + return (short) getTextObjectRecord().getVerticalTextAlignment(); } /** * Sets the vertical alignment. */ - public void setVerticalAlignment( short align ) - { - this.valign = align; + public void setVerticalAlignment(short align) { + getTextObjectRecord().setVerticalTextAlignment(align); + } + + @Override + public void setShapeType(int shapeType) { + throw new IllegalStateException("Shape type can not be changed in " + this.getClass().getSimpleName()); + } + + @Override + protected HSSFShape cloneShape() { + TextObjectRecord txo = (TextObjectRecord) getTextObjectRecord().cloneViaReserialise(); + EscherContainerRecord spContainer = new EscherContainerRecord(); + byte[] inSp = getEscherContainer().serialize(); + spContainer.fillFields(inSp, 0, new DefaultEscherRecordFactory()); + ObjRecord obj = (ObjRecord) getObjRecord().cloneViaReserialise(); + return new HSSFTextbox(spContainer, obj, txo); + } + + @Override + protected void afterRemove(HSSFPatriarch patriarch) { + patriarch._getBoundAggregate().removeShapeToObjRecord(getEscherContainer().getChildById(EscherClientDataRecord.RECORD_ID)); + patriarch._getBoundAggregate().removeShapeToObjRecord(getEscherContainer().getChildById(EscherTextboxRecord.RECORD_ID)); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 4ee126705..f1828fa38 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -696,7 +696,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss names.add(newName); } // TODO - maybe same logic required for other/all built-in name records - workbook.cloneDrawings(clonedSheet.getSheet()); +// workbook.cloneDrawings(clonedSheet.getSheet()); return clonedSheet; } @@ -1606,7 +1606,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss r.setUid( uid ); r.setTag( (short) 0xFF ); r.setSize( pictureData.length + 25 ); - r.setRef( 1 ); + r.setRef( 0 ); r.setOffset( 0 ); r.setBlipRecord( blipRecord ); @@ -1698,7 +1698,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss List objects = new ArrayList(); for (int i = 0; i < getNumberOfSheets(); i++) { - getAllEmbeddedObjects(getSheetAt(i).getSheet().getRecords(), objects); + getAllEmbeddedObjects(getSheetAt(i), objects); } return objects; } @@ -1706,27 +1706,20 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss /** * Gets all embedded OLE2 objects from the Workbook. * - * @param records the list of records to search. + * @param sheet embedded object attached to * @param objects the list of embedded objects to populate. */ - private void getAllEmbeddedObjects(List records, List objects) + private void getAllEmbeddedObjects(HSSFSheet sheet, List objects) { - for (RecordBase obj : records) { - if (obj instanceof ObjRecord) - { - // TODO: More convenient way of determining if there is stored binary. - // TODO: Link to the data stored in the other stream. - Iterator subRecordIter = ((ObjRecord) obj).getSubRecords().iterator(); - while (subRecordIter.hasNext()) - { - SubRecord sub = subRecordIter.next(); - if (sub instanceof EmbeddedObjectRefSubRecord) - { - objects.add(new HSSFObjectData((ObjRecord) obj, directory)); - } - } - } - } + HSSFPatriarch patriarch = sheet.getDrawingPatriarch(); + if (null == patriarch){ + return; + } + for (HSSFShape shape: patriarch.getChildren()){ + if (shape instanceof HSSFObjectData){ + objects.add((HSSFObjectData) shape); + } + } } public HSSFCreationHelper getCreationHelper() { @@ -1800,4 +1793,8 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss public boolean changeExternalReference(String oldUrl, String newUrl) { return workbook.changeExternalReference(oldUrl, newUrl); } + + public DirectoryNode getRootDirectory(){ + return directory; + } } diff --git a/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java b/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java new file mode 100644 index 000000000..10acb70e7 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/model/HSSFTestModelHelper.java @@ -0,0 +1,40 @@ +/* ==================================================================== + 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.usermodel.HSSFComment; +import org.apache.poi.hssf.usermodel.HSSFPolygon; +import org.apache.poi.hssf.usermodel.HSSFTextbox; + +/** + * @author Evgeniy Berlog + * @date 25.06.12 + */ +public class HSSFTestModelHelper { + public static TextboxShape createTextboxShape(int shapeId, HSSFTextbox textbox){ + return new TextboxShape(textbox, shapeId); + } + + public static CommentShape createCommentShape(int shapeId, HSSFComment comment){ + return new CommentShape(comment, shapeId); + } + + public static PolygonShape createPolygonShape(int shapeId, HSSFPolygon polygon){ + return new PolygonShape(polygon, shapeId); + } +} diff --git a/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java b/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java new file mode 100644 index 000000000..9de806cfc --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java @@ -0,0 +1,2265 @@ +/* ==================================================================== + 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 junit.framework.TestCase; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.ContinueRecord; +import org.apache.poi.hssf.record.DrawingRecord; +import org.apache.poi.hssf.record.EOFRecord; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.NoteRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordBase; +import org.apache.poi.hssf.record.RecordFactory; +import org.apache.poi.hssf.record.TextObjectRecord; +import org.apache.poi.hssf.record.WindowTwoRecord; +import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; +import org.apache.poi.hssf.usermodel.HSSFPatriarch; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFTestHelper; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.HexRead; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Yegor Kozlov + * @author Evgeniy Berlog + */ +public class TestDrawingAggregate extends TestCase { + + + /** + * information about drawing aggregate in a worksheet + */ + private static class DrawingAggregateInfo { + /** + * start and end indices of the aggregate in the worksheet stream + */ + private int startRecordIndex, endRecordIndex; + /** + * the records being aggregated + */ + private List aggRecords; + + /** + * @return aggregate info or null if the sheet does not contain drawing objects + */ + static DrawingAggregateInfo get(HSSFSheet sheet){ + DrawingAggregateInfo info = null; + InternalSheet isheet = HSSFTestHelper.getSheetForTest(sheet); + List records = isheet.getRecords(); + for(int i = 0; i < records.size(); i++){ + RecordBase rb = records.get(i); + if((rb instanceof DrawingRecord) && info == null) { + info = new DrawingAggregateInfo(); + info.startRecordIndex = i; + info.endRecordIndex = i; + } else if (info != null && ( + rb instanceof DrawingRecord + || rb instanceof ObjRecord + || rb instanceof TextObjectRecord + || rb instanceof ContinueRecord + || rb instanceof NoteRecord + )){ + info.endRecordIndex = i; + } else { + if(rb instanceof EscherAggregate) + throw new IllegalStateException("Drawing data already aggregated. " + + "You should cal this method before the first invocation of HSSFSheet#getDrawingPatriarch()"); + if (info != null) break; + } + } + if(info != null){ + info.aggRecords = new ArrayList( + records.subList(info.startRecordIndex, info.endRecordIndex + 1)); + } + return info; + } + + /** + * @return the raw data being aggregated + */ + byte[] getRawBytes(){ + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (RecordBase rb : aggRecords) { + Record r = (Record) rb; + try { + out.write(r.serialize()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return out.toByteArray(); + } + } + + /** + * iterate over all sheets, aggregate drawing records (if there are any) + * and remember information about the aggregated data. + * Then serialize the workbook, read back and assert that the aggregated data is preserved. + * + * The assertion is strict meaning that the drawing data before and after save must be equal. + */ + private static void assertWriteAndReadBack(HSSFWorkbook wb){ + // map aggregate info by sheet index + Map aggs = new HashMap(); + for(int i = 0; i < wb.getNumberOfSheets(); i++){ + HSSFSheet sheet = wb.getSheetAt(i); + DrawingAggregateInfo info = DrawingAggregateInfo.get(sheet); + if(info != null) { + aggs.put(i, info); + HSSFPatriarch p = sheet.getDrawingPatriarch(); + + // compare aggregate.serialize() with raw bytes from the record stream + EscherAggregate agg = HSSFTestHelper.getEscherAggregate(p); + + byte[] dgBytes1 = info.getRawBytes(); + byte[] dgBytes2 = agg.serialize(); + + assertEquals("different size of raw data ande aggregate.serialize()", dgBytes1.length, dgBytes2.length); + assertTrue("raw drawing data ("+dgBytes1.length+" bytes) and aggregate.serialize() are different.", + Arrays.equals(dgBytes1, dgBytes2)); + } + } + + if(aggs.size() != 0){ + HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb); + for(int i = 0; i < wb2.getNumberOfSheets(); i++){ + DrawingAggregateInfo info1 = aggs.get(i); + if(info1 != null) { + HSSFSheet sheet2 = wb2.getSheetAt(i); + DrawingAggregateInfo info2 = DrawingAggregateInfo.get(sheet2); + byte[] dgBytes1 = info1.getRawBytes(); + byte[] dgBytes2 = info2.getRawBytes(); + assertEquals("different size of drawing data before and after save", dgBytes1.length, dgBytes2.length); + assertTrue("drawing data ("+dgBytes1.length+" bytes) before and after save is different.", + Arrays.equals(dgBytes1, dgBytes2)); + } + } + } + } + + /** + * test that we correctly read and write drawing aggregates + * in all .xls files in POI test samples + */ + public void testAllTestSamples(){ + File[] xls = new File(System.getProperty("POI.testdata.path"), "spreadsheet").listFiles( + new FilenameFilter() { + public boolean accept(File dir, String name) { + return name.endsWith(".xls"); + } + } + ); + for(File file : xls) { + HSSFWorkbook wb; + try { + wb = HSSFTestDataSamples.openSampleWorkbook(file.getName()); + } catch (Throwable e){ + // don't bother about files we cannot read - they are different bugs + // System.out.println("[WARN] Cannot read " + file.getName()); + continue; + } + assertWriteAndReadBack(wb); + } + } + + /** + * when reading incomplete data ensure that the serialized bytes + match the source + */ + public void testIncompleteData(){ + //EscherDgContainer and EscherSpgrContainer length exceeds the actual length of the data + String hex = + " 0F 00 02 F0 30 03 00 00 10 00 08 F0 08 00 00 " + + " 00 07 00 00 00 B2 04 00 00 0F 00 03 F0 18 03 00 " + + " 00 0F 00 04 F0 28 00 00 00 01 00 09 F0 10 00 00 " + + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 02 00 0A F0 08 00 00 00 00 04 00 00 05 00 00 " + + " 00 0F 00 04 F0 74 00 00 00 92 0C 0A F0 08 00 00 " + + " 00 AD 04 00 00 00 0A 00 00 63 00 0B F0 3A 00 00 " + + " 00 7F 00 04 01 E5 01 BF 00 08 00 08 00 81 01 4E " + + " 00 00 08 BF 01 10 00 10 00 80 C3 16 00 00 00 BF " + + " 03 00 00 02 00 44 00 69 00 61 00 67 00 72 00 61 " + + " 00 6D 00 6D 00 20 00 32 00 00 00 00 00 10 F0 12 " + + " 00 00 00 00 00 05 00 00 00 01 00 00 00 0B 00 00 " + + " 00 0F 00 66 00 00 00 11 F0 00 00 00 00 "; + byte[] buffer = HexRead.readFromString(hex); + + List records = new ArrayList(); + EscherRecordFactory recordFactory = new DefaultEscherRecordFactory(); + int pos = 0; + while (pos < buffer.length) { + EscherRecord r = recordFactory.createRecord(buffer, pos); + int bytesRead = r.fillFields(buffer, pos, recordFactory); + records.add(r); + pos += bytesRead; + } + assertEquals("data was not fully read", buffer.length, pos); + + // serialize to byte array + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + for(EscherRecord r : records) out.write(r.serialize()); + } catch (IOException e){ + throw new RuntimeException(e); + } + assertEquals(HexDump.toHex(buffer, 10), HexDump.toHex(out.toByteArray(), 10)); + } + + /** + * TODO: figure out why it fails with "RecordFormatException: 0 bytes written but getRecordSize() reports 80" + */ + public void testFailing(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("15573.xls"); + HSSFSheet sh = wb.getSheetAt(0); + sh.getDrawingPatriarch(); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + } + + private static byte[] toByteArray(List records) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (RecordBase rb : records) { + Record r = (Record) rb; + try { + out.write(r.serialize()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return out.toByteArray(); + } + + public void testSolverContainerMustBeSavedDuringSerialization() throws IOException{ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("SolverContainerAfterSPGR.xls"); + HSSFSheet sh = wb.getSheetAt(0); + InternalSheet ish = HSSFTestHelper.getSheetForTest(sh); + List records = ish.getRecords(); + // records to be aggregated + List dgRecords = records.subList(19, 22); + byte[] dgBytes = toByteArray(dgRecords); + sh.getDrawingPatriarch(); + EscherAggregate agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid); + assertEquals(agg.getEscherRecords().get(0).getChildRecords().size(), 3); + assertEquals(agg.getEscherRecords().get(0).getChild(2).getRecordId(), EscherContainerRecord.SOLVER_CONTAINER); + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + sh.getDrawingPatriarch(); + ish = HSSFTestHelper.getSheetForTest(sh); + agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid); + assertEquals(agg.getEscherRecords().get(0).getChildRecords().size(), 3); + assertEquals(agg.getEscherRecords().get(0).getChild(2).getRecordId(), EscherContainerRecord.SOLVER_CONTAINER); + + + // collect drawing records into a byte buffer. + agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid); + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } + + public void testFileWithTextbox() throws IOException{ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("text.xls"); + HSSFSheet sh = wb.getSheetAt(0); + InternalSheet ish = HSSFTestHelper.getSheetForTest(sh); + List records = ish.getRecords(); + // records to be aggregated + List dgRecords = records.subList(19, 23); + byte[] dgBytes = toByteArray(dgRecords); + sh.getDrawingPatriarch(); + + // collect drawing records into a byte buffer. + EscherAggregate agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid); + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } + + public void testFileWithCharts(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("49581.xls"); + HSSFSheet sh = wb.getSheetAt(0); + InternalSheet ish = HSSFTestHelper.getSheetForTest(sh); + List records = ish.getRecords(); + // records to be aggregated + List dgRecords = records.subList(19, 21); + byte[] dgBytes = toByteArray(dgRecords); + sh.getDrawingPatriarch(); + + // collect drawing records into a byte buffer. + EscherAggregate agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid); + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + for (int i=0; i< dgBytes.length; i++){ + if (dgBytes[i] != dgBytesAfterSave[i]){ + System.out.println("pos = " + i); + } + } + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } + + /** + * test reading drawing aggregate from a test file from Bugzilla 45129 + */ + public void test45129() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("45129.xls"); + HSSFSheet sh = wb.getSheetAt(0); + + InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb); + InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh); + + List records = isheet.getRecords(); + + // the sheet's drawing is not aggregated + assertEquals("wrong size of sheet records stream", 394, records.size()); + // the last record before the drawing block + assertTrue( + "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(), + records.get(18) instanceof RowRecordsAggregate); + + // records to be aggregated + List dgRecords = records.subList(19, 389); + // collect drawing records into a byte buffer. + byte[] dgBytes = toByteArray(dgRecords); + + for (RecordBase rb : dgRecords) { + Record r = (Record) rb; + short sid = r.getSid(); + // we expect that drawing block consists of either + // DrawingRecord or ContinueRecord or ObjRecord or TextObjectRecord + assertTrue( + sid == DrawingRecord.sid || + sid == ContinueRecord.sid || + sid == ObjRecord.sid || + sid == TextObjectRecord.sid); + } + + // the first record after the drawing block + assertTrue( + "records.get(389) is expected to be Window2", + records.get(389) instanceof WindowTwoRecord); + + // aggregate drawing records. + // The subrange [19, 388] is expected to be replaced with a EscherAggregate object + DrawingManager2 drawingManager = iworkbook.findDrawingGroup(); + int loc = isheet.aggregateDrawingRecords(drawingManager, false); + EscherAggregate agg = (EscherAggregate) records.get(loc); + + assertEquals("wrong size of the aggregated sheet records stream", 25, records.size()); + assertTrue( + "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(), + records.get(18) instanceof RowRecordsAggregate); + assertTrue("records.get(19) is expected to be EscherAggregate but was " + records.get(19).getClass().getSimpleName(), + records.get(19) instanceof EscherAggregate); + assertTrue("records.get(20) is expected to be Window2 but was " + records.get(20).getClass().getSimpleName(), + records.get(20) instanceof WindowTwoRecord); + + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } + + /** + * Try to check file with such record sequence + * ... + * DrawingRecord + * ContinueRecord + * ObjRecord | TextObjRecord + * ... + */ + public void testSerializeDrawingBigger8k() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("DrawingContinue.xls"); + InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb); + HSSFSheet sh = wb.getSheetAt(0); + InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh); + + + List records = isheet.getRecords(); + + // the sheet's drawing is not aggregated + assertEquals("wrong size of sheet records stream", 32, records.size()); + // the last record before the drawing block + assertTrue( + "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(), + records.get(18) instanceof RowRecordsAggregate); + + // records to be aggregated + List dgRecords = records.subList(19, 26); + for (RecordBase rb : dgRecords) { + Record r = (Record) rb; + short sid = r.getSid(); + // we expect that drawing block consists of either + // DrawingRecord or ContinueRecord or ObjRecord or TextObjectRecord + assertTrue( + sid == DrawingRecord.sid || + sid == ContinueRecord.sid || + sid == ObjRecord.sid || + sid == NoteRecord.sid || + sid == TextObjectRecord.sid); + } + // collect drawing records into a byte buffer. + byte[] dgBytes = toByteArray(dgRecords); + + // the first record after the drawing block + assertTrue( + "records.get(26) is expected to be Window2", + records.get(26) instanceof WindowTwoRecord); + + // aggregate drawing records. + // The subrange [19, 38] is expected to be replaced with a EscherAggregate object + DrawingManager2 drawingManager = iworkbook.findDrawingGroup(); + int loc = isheet.aggregateDrawingRecords(drawingManager, false); + EscherAggregate agg = (EscherAggregate) records.get(loc); + + assertEquals("wrong size of the aggregated sheet records stream", 26, records.size()); + assertTrue( + "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(), + records.get(18) instanceof RowRecordsAggregate); + assertTrue("records.get(19) is expected to be EscherAggregate but was " + records.get(19).getClass().getSimpleName(), + records.get(19) instanceof EscherAggregate); + assertTrue("records.get(20) is expected to be Window2 but was " + records.get(20).getClass().getSimpleName(), + records.get(20) instanceof WindowTwoRecord); + + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + + } + + + public void testSerializeDrawingBigger8k_noAggregation() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("DrawingContinue.xls"); + + InternalSheet isheet = HSSFTestHelper.getSheetForTest(wb.getSheetAt(0)); + List records = isheet.getRecords(); + + HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb); + InternalSheet isheet2 = HSSFTestHelper.getSheetForTest(wb2.getSheetAt(0)); + List records2 = isheet2.getRecords(); + + assertEquals(records.size(), records2.size()); + for (int i = 0; i < records.size(); i++) { + RecordBase r1 = records.get(i); + RecordBase r2 = records2.get(i); + assertTrue(r1.getClass() == r2.getClass()); + assertEquals(r1.getRecordSize(), r2.getRecordSize()); + if (r1 instanceof Record) { + assertEquals(((Record) r1).getSid(), ((Record) r2).getSid()); + assertTrue(Arrays.equals(((Record) r1).serialize(), ((Record) r2).serialize())); + } + } + } + + public void testSerializeDrawingWithComments() throws IOException { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("DrawingAndComments.xls"); + HSSFSheet sh = wb.getSheetAt(0); + InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb); + InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh); + + List records = isheet.getRecords(); + + // the sheet's drawing is not aggregated + assertEquals("wrong size of sheet records stream", 46, records.size()); + // the last record before the drawing block + assertTrue( + "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(), + records.get(18) instanceof RowRecordsAggregate); + + // records to be aggregated + List dgRecords = records.subList(19, 39); + for (RecordBase rb : dgRecords) { + Record r = (Record) rb; + short sid = r.getSid(); + // we expect that drawing block consists of either + // DrawingRecord or ContinueRecord or ObjRecord or TextObjectRecord + assertTrue( + sid == DrawingRecord.sid || + sid == ContinueRecord.sid || + sid == ObjRecord.sid || + sid == NoteRecord.sid || + sid == TextObjectRecord.sid); + } + // collect drawing records into a byte buffer. + byte[] dgBytes = toByteArray(dgRecords); + + // the first record after the drawing block + assertTrue( + "records.get(39) is expected to be Window2", + records.get(39) instanceof WindowTwoRecord); + + // aggregate drawing records. + // The subrange [19, 38] is expected to be replaced with a EscherAggregate object + DrawingManager2 drawingManager = iworkbook.findDrawingGroup(); + int loc = isheet.aggregateDrawingRecords(drawingManager, false); + EscherAggregate agg = (EscherAggregate) records.get(loc); + + assertEquals("wrong size of the aggregated sheet records stream", 27, records.size()); + assertTrue( + "records.get(18) is expected to be RowRecordsAggregate but was " + records.get(18).getClass().getSimpleName(), + records.get(18) instanceof RowRecordsAggregate); + assertTrue("records.get(19) is expected to be EscherAggregate but was " + records.get(19).getClass().getSimpleName(), + records.get(19) instanceof EscherAggregate); + assertTrue("records.get(20) is expected to be Window2 but was " + records.get(20).getClass().getSimpleName(), + records.get(20) instanceof WindowTwoRecord); + + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } + + + public void testFileWithPictures() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("ContinueRecordProblem.xls"); + HSSFSheet sh = wb.getSheetAt(0); + + InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb); + InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh); + + List records = isheet.getRecords(); + + // the sheet's drawing is not aggregated + assertEquals("wrong size of sheet records stream", 315, records.size()); + // the last record before the drawing block + assertTrue( + "records.get(21) is expected to be RowRecordsAggregate but was " + records.get(21).getClass().getSimpleName(), + records.get(21) instanceof RowRecordsAggregate); + + // records to be aggregated + List dgRecords = records.subList(22, 300); + for (RecordBase rb : dgRecords) { + Record r = (Record) rb; + short sid = r.getSid(); + // we expect that drawing block consists of either + // DrawingRecord or ContinueRecord or ObjRecord or TextObjectRecord + assertTrue( + sid == DrawingRecord.sid || + sid == ContinueRecord.sid || + sid == ObjRecord.sid || + sid == TextObjectRecord.sid); + } + // collect drawing records into a byte buffer. + byte[] dgBytes = toByteArray(dgRecords); + + // the first record after the drawing block + assertTrue( + "records.get(300) is expected to be Window2", + records.get(300) instanceof WindowTwoRecord); + + // aggregate drawing records. + // The subrange [19, 299] is expected to be replaced with a EscherAggregate object + DrawingManager2 drawingManager = iworkbook.findDrawingGroup(); + int loc = isheet.aggregateDrawingRecords(drawingManager, false); + EscherAggregate agg = (EscherAggregate) records.get(loc); + + assertEquals("wrong size of the aggregated sheet records stream", 38, records.size()); + assertTrue( + "records.get(21) is expected to be RowRecordsAggregate but was " + records.get(21).getClass().getSimpleName(), + records.get(21) instanceof RowRecordsAggregate); + assertTrue("records.get(22) is expected to be EscherAggregate but was " + records.get(22).getClass().getSimpleName(), + records.get(22) instanceof EscherAggregate); + assertTrue("records.get(23) is expected to be Window2 but was " + records.get(23).getClass().getSimpleName(), + records.get(23) instanceof WindowTwoRecord); + + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } + + public void testUnhandledContinue() { + String data = + " EC 00 1C 08 0F 00 02 F0 66 27 00 " + + " 00 10 00 08 F0 08 00 00 00 06 00 00 00 13 04 00 " + + " 00 0F 00 03 F0 4E 27 00 00 0F 00 04 F0 28 00 00 " + + " 00 01 00 09 F0 10 00 00 00 00 00 00 00 00 00 00 " + + " 00 00 00 00 00 00 00 00 00 02 00 0A F0 08 00 00 " + + " 00 00 04 00 00 05 00 00 00 0F 00 04 F0 CC 07 00 " + + " 00 A2 0C 0A F0 08 00 00 00 0F 04 00 00 00 0A 00 " + + " 00 93 00 0B F0 4A 00 00 00 7F 00 00 00 04 00 80 " + + " 00 C0 D5 9A 02 85 00 02 00 00 00 8B 00 02 00 00 " + + " 00 BF 00 1A 00 1F 00 BF 01 01 00 11 00 FF 01 00 " + + " 00 08 00 80 C3 14 00 00 00 BF 03 00 00 02 00 54 " + + " 00 65 00 78 00 74 00 42 00 6F 00 78 00 20 00 31 " + + " 00 00 00 13 00 22 F1 38 07 00 00 A9 C3 32 07 00 " + + " 00 50 4B 03 04 14 00 06 00 08 00 00 00 21 00 F0 " + + " F7 8A BB FD 00 00 00 E2 01 00 00 13 00 00 00 5B " + + " 43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 5D 2E 78 " + + " 6D 6C 94 91 CD 4A C4 30 10 C7 EF 82 EF 10 E6 2A " + + " 6D AA 07 11 69 BA 07 AB 47 15 5D 1F 60 48 A6 6D " + + " D8 36 09 99 58 77 DF DE 74 3F 2E E2 0A 1E 67 E6 " + + " FF F1 23 A9 57 DB 69 14 33 45 B6 DE 29 B8 2E 2B " + + " 10 E4 B4 37 D6 F5 0A 3E D6 4F C5 1D 08 4E E8 0C " + + " 8E DE 91 82 1D 31 AC 9A CB 8B 7A BD 0B C4 22 BB " + + " 1D 2B 18 52 0A F7 52 B2 1E 68 42 2E 7D 20 97 2F " + + " 9D 8F 13 A6 3C C6 5E 06 D4 1B EC 49 DE 54 D5 AD " + + " D4 DE 25 72 A9 48 4B 06 34 75 4B 1D 7E 8E 49 3C " + + " 6E F3 FA 40 12 69 64 10 0F 07 E1 D2 A5 00 43 18 " + + " AD C6 94 49 E5 EC CC 8F 96 E2 D8 50 66 E7 5E C3 " + + " 83 0D 7C 95 31 40 FE DA B0 5C CE 17 1C 7D 2F F9 " + + " 69 A2 35 24 5E 31 A6 67 9C 32 86 34 91 25 0F 18 " + + " 28 6B CA BF 53 16 CC 89 0B DF 75 56 53 D9 46 7E " + + " 5F 7C 27 A8 73 E1 C6 7F B9 48 F3 7F B3 DB 6C 7B " + + " A3 F9 94 2E F7 3F D4 7C 03 00 00 FF FF 03 00 50 " + + " 4B 03 04 14 00 06 00 08 00 00 00 21 00 31 DD 5F " + + " 61 D2 00 00 00 8F 01 00 00 0B 00 00 00 5F 72 65 " + + " 6C 73 2F 2E 72 65 6C 73 A4 90 C1 6A C3 30 0C 86 " + + " EF 83 BD 83 D1 BD 71 DA 43 19 A3 4E 6F 85 5E 4B " + + " 07 BB 0A 5B 49 4C 63 CB 58 26 6D DF BE A6 30 58 " + + " 46 6F 3B EA 17 FA 3E F1 EF F6 B7 30 A9 99 B2 78 " + + " 8E 06 D6 4D 0B 8A A2 65 E7 E3 60 E0 EB 7C 58 7D " + + " 80 92 82 D1 E1 C4 91 0C DC 49 60 DF BD BF ED 4E " + + " 34 61 A9 47 32 FA 24 AA 52 A2 18 18 4B 49 9F 5A " + + " 8B 1D 29 A0 34 9C 28 D6 4D CF 39 60 A9 63 1E 74 " + + " 42 7B C1 81 F4 A6 6D B7 3A FF 66 40 B7 60 AA A3 " + + " 33 90 8F 6E 03 EA 7C 4F D5 FC 87 1D BC CD 2C DC " + + " 97 C6 72 D0 DC F7 DE BE A2 6A C7 D7 78 A2 B9 52 " + + " 30 0F 54 0C B8 2C CF 30 D3 DC D4 E7 40 BF F6 AE " + + " FF E9 95 11 13 7D 57 FE 42 FC 4C AB F5 C7 AC 17 " + + " 35 76 0F 00 00 00 FF FF 03 00 50 4B 03 04 14 00 " + + " 06 00 08 00 00 00 21 00 AE B1 05 56 77 02 00 00 " + + " 1B 07 00 00 10 00 00 00 64 72 73 2F 73 68 61 70 " + + " 65 78 6D 6C 2E 78 6D 6C BC 55 4B 6F DB 30 0C BE " + + " 0F D8 7F 10 74 4F FD 58 9D 87 11 BB E8 52 74 97 " + + " 61 0D 92 E6 07 A8 B2 9C 18 93 25 43 52 13 A7 BF " + + " 7E A4 E4 A4 5D 0F 3B 34 43 2F 0A 43 4A FC C8 8F " + + " 0F CF 6F FA 56 92 BD 30 B6 D1 AA A0 C9 55 4C 89 " + + " 50 5C 57 8D DA 16 74 F3 78 3F 9A 52 62 1D 53 15 " + + " 93 5A 89 82 1E 85 A5 37 E5 D7 2F F3 BE 32 39 53 " + + " 7C A7 0D 01 17 CA E6 A0 28 E8 CE B9 2E 8F 22 CB " + + " 77 A2 65 F6 4A 77 42 81 B5 D6 A6 65 0E FE 9A 6D " + + " 54 19 76 00 E7 AD 8C D2 38 1E 47 B6 33 82 55 76 " + + " 27 84 BB 0B 16 5A 7A DF 80 B6 10 52 DE 7A 88 A0 " + + " AA 8D 6E 83 C4 B5 2C 93 79 84 31 A0 E8 1F 80 F0 " + + " 50 D7 65 1A CF B2 2C 3E DB 50 E5 CD 46 1F CA 6F " + + " 41 8D E2 49 87 F6 64 1A CF 26 D9 D9 E6 9F 78 DF " + + " AF 80 A2 77 84 F7 05 CD D2 2C 8D 81 12 7E 2C 68 " + + " 3A BE CE C6 31 8D 82 2F DB 91 96 71 A3 0B 4A 89 " + + " 83 EB B2 51 BF 41 0E 46 B5 5F 77 4B 13 64 FE 6B " + + " BF 34 A4 A9 C0 01 25 8A B5 40 EA 23 DC FF AE 7B " + + " 92 9C 9C C1 1D 7C 40 5C 0F 6A 28 0B EA 7D 44 6F " + + " 1D 59 EF 92 E5 7D 6D DA A1 06 EC 03 15 68 59 A3 " + + " 20 4C 96 EB BA 26 00 36 4D 66 49 06 6D 00 19 4E " + + " B2 F4 7A 92 21 38 CB FF CD 40 14 C2 C0 8B 9D B1 " + + " EE 87 D0 17 87 44 D0 51 41 8D E0 CE 87 C7 F6 3F " + + " AD 43 1E 5E 21 10 4E E9 FB 46 CA 4B F3 3F F1 1B " + + " 38 C5 BE B2 EE 28 05 02 48 B5 12 40 8C EF F1 0F " + + " F3 0B F5 06 6A 63 9F 88 E5 66 FB B4 90 86 C0 BC " + + " 00 CF 30 67 70 3E E1 19 B2 F3 80 88 5C 43 62 9F " + + " 8C 3D 40 22 BA A8 6B A0 FE 93 F1 CF A0 3E 7F AD " + + " FE 1F 7E DB 28 6D 06 FE 61 39 09 2C C0 9E C9 82 " + + " BA 3E 8C 17 F0 1D F0 86 51 1B 1A 00 7B 01 C7 B0 " + + " 3A 62 48 4F F0 0B 73 79 69 37 C0 C2 75 0F 70 D4 " + + " 52 1F 0A CA 65 D3 51 02 9B F4 E5 BD EE 60 58 57 " + + " 50 05 BB 90 12 E3 E4 42 43 BC D0 2C 61 EF 42 E8 " + + " 21 9F EE F6 D9 C1 14 0C C3 11 42 C4 60 A5 75 6B " + + " EC E2 4B C3 F5 F3 DF 5D EA 05 23 32 FE 00 02 25 " + + " C3 EF 8B 50 A3 CD 1A BE 2F 2F B0 E2 92 D8 F7 3F " + + " CB 5D 89 EB 73 04 EB 1D 65 3C FD 2B A1 AA 25 33 " + + " 6C 75 7E 6C 9E 47 AB CD DF 8F 71 35 0C D5 3B 95 " + + " CC 6F 4D 0B 5A FF AD 90 8D 50 EE 8E 39 76 9A F7 " + + " 77 5F 19 7F 3B B0 5B FE 01 00 00 FF FF 03 00 50 " + + " 4B 03 04 14 00 06 00 08 00 00 00 21 00 44 8B 69 " + + " 1E 2C 01 00 00 AB 01 00 00 0F 00 00 00 64 72 73 " + + " 2F 64 6F 77 6E 72 65 76 2E 78 6D 6C 5C 50 D1 4E " + + " C2 30 14 7D 37 F1 1F 9A 6B E2 9B 74 0C 36 07 52 " + + " C8 62 42 C4 04 D1 21 89 AF 65 6D D9 E2 DA CE B6 " + + " C0 F0 EB ED 46 0C C6 C7 73 EE 39 E7 DE 73 27 B3 " + + " 46 56 E8 C0 8D 2D B5 22 D0 EF 05 80 B8 CA 35 2B " + + " D5 8E C0 E6 7D 7E 97 00 B2 8E 2A 46 2B AD 38 81 " + + " 13 B7 30 9B 5E 5F 4D E8 98 E9 A3 CA F8 61 ED 76 " + + " C8 87 28 3B A6 04 0A E7 EA 31 C6 36 2F B8 A4 B6 " + + " A7 6B AE FC 4C 68 23 A9 F3 D0 EC 30 33 F4 E8 C3 " + + " 65 85 C3 20 88 B1 A4 A5 F2 1B 0A 5A F3 C7 82 E7 " + + " 9F EB BD 24 B0 64 E2 E5 63 58 CE 9F A9 F8 4A 17 " + + " C3 2C 89 0E AF 2C 22 E4 F6 A6 49 1F 00 39 DE B8 " + + " 8B B8 DE AB 55 B8 5C 6E DF 36 E9 AF A0 8B 5B 30 " + + " 02 21 20 F1 74 DA 9A 92 65 D4 3A 6E 08 F8 7E BE " + + " AD 6F 0A 53 5F A1 A9 52 95 17 DA 20 91 71 5B 7E " + + " FB 7E 67 5E 18 2D 91 D1 47 02 03 40 B9 AE 5A BE " + + " C5 2B 21 2C 77 1E 25 C1 E8 3E EA 46 BF 54 18 8C " + + " A2 28 00 DC C6 3A 7D 36 9F 15 DD 19 7F CC 71 1C " + + " FF F3 F6 C3 41 12 46 AD 17 5F 6E EA C0 E5 C7 D3 " + + " 1F 00 00 00 FF FF 03 00 50 4B 01 02 2D 00 14 00 " + + " 06 00 08 00 00 00 21 00 F0 F7 8A BB FD 00 00 00 " + + " E2 01 00 00 13 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 00 00 00 00 00 5B 43 6F 6E 74 65 6E 74 5F 54 " + + " 79 70 65 73 5D 2E 78 6D 6C 50 4B 01 02 2D 00 14 " + + " 00 06 00 08 00 00 00 21 00 31 DD 5F 61 D2 00 00 " + + " 00 8F 01 00 00 0B 00 00 00 00 00 00 00 00 00 00 " + + " 00 00 00 2E 01 00 00 5F 72 65 6C 73 2F 2E 72 65 " + + " 6C 73 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 " + + " 21 00 AE B1 05 56 77 02 00 00 1B 07 00 00 10 00 " + + " 00 00 00 00 00 00 00 00 00 00 00 00 29 02 00 00 " + + " 64 72 73 2F 73 68 61 70 65 78 6D 6C 2E 78 6D 6C " + + " 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 21 00 " + + " 44 8B 69 1E 2C 01 00 00 AB 01 00 00 0F 00 00 00 " + + " 00 00 00 00 00 00 00 00 00 00 CE 04 00 00 64 72 " + + " 73 2F 64 6F 77 6E 72 65 76 2E 78 6D 6C 50 4B 05 " + + " 06 00 00 00 00 04 00 04 00 F5 00 00 00 27 06 00 " + + " 00 00 00 00 00 10 F0 12 00 00 00 02 00 01 00 60 " + + " 01 03 00 F3 00 02 00 D0 00 05 00 5A 00 00 00 11 " + + " F0 00 00 00 00 5D 00 1A 00 15 00 12 00 06 00 0F " + + " 00 11 60 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 00 00 EC 00 08 00 00 00 0D F0 00 00 00 00 B6 " + + " 01 12 00 12 02 00 00 00 00 00 00 00 00 06 00 10 " + + " 00 00 00 00 00 3C 00 07 00 00 74 65 78 74 2D 31 " + + " 3C 00 10 00 00 00 16 00 00 00 00 00 06 00 00 00 " + + " 00 00 00 00 EC 00 CB 07 0F 00 04 F0 CB 07 00 00 " + + " A2 0C 0A F0 08 00 00 00 10 04 00 00 00 0A 00 00 " + + " 93 00 0B F0 4A 00 00 00 7F 00 00 00 04 00 80 00 " + + " 00 65 53 02 85 00 02 00 00 00 8B 00 02 00 00 00 " + + " BF 00 1A 00 1F 00 BF 01 01 00 11 00 FF 01 00 00 " + + " 08 00 80 C3 14 00 00 00 BF 03 00 00 02 00 54 00 " + + " 65 00 78 00 74 00 42 00 6F 00 78 00 20 00 32 00 " + + " 00 00 13 00 22 F1 37 07 00 00 A9 C3 31 07 00 00 " + + " 50 4B 03 04 14 00 06 00 08 00 00 00 21 00 F0 F7 " + + " 8A BB FD 00 00 00 E2 01 00 00 13 00 00 00 5B 43 " + + " 6F 6E 74 65 6E 74 5F 54 79 70 65 73 5D 2E 78 6D " + + " 6C 94 91 CD 4A C4 30 10 C7 EF 82 EF 10 E6 2A 6D " + + " AA 07 11 69 BA 07 AB 47 15 5D 1F 60 48 A6 6D D8 " + + " 36 09 99 58 77 DF DE 74 3F 2E E2 0A 1E 67 E6 FF " + + " F1 23 A9 57 DB 69 14 33 45 B6 DE 29 B8 2E 2B 10 " + + " E4 B4 37 D6 F5 0A 3E D6 4F C5 1D 08 4E E8 0C 8E " + + " DE 91 82 1D 31 AC 9A CB 8B 7A BD 0B C4 22 BB 1D " + + " 2B 18 52 0A F7 52 B2 1E 68 42 2E 7D 20 97 2F 9D " + + " 8F 13 A6 3C C6 5E 06 D4 1B EC 49 DE 54 D5 AD D4 " + + " DE 25 72 A9 48 4B 06 34 75 4B 1D 7E 8E 49 3C 6E " + + " F3 FA 40 12 69 64 10 0F 07 E1 D2 A5 00 43 18 AD " + + " C6 94 49 E5 EC CC 8F 96 E2 D8 50 66 E7 5E C3 83 " + + " 0D 7C 95 31 40 FE DA B0 5C CE 17 1C 7D 2F F9 69 " + + " A2 35 24 5E 31 A6 67 9C 32 86 34 91 25 0F 18 28 " + + " 6B CA BF 53 16 CC 89 0B DF 75 56 53 D9 46 7E 5F " + + " 7C 27 A8 73 E1 C6 7F B9 48 F3 7F B3 DB 6C 7B A3 " + + " F9 94 2E F7 3F D4 7C 03 00 00 FF FF 03 00 50 4B " + + " 03 04 14 00 06 00 08 00 00 00 21 00 31 DD 5F 61 " + + " D2 00 00 00 8F 01 00 00 0B 00 00 00 5F 72 65 6C " + + " 73 2F 2E 72 65 6C 73 A4 90 C1 6A C3 30 0C 86 EF " + + " 83 BD 83 D1 BD 71 DA 43 19 A3 4E 6F 85 5E 4B 07 " + + " BB 0A 5B 49 4C 63 CB 58 26 6D DF BE A6 30 58 46 " + + " 6F 3B EA 17 FA 3E F1 EF F6 B7 30 A9 99 B2 78 8E " + + " 06 D6 4D 0B 8A A2 65 E7 E3 60 E0 EB 7C 58 7D 80 " + + " 92 82 D1 E1 C4 91 0C DC 49 60 DF BD BF ED 4E 34 " + + " 61 A9 47 32 FA 24 AA 52 A2 18 18 4B 49 9F 5A 8B " + + " 1D 29 A0 34 9C 28 D6 4D CF 39 60 A9 63 1E 74 42 " + + " 7B C1 81 F4 A6 6D B7 3A FF 66 40 B7 60 AA A3 33 " + + " 90 8F 6E 03 EA 7C 4F D5 FC 87 1D BC CD 2C DC 97 " + + " C6 72 D0 DC F7 DE BE A2 6A C7 D7 78 A2 B9 52 30 " + + " 0F 54 0C B8 2C CF 30 D3 DC D4 E7 40 BF F6 AE FF " + + " E9 95 11 13 7D 57 FE 42 FC 4C AB F5 C7 AC 17 35 " + + " 76 0F 00 00 00 FF FF 03 00 50 4B 03 04 14 00 06 " + + " 00 08 00 00 00 21 00 99 C9 E2 87 75 02 00 00 1B " + + " 07 00 00 10 00 00 00 64 72 73 2F 73 68 61 70 65 " + + " 78 6D 6C 2E 78 6D 6C BC 55 CD 6E E2 30 10 BE AF " + + " D4 77 B0 7C A7 F9 E9 82 20 C2 A9 5A AA EE 65 B5 " + + " 45 50 1E C0 24 0E 44 EB D8 91 ED 42 E8 D3 EF 8C " + + " 9D D0 6E 0F 7B 28 AB 5E 1C 67 C6 33 DF F8 9B 1F " + + " CF 6F BB 46 92 83 30 B6 D6 8A D1 E4 3A A6 44 A8 " + + " 42 97 B5 DA 31 BA 79 7E 1C 4D 29 B1 8E AB 92 4B " + + " AD 04 A3 27 61 E9 6D 7E F5 6D DE 95 26 E3 AA D8 " + + " 6B 43 C0 85 B2 19 08 18 DD 3B D7 66 51 64 8B BD " + + " 68 B8 BD D6 AD 50 A0 AD B4 69 B8 83 5F B3 8B 4A " + + " C3 8F E0 BC 91 51 1A C7 93 C8 B6 46 F0 D2 EE 85 " + + " 70 0F 41 43 73 EF 1B D0 16 42 CA 3B 0F 11 44 95 " + + " D1 4D D8 15 5A E6 C9 3C C2 18 70 EB 0D 60 F3 54 " + + " 55 79 1A CF C6 E3 F8 AC 43 91 57 1B 7D CC 27 41 " + + " 8C DB 41 86 FA D9 38 1D 2C 40 E5 2D BC EB 37 3C " + + " D1 39 52 74 8C C2 C1 34 06 46 8A 13 A3 E9 E4 FB " + + " 78 12 D3 28 B8 B2 2D 69 78 61 34 A3 94 38 38 2E " + + " 6B F5 1B F6 41 A9 0E EB 76 69 C2 BE F8 75 58 1A " + + " 52 97 8C DE 50 A2 78 03 9C 3E C3 F9 7B DD 91 74 " + + " 70 06 67 D0 80 B8 0E C4 90 15 94 FB 88 DE 3B B2 " + + " DE 25 CF BA CA 34 7D 0A F8 27 12 D0 F0 5A 41 98 " + + " 3C D3 55 45 00 6C 9A CC 92 31 54 01 DC 30 49 6F " + + " A6 C0 0C A2 F3 EC DF 14 44 21 0E 3C D8 1A EB 7E " + + " 08 7D 71 4C 04 1D 31 6A 44 E1 7C 7C FC F0 D3 3A " + + " 24 E2 0D 02 E1 94 7E AC A5 BC 94 80 81 E0 40 2A " + + " D6 95 75 27 29 10 40 AA 95 00 66 7C 8D 7F 9A 60 " + + " 48 38 70 1B FB 8B D8 C2 EC B6 0B 69 08 F4 0B 10 " + + " 0D 7D 06 EB 16 D7 70 3B 0F 88 C8 15 5C EC 8B B1 " + + " 7B 48 44 17 55 05 D4 7F 31 FE 19 D4 DF 5F AB FF " + + " 87 DF D4 4A 9B 9E 7F 18 4E 02 13 70 E0 92 51 D7 " + + " 85 FE 02 BE 03 5E DF 6B 7D 01 60 2D 60 1F 96 27 " + + " 0C 69 0B 5F 68 CC 4B AB 01 06 AE 7B 82 A5 92 FA " + + " C8 68 21 EB 96 12 98 A4 AF 1F 65 47 C3 5B 46 15 " + + " CC 42 4A 8C 93 0B 0D F1 42 B1 84 B9 0B A1 87 FB " + + " B4 77 2F 0E BA A0 6F 8E 10 22 06 2B AD 5B 63 15 " + + " 5F 1A AE EF FF F6 52 2F 18 91 F1 0B 10 28 39 BE " + + " 2F 42 8D 36 6B 78 5F 5E 61 D6 24 71 3F 68 5C 8E " + + " F3 73 94 62 A3 3B DF EE DE 4A A8 72 C9 0D 5F 9D " + + " 8D CD CB 68 B5 F9 DB 18 47 43 9F BD 21 65 7E 6C " + + " 5A 90 FA B7 42 D6 42 B9 07 EE F8 D0 EF 1F 5E 19 " + + " 7F 3A B0 9B FF 01 00 00 FF FF 03 00 50 4B 03 04 " + + " 14 00 06 00 08 00 00 00 21 00 56 84 64 08 2D 01 " + + " 00 00 AB 01 00 00 0F 00 00 00 64 72 73 2F 64 6F " + + " 77 6E 72 65 76 2E 78 6D 6C 5C 90 5F 4F C2 30 14 " + + " C5 DF 4D FC 0E CD 35 F1 4D B6 15 C6 3F 29 64 31 " + + " 21 62 82 E2 90 C4 D7 B2 B6 6C BA B6 B3 AD 30 F8 " + + " F4 76 20 21 FA 78 CE BD BF D3 7B 3A 9A D4 B2 44 " + + " 5B 6E 6C A1 15 81 A8 15 02 E2 2A D3 AC 50 1B 02 " + + " AB B7 E9 5D 1F 90 75 54 31 5A 6A C5 09 EC B9 85 " + + " C9 F8 FA 6A 44 87 4C EF 54 CA B7 4B B7 41 3E 44 " + + " D9 21 25 90 3B 57 0D 83 C0 66 39 97 D4 B6 74 C5 " + + " 95 9F 09 6D 24 75 5E 9A 4D C0 0C DD F9 70 59 06 " + + " 38 0C BB 81 A4 85 F2 2F E4 B4 E2 0F 39 CF 3E 97 " + + " DF 92 C0 9C 89 E7 F7 4E 31 7D A2 E2 2B 99 75 D2 " + + " 7E BC 5D B0 98 90 DB 9B 3A B9 07 E4 78 ED 2E CB " + + " 55 FE B1 C0 F3 F9 FA 75 95 9C 17 8E 71 33 46 A0 " + + " 0D 48 3C EE D7 A6 60 29 B5 8E 1B 02 BE 9F 6F EB " + + " 9B C2 D8 57 A8 CB 44 65 B9 36 48 A4 DC 16 07 DF " + + " EF E4 0B A3 25 32 7A 47 A0 0B 28 D3 65 E3 37 FA " + + " 45 08 CB 1D 81 41 8C 63 9F E4 27 67 07 87 83 D8 " + + " 5B 41 93 EA F4 89 ED FD B2 F8 0F 1B F5 A2 CE 3F " + + " 38 C2 ED 3E 8E 1B 38 B8 DC 74 14 97 3F 1E FF 00 " + + " 00 00 FF FF 03 00 50 4B 01 02 2D 00 14 00 06 00 " + + " 08 00 00 00 21 00 F0 F7 8A BB FD 00 00 00 E2 01 " + + " 00 00 13 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 00 00 00 5B 43 6F 6E 74 65 6E 74 5F 54 79 70 " + + " 65 73 5D 2E 78 6D 6C 50 4B 01 02 2D 00 14 00 06 " + + " 00 08 00 00 00 21 00 31 DD 5F 61 D2 00 00 00 8F " + + " 01 00 00 0B 00 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 2E 01 00 00 5F 72 65 6C 73 2F 2E 72 65 6C 73 " + + " 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 21 00 " + + " 99 C9 E2 87 75 02 00 00 1B 07 00 00 10 00 00 00 " + + " 00 00 00 00 00 00 00 00 00 00 29 02 00 00 64 72 " + + " 73 2F 73 68 61 70 65 78 6D 6C 2E 78 6D 6C 50 4B " + + " 01 02 2D 00 14 00 06 00 08 00 00 00 21 00 56 84 " + + " 64 08 2D 01 00 00 AB 01 00 00 0F 00 00 00 00 00 " + + " 00 00 00 00 00 00 00 00 CC 04 00 00 64 72 73 2F " + + " 64 6F 77 6E 72 65 76 2E 78 6D 6C 50 4B 05 06 00 " + + " 00 00 00 04 00 04 00 F5 00 00 00 26 06 00 00 00 " + + " 00 00 00 10 F0 12 00 00 00 02 00 01 00 60 01 06 " + + " 00 80 00 02 00 D0 00 07 00 E6 00 00 00 11 F0 00 " + + " 00 00 00 5D 00 1A 00 15 00 12 00 06 00 10 00 11 " + + " 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 EC 00 08 00 00 00 0D F0 00 00 00 00 B6 01 12 " + + " 00 12 02 00 00 00 00 00 00 00 00 06 00 10 00 00 " + + " 00 00 00 3C 00 07 00 00 74 65 78 74 2D 32 3C 00 " + + " 10 00 00 00 16 00 00 00 00 00 06 00 00 00 00 00 " + + " 00 00 EC 00 CB 07 0F 00 04 F0 CB 07 00 00 A2 0C " + + " 0A F0 08 00 00 00 11 04 00 00 00 0A 00 00 93 00 " + + " 0B F0 4A 00 00 00 7F 00 00 00 04 00 80 00 00 D1 " + + " 53 02 85 00 02 00 00 00 8B 00 02 00 00 00 BF 00 " + + " 1A 00 1F 00 BF 01 01 00 11 00 FF 01 00 00 08 00 " + + " 80 C3 14 00 00 00 BF 03 00 00 02 00 54 00 65 00 " + + " 78 00 74 00 42 00 6F 00 78 00 20 00 33 00 00 00 " + + " 13 00 22 F1 37 07 00 00 A9 C3 31 07 00 00 50 4B " + + " 03 04 14 00 06 00 08 00 00 00 21 00 F0 F7 8A BB " + + " FD 00 00 00 E2 01 00 00 13 00 00 00 5B 43 6F 6E " + + " 74 65 6E 74 5F 54 79 70 65 73 5D 2E 78 6D 6C 94 " + + " 91 CD 4A C4 30 10 C7 EF 82 EF 10 E6 2A 6D AA 07 " + + " 11 69 BA 07 AB 47 15 5D 1F 60 48 A6 6D D8 36 09 " + + " 99 58 77 DF DE 74 3F 2E E2 0A 1E 67 E6 FF F1 23 " + + " A9 57 DB 69 14 33 45 B6 DE 29 B8 2E 2B 10 E4 B4 " + + " 37 D6 F5 0A 3E D6 4F C5 1D 08 4E E8 0C 8E DE 91 " + + " 82 1D 31 AC 9A CB 8B 7A BD 0B C4 22 BB 1D 2B 18 " + + " 52 0A F7 52 B2 1E 68 42 2E 7D 20 97 2F 9D 8F 13 " + + " A6 3C C6 5E 06 D4 1B EC 49 DE 54 D5 AD D4 DE 25 " + + " 72 A9 48 4B 06 34 75 4B 1D 7E 8E 49 3C 6E F3 FA " + + " 40 12 69 64 10 0F 07 E1 D2 A5 00 43 18 AD C6 94 " + + " 49 E5 EC CC 8F 96 E2 D8 50 66 E7 5E C3 83 0D 7C " + + " 95 31 40 FE DA B0 5C CE 17 1C 7D 2F F9 69 A2 35 " + + " 24 5E 31 A6 67 9C 32 86 34 91 25 0F 18 28 6B CA " + + " BF 53 16 CC 89 0B DF 75 56 53 D9 46 7E 5F 7C 27 " + + " A8 73 E1 C6 7F B9 48 F3 7F B3 DB 6C 7B A3 F9 94 " + + " 2E F7 3F D4 7C 03 00 00 FF FF 03 00 50 4B 03 04 " + + " 14 00 06 00 08 00 00 00 21 00 31 DD 5F 61 D2 00 " + + " 00 00 8F 01 00 00 0B 00 00 00 5F 72 65 6C 73 2F " + + " 2E 72 65 6C 73 A4 90 C1 6A C3 30 0C 86 EF 83 BD " + + " 83 D1 BD 71 DA 43 19 A3 4E 6F 85 5E 4B 07 BB 0A " + + " 5B 49 4C 63 CB 58 26 6D DF BE A6 30 58 46 6F 3B " + + " EA 17 FA 3E F1 EF F6 B7 30 A9 99 B2 78 8E 06 D6 " + + " 4D 0B 8A A2 65 E7 E3 60 E0 EB 7C 58 7D 80 92 82 " + + " D1 E1 C4 91 0C DC 49 60 DF BD BF ED 4E 34 61 A9 " + + " 47 32 FA 24 AA 52 A2 18 18 4B 49 9F 5A 8B 1D 29 " + + " A0 34 9C 28 D6 4D CF 39 60 A9 63 1E 74 42 7B C1 " + + " 81 F4 A6 6D B7 3A FF 66 40 B7 60 AA A3 33 90 8F " + + " 6E 03 EA 7C 4F D5 FC 87 1D BC CD 2C DC 97 C6 72 " + + " D0 DC F7 DE BE A2 6A C7 D7 78 A2 B9 52 30 0F 54 " + + " 0C B8 2C CF 30 D3 DC D4 E7 40 BF F6 AE FF E9 95 " + + " 11 13 7D 57 FE 42 FC 4C AB F5 C7 AC 17 35 76 0F " + + " 00 00 00 FF FF 03 00 50 4B 03 04 14 00 06 00 08 " + + " 00 00 00 21 00 49 0D 41 2E 77 02 00 00 1A 07 00 " + + " 00 10 00 00 00 64 72 73 2F 73 68 61 70 65 78 6D " + + " 6C 2E 78 6D 6C BC 55 4D 73 DB 20 10 BD 77 A6 FF " + + " 81 E1 EE E8 23 96 1B 6B 8C 32 A9 33 E9 A5 D3 78 " + + " EC F8 07 60 09 D9 9A 22 D0 00 B1 E5 FC FA EE 82 " + + " EC A4 39 F4 10 77 72 41 68 17 F6 3D 1E BB CB EC " + + " B6 6F 25 D9 0B 63 1B AD 18 4D AE 62 4A 84 2A 75 " + + " D5 A8 2D A3 EB A7 87 D1 0D 25 D6 71 55 71 A9 95 " + + " 60 F4 28 2C BD 2D BE 7E 99 F5 95 C9 B9 2A 77 DA " + + " 10 08 A1 6C 0E 06 46 77 CE 75 79 14 D9 72 27 5A " + + " 6E AF 74 27 14 78 6B 6D 5A EE E0 D7 6C A3 CA F0 " + + " 03 04 6F 65 94 C6 F1 24 B2 9D 11 BC B2 3B 21 DC " + + " 7D F0 D0 C2 C7 06 B4 B9 90 F2 CE 43 04 53 6D 74 " + + " 1B 66 A5 96 45 32 8B 90 03 4E FD 06 98 3C D6 75 " + + " 91 C6 D3 2C 8B CF 3E 34 79 B7 D1 87 62 1A CC 38 " + + " 3D D9 D0 3F CD D2 EC EC F1 1B 7C E4 57 38 D1 3B " + + " 52 F6 8C C2 BA 34 06 41 CA 23 A3 E9 64 9C 4D 62 " + + " 1A 85 48 B6 23 2D 2F 8D 66 94 12 07 CB 65 A3 7E " + + " C3 3C 38 D5 7E D5 2D 4C 98 97 BF F6 0B 43 9A 8A " + + " D1 31 25 8A B7 20 E9 13 AC FF AE 7B 72 7D 0A 06 " + + " 6B 70 03 71 3D 98 E1 52 D0 EE 19 BD 0D 64 7D 48 " + + " 9E F7 B5 69 87 1B E0 1F D0 BF E5 8D 02 9A 3C D7 " + + " 75 4D 00 EC 26 99 26 19 24 01 9C 30 F9 96 8E E3 " + + " 34 43 74 9E FF 5B 82 28 F0 C0 85 9D B1 EE 87 D0 " + + " 17 73 22 18 88 51 23 4A E7 F9 F1 FD 4F EB 50 88 " + + " 57 08 84 53 FA A1 91 F2 52 01 4E 02 07 51 31 AD " + + " AC 3B 4A 81 00 52 2D 05 28 E3 53 FC C3 02 C3 85 " + + " 83 B6 B1 3F 88 2D CD 76 33 97 86 40 B9 80 D0 50 " + + " 66 30 6E 70 0C A7 F3 80 88 5C C3 C1 3E 19 7B 80 " + + " 44 74 51 D7 20 FD 27 E3 9F 41 FD F9 B5 FA 7F F8 " + + " 6D A3 B4 19 F4 87 DE 24 F0 02 F6 5C 32 EA FA 50 " + + " 5F A0 77 C0 1B 6A 6D 48 00 CC 05 AC C3 EA 88 94 " + + " 36 F0 85 C2 BC 34 1B A0 DF BA 47 18 6A A9 0F 8C " + + " 96 B2 E9 28 81 46 FA F2 DE 76 30 BC 63 54 41 2B " + + " A4 C4 38 39 D7 C0 17 92 25 B4 5D A0 1E CE D3 DD " + + " 3D 3B A8 82 A1 38 02 45 24 2B AD 5B 61 16 5F 4A " + + " D7 D7 7F 77 69 14 64 64 FC 00 02 4A 8E CF 8B 50 " + + " A3 F5 0A 9E 97 17 E8 35 49 EC F3 9F E7 AE C0 FE " + + " 39 BA C6 42 77 BE DC FD 2E A1 AA 05 37 7C 79 DE " + + " 6C 9E 47 CB F5 DF 9B B1 35 0C B7 77 BA 32 DF 36 " + + " 2D 58 FD 53 21 1B A1 DC 3D 77 FC 54 EF EF 1E 19 " + + " BF 3A A8 5B FC 01 00 00 FF FF 03 00 50 4B 03 04 " + + " 14 00 06 00 08 00 00 00 21 00 79 45 76 AD 2B 01 " + + " 00 00 AA 01 00 00 0F 00 00 00 64 72 73 2F 64 6F " + + " 77 6E 72 65 76 2E 78 6D 6C 5C 90 51 4F C2 30 14 " + + " 85 DF 4D FC 0F CD 35 F1 4D 3A 26 D5 81 14 B2 98 " + + " 10 31 99 E2 90 C4 D7 B2 B6 6C 71 6D 67 5B 61 F8 " + + " EB 2D 23 84 E8 E3 39 F7 7E E7 F6 74 3C 6D 55 8D " + + " B6 C2 BA CA 68 0A FD 5E 04 48 E8 C2 F0 4A 6F 28 " + + " AC DE 67 37 09 20 E7 99 E6 AC 36 5A 50 D8 0B 07 " + + " D3 C9 E5 C5 98 8D B8 D9 E9 5C 6C 97 7E 83 42 88 " + + " 76 23 46 A1 F4 BE 19 61 EC 8A 52 28 E6 7A A6 11 " + + " 3A CC A4 B1 8A F9 20 ED 06 73 CB 76 21 5C D5 38 " + + " 8E A2 3B AC 58 A5 C3 85 92 35 E2 B1 14 C5 E7 F2 " + + " 5B 51 C8 B8 7C F9 18 54 B3 67 26 BF D2 F9 20 4F " + + " C8 76 C1 09 A5 D7 57 6D FA 00 C8 8B D6 9F 97 1B " + + " 2B 17 71 96 AD DF 56 E9 69 A1 8B 9B 73 0A 03 40 " + + " F2 69 BF B6 15 CF 99 F3 C2 52 08 FD 42 DB D0 14 " + + " 26 A1 42 5B A7 BA 28 8D 45 32 17 AE FA 09 FD 8E " + + " BE B4 46 21 6B 76 14 86 80 0A 53 1F FC 83 7E 95 " + + " D2 09 1F 5C 12 93 6E 70 32 E2 68 48 48 04 F8 10 " + + " EA CD 11 ED 87 5B 1D 1B FF 61 13 72 FF 0F EE C7 " + + " B7 49 B0 02 8C CF 4F EA C4 F9 8B 27 BF 00 00 00 " + + " FF FF 03 00 50 4B 01 02 2D 00 14 00 06 00 08 00 " + + " 00 00 21 00 F0 F7 8A BB FD 00 00 00 E2 01 00 00 " + + " 13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 00 5B 43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 " + + " 5D 2E 78 6D 6C 50 4B 01 02 2D 00 14 00 06 00 08 " + + " 00 00 00 21 00 31 DD 5F 61 D2 00 00 00 8F 01 00 " + + " 00 0B 00 00 00 00 00 00 00 00 00 00 00 00 00 2E " + + " 01 00 00 5F 72 65 6C 73 2F 2E 72 65 6C 73 50 4B " + + " 01 02 2D 00 14 00 06 00 08 00 00 00 21 00 49 0D " + + " 41 2E 77 02 00 00 1A 07 00 00 10 00 00 00 00 00 " + + " 00 00 00 00 00 00 00 00 29 02 00 00 64 72 73 2F " + + " 73 68 61 70 65 78 6D 6C 2E 78 6D 6C 50 4B 01 02 " + + " 2D 00 14 00 06 00 08 00 00 00 21 00 79 45 76 AD " + + " 2B 01 00 00 AA 01 00 00 0F 00 00 00 00 00 00 00 " + + " 00 00 00 00 00 00 CE 04 00 00 64 72 73 2F 64 6F " + + " 77 6E 72 65 76 2E 78 6D 6C 50 4B 05 06 00 00 00 " + + " 00 04 00 04 00 F5 00 00 00 26 06 00 00 00 00 00 " + + " 00 10 F0 12 00 00 00 02 00 01 00 60 01 09 00 0D " + + " 00 02 00 D0 00 0A 00 73 00 00 00 11 F0 00 00 00 " + + " 00 5D 00 1A 00 15 00 12 00 06 00 11 00 11 60 00 " + + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 EC " + + " 00 08 00 00 00 0D F0 00 00 00 00 B6 01 12 00 12 " + + " 02 00 00 00 00 00 00 00 00 06 00 10 00 00 00 00 " + + " 00 3C 00 07 00 00 74 65 78 74 2D 33 3C 00 10 00 " + + " 00 00 16 00 00 00 00 00 06 00 00 00 00 00 00 00 " + + " EC 00 CC 07 0F 00 04 F0 CC 07 00 00 A2 0C 0A F0 " + + " 08 00 00 00 12 04 00 00 00 0A 00 00 93 00 0B F0 " + + " 4A 00 00 00 7F 00 00 00 04 00 80 00 00 36 9A 02 " + + " 85 00 02 00 00 00 8B 00 02 00 00 00 BF 00 1A 00 " + + " 1F 00 BF 01 01 00 11 00 FF 01 00 00 08 00 80 C3 " + + " 14 00 00 00 BF 03 00 00 02 00 54 00 65 00 78 00 " + + " 74 00 42 00 6F 00 78 00 20 00 34 00 00 00 13 00 " + + " 22 F1 38 07 00 00 A9 C3 32 07 00 00 50 4B 03 04 " + + " 14 00 06 00 08 00 00 00 21 00 F0 F7 8A BB FD 00 " + + " 00 00 E2 01 00 00 13 00 00 00 5B 43 6F 6E 74 65 " + + " 6E 74 5F 54 79 70 65 73 5D 2E 78 6D 6C 94 91 CD " + + " 4A C4 30 10 C7 EF 82 EF 10 E6 2A 6D AA 07 11 69 " + + " BA 07 AB 47 15 5D 1F 60 48 A6 6D D8 36 09 99 58 " + + " 77 DF DE 74 3F 2E E2 0A 1E 67 E6 FF F1 23 A9 57 " + + " DB 69 14 33 45 B6 DE 29 B8 2E 2B 10 E4 B4 37 D6 " + + " F5 0A 3E D6 4F C5 1D 08 4E E8 0C 8E DE 91 82 1D " + + " 31 AC 9A CB 8B 7A BD 0B C4 22 BB 1D 2B 18 52 0A " + + " F7 52 B2 1E 68 42 2E 7D 20 97 2F 9D 8F 13 A6 3C " + + " C6 5E 06 D4 1B EC 49 DE 54 D5 AD D4 DE 25 72 A9 " + + " 48 4B 06 34 75 4B 1D 7E 8E 49 3C 6E F3 FA 40 12 " + + " 69 64 10 0F 07 E1 D2 A5 00 43 18 AD C6 94 49 E5 " + + " EC CC 8F 96 E2 D8 50 66 E7 5E C3 83 0D 7C 95 31 " + + " 40 FE DA B0 5C CE 17 1C 7D 2F F9 69 A2 35 24 5E " + + " 31 A6 67 9C 32 86 34 91 25 0F 18 28 6B CA BF 53 " + + " 16 CC 89 0B DF 75 56 53 D9 46 7E 5F 7C 27 A8 73 " + + " E1 C6 7F B9 48 F3 7F B3 DB 6C 7B A3 F9 94 2E F7 " + + " 3F D4 7C 03 00 00 FF FF 03 00 50 4B 03 04 14 00 " + + " 06 00 08 00 00 00 21 00 31 DD 5F 61 D2 00 00 00 " + + " 8F 01 00 00 0B 00 00 00 5F 72 65 6C 73 2F 2E 72 " + + " 65 6C 73 A4 90 C1 6A C3 30 0C 86 EF 83 BD 83 D1 " + + " BD 71 DA 43 19 A3 4E 6F 85 5E 4B 07 BB 0A 5B 49 " + + " 4C 63 CB 58 26 6D DF BE A6 30 58 46 6F 3B EA 17 " + + " FA 3E F1 EF F6 B7 30 A9 99 B2 78 8E 06 D6 4D 0B " + + " 8A A2 65 E7 E3 60 E0 EB 7C 58 7D 80 92 82 D1 E1 " + + " C4 91 0C DC 49 60 DF BD BF ED 4E 34 61 A9 47 32 " + + " FA 24 AA 52 A2 18 18 4B 49 9F 5A 8B 1D 29 A0 34 " + + " 9C 28 D6 4D CF 39 60 A9 63 1E 74 42 7B C1 81 F4 " + + " A6 6D B7 3A FF 66 40 B7 60 AA A3 33 90 8F 6E 03 " + + " EA 7C 4F D5 FC 87 1D BC CD 2C DC 97 C6 72 D0 DC " + + " F7 DE BE A2 6A C7 D7 78 A2 B9 52 30 0F 54 0C B8 " + + " 2C CF 30 D3 DC D4 E7 40 BF F6 AE FF E9 95 11 13 " + + " 7D 57 FE 42 FC 4C AB F5 C7 AC 17 35 76 0F 00 00 " + + " 00 FF FF 03 00 50 4B 03 04 14 00 06 00 08 00 00 " + + " 00 21 00 5B 36 01 DE 77 02 00 00 1D 07 00 00 10 " + + " 00 00 00 64 72 73 2F 73 68 61 70 65 78 6D 6C 2E " + + " 78 6D 6C BC 55 4D 53 DB 30 10 BD 77 A6 FF 41 A3 " + + " 7B B0 9D 26 99 90 C1 66 28 0C BD 74 0A 93 C0 0F " + + " 10 B6 9C 78 2A 4B 1E 49 24 0E BF BE 6F 25 27 50 " + + " 0E 3D 90 0E 17 59 5E 69 F7 ED BE FD D0 C5 65 DF " + + " 2A B6 95 D6 35 46 E7 3C 3B 4B 39 93 BA 34 55 A3 " + + " D7 39 7F 7C B8 1D CD 39 73 5E E8 4A 28 A3 65 CE " + + " F7 D2 F1 CB E2 EB 97 8B BE B2 0B A1 CB 8D B1 0C " + + " 26 B4 5B 40 90 F3 8D F7 DD 22 49 5C B9 91 AD 70 " + + " 67 A6 93 1A A7 B5 B1 AD F0 F8 B5 EB A4 B2 62 07 " + + " E3 AD 4A C6 69 3A 4B 5C 67 A5 A8 DC 46 4A 7F 13 " + + " 4F 78 11 6C 03 ED 5A 2A 75 15 20 A2 A8 B6 A6 8D " + + " BB D2 A8 22 BB 48 C8 07 DA 06 05 6C EE EA BA 18 " + + " A7 E7 D3 69 7A 3C 23 51 38 B6 66 57 64 83 0E ED " + + " 0F 42 BA 90 65 93 6F E9 A0 83 B3 A0 13 8C BF 22 " + + " CA DE B3 B2 CF F9 74 3C 1D A7 E0 A4 DC E7 7C 3C " + + " 9B 4C 67 29 4F A2 2D D7 B1 56 94 D6 E4 9C 33 8F " + + " EB AA D1 BF B1 8F 87 7A BB EA EE 6D DC 97 BF B6 " + + " F7 96 35 15 8C 71 A6 45 0B 56 1F 70 FF BB E9 D9 " + + " E4 60 0C 77 48 81 F9 1E 62 E4 85 E4 C1 A3 B7 86 " + + " 5C 30 29 16 7D 6D DB 21 09 E2 03 29 68 45 A3 E1 " + + " A6 58 98 BA 66 00 9B 67 E7 D9 14 75 40 11 82 CD " + + " 79 1A 42 14 8B 7F 53 90 44 3F C8 4E 67 9D FF 21 " + + " CD C9 3E 31 32 94 73 2B 4B 1F FC 13 DB 9F CE 13 " + + " 11 AF 10 04 A7 CD 6D A3 D4 A9 04 1C 08 8E A4 52 " + + " 65 39 BF 57 92 00 94 5E 4A 30 13 AA FC C3 04 23 " + + " E1 E0 36 0D 81 B8 D2 AE 9F AE 95 65 E8 18 10 8D " + + " 4E C3 FA 44 6B 8C 2E 00 12 72 8D C0 3E 19 7B 80 " + + " 24 74 59 D7 A0 FE 93 F1 8F A0 21 7E A3 FF 1F 7E " + + " DB 68 63 07 FE 31 9E 24 25 60 2B 54 CE 7D 1F FB " + + " 0B 7C 47 BC A1 D7 86 02 A0 5A A0 3E AC F6 E4 D2 " + + " 13 BE 68 CC 53 AB 01 23 D7 DF 61 A9 95 D9 E5 BC " + + " 54 4D C7 19 66 E9 CB 7B D9 CE 8A 2E E7 1A D3 90 " + + " 33 EB D5 B5 81 BF 28 96 38 79 E1 7A 8C A7 BB 7A " + + " F6 E8 82 A1 39 A2 8B E4 AC 72 7E 45 55 7C AA BB " + + " 28 4B F4 DC A9 56 C8 88 0D 0B 08 54 82 5E 18 A9 " + + " 47 8F 2B BC 30 2F 98 71 D9 61 D0 F8 82 E6 E7 68 " + + " 42 8D EE 43 BB 07 2D A9 AB 7B 61 C5 F2 A8 6C 9F " + + " 47 CB C7 BF 95 69 34 0C D9 3B A4 2C 8C 4D 07 69 " + + " 78 2D 54 23 B5 BF 11 5E 1C FA FD DD 3B 13 6E 47 " + + " 76 8B 3F 00 00 00 FF FF 03 00 50 4B 03 04 14 00 " + + " 06 00 08 00 00 00 21 00 DB CE 2D F9 2C 01 00 00 " + + " AE 01 00 00 0F 00 00 00 64 72 73 2F 64 6F 77 6E " + + " 72 65 76 2E 78 6D 6C 5C 50 5D 4F C2 30 14 7D 37 " + + " F1 3F 34 D7 C4 37 E9 18 D4 00 52 C8 62 42 C4 64 " + + " 7E 0C 49 7C 2D 6B CB 16 D7 76 B6 75 0C 7F BD 1D " + + " 42 30 3E DD 9C 73 EE 39 ED B9 D3 79 AB 2A D4 08 " + + " EB 4A A3 29 F4 7B 11 20 A1 73 C3 4B BD A5 B0 7E " + + " 5B DC 8C 00 39 CF 34 67 95 D1 82 C2 5E 38 98 CF " + + " 2E 2F A6 6C C2 CD 4E 67 A2 59 F9 2D 0A 21 DA 4D " + + " 18 85 C2 FB 7A 82 B1 CB 0B A1 98 EB 99 5A E8 A0 " + + " 49 63 15 F3 01 DA 2D E6 96 ED 42 B8 AA 70 1C 45 " + + " B7 58 B1 52 87 17 0A 56 8B FB 42 E4 1F AB 2F 45 " + + " 21 E5 F2 E9 7D 58 2E 1E 99 FC 4C 96 C3 6C 44 9A " + + " 17 4E 28 BD BE 6A 93 3B 40 5E B4 FE BC 5C 37 36 " + + " 8D D3 74 F3 BA 4E 4E 0B 87 B8 25 A7 40 00 C9 87 " + + " FD C6 96 3C 63 CE 0B 4B 21 F4 0B 6D 43 53 98 85 " + + " 0A 6D 95 E8 BC 30 16 C9 4C B8 F2 3B F4 FB E5 A5 " + + " 35 0A 59 B3 0B B8 0F 28 37 55 27 74 C4 B3 94 4E " + + " F8 8E 1E 0E A2 90 15 A4 13 15 47 63 42 22 C0 5D " + + " AE 37 47 77 7C 74 87 F9 D7 3D 8E C8 3F 77 3F 1E " + + " 8C 62 D2 B9 F1 F9 5B 07 70 3E F3 EC 07 00 00 FF " + + " FF 03 00 50 4B 01 02 2D 00 14 00 06 00 08 00 00 " + + " 00 21 00 F0 F7 8A BB FD 00 00 00 E2 01 00 00 13 " + + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 5B 43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 5D " + + " 2E 78 6D 6C 50 4B 01 02 2D 00 14 00 06 00 08 00 " + + " 00 00 21 00 31 DD 5F 61 D2 00 00 00 8F 01 00 00 " + + " 0B 00 00 00 00 00 00 00 00 00 00 00 00 00 2E 01 " + + " 00 00 5F 72 65 6C 73 2F 2E 72 65 6C 73 50 4B 01 " + + " 02 2D 00 14 00 06 00 08 00 00 00 21 00 5B 36 01 " + + " DE 77 02 00 00 1D 07 00 00 10 00 00 00 00 00 00 " + + " 00 00 00 00 00 00 00 29 02 00 00 64 72 73 2F 73 " + + " 68 61 70 65 78 6D 6C 2E 78 6D 6C 50 4B 01 02 2D " + + " 00 14 00 06 00 08 00 00 00 21 00 DB CE 2D F9 2C " + + " 01 00 00 AE 01 00 00 0F 00 00 00 00 00 00 00 00 " + + " 00 00 00 00 00 CE 04 00 00 64 72 73 2F 64 6F 77 " + + " 6E 72 65 76 2E 78 6D 6C 50 4B 05 06 00 00 00 00 " + + " 04 00 04 00 F5 00 00 00 27 06 00 00 00 00 00 00 " + + " 10 F0 12 00 00 00 02 00 01 00 60 01 0B 00 9A 00 " + + " 02 00 D0 00 0C 00 00 01 00 00 11 F0 00 00 00 00 " + + " 5D 00 1A 00 15 00 12 00 06 00 12 00 11 60 00 00 " + + " 00 00 00 00 00 00 00 00 00 00 00 00 00 00 EC 00 " + + " 08 00 00 00 0D F0 00 00 00 00 B6 01 12 00 12 02 " + + " 00 00 00 00 00 00 00 00 06 00 10 00 00 00 00 00 " + + " 3C 00 07 00 00 74 65 78 74 2D 34 3C 00 10 00 00 " + + " 00 16 00 00 00 00 00 06 00 00 00 00 00 00 00 3C " + + " 00 C8 07 0F 00 04 F0 C8 07 00 00 A2 0C 0A F0 08 " + + " 00 00 00 13 04 00 00 00 0A 00 00 93 00 0B F0 4A " + + " 00 00 00 7F 00 00 00 04 00 80 00 80 33 9A 02 85 " + + " 00 02 00 00 00 8B 00 02 00 00 00 BF 00 1A 00 1F " + + " 00 BF 01 01 00 11 00 FF 01 00 00 08 00 80 C3 14 " + + " 00 00 00 BF 03 00 00 02 00 54 00 65 00 78 00 74 " + + " 00 42 00 6F 00 78 00 20 00 35 00 00 00 13 00 22 " + + " F1 34 07 00 00 A9 C3 2E 07 00 00 50 4B 03 04 14 " + + " 00 06 00 08 00 00 00 21 00 F0 F7 8A BB FD 00 00 " + + " 00 E2 01 00 00 13 00 00 00 5B 43 6F 6E 74 65 6E " + + " 74 5F 54 79 70 65 73 5D 2E 78 6D 6C 94 91 CD 4A " + + " C4 30 10 C7 EF 82 EF 10 E6 2A 6D AA 07 11 69 BA " + + " 07 AB 47 15 5D 1F 60 48 A6 6D D8 36 09 99 58 77 " + + " DF DE 74 3F 2E E2 0A 1E 67 E6 FF F1 23 A9 57 DB " + + " 69 14 33 45 B6 DE 29 B8 2E 2B 10 E4 B4 37 D6 F5 " + + " 0A 3E D6 4F C5 1D 08 4E E8 0C 8E DE 91 82 1D 31 " + + " AC 9A CB 8B 7A BD 0B C4 22 BB 1D 2B 18 52 0A F7 " + + " 52 B2 1E 68 42 2E 7D 20 97 2F 9D 8F 13 A6 3C C6 " + + " 5E 06 D4 1B EC 49 DE 54 D5 AD D4 DE 25 72 A9 48 " + + " 4B 06 34 75 4B 1D 7E 8E 49 3C 6E F3 FA 40 12 69 " + + " 64 10 0F 07 E1 D2 A5 00 43 18 AD C6 94 49 E5 EC " + + " CC 8F 96 E2 D8 50 66 E7 5E C3 83 0D 7C 95 31 40 " + + " FE DA B0 5C CE 17 1C 7D 2F F9 69 A2 35 24 5E 31 " + + " A6 67 9C 32 86 34 91 25 0F 18 28 6B CA BF 53 16 " + + " CC 89 0B DF 75 56 53 D9 46 7E 5F 7C 27 A8 73 E1 " + + " C6 7F B9 48 F3 7F B3 DB 6C 7B A3 F9 94 2E F7 3F " + + " D4 7C 03 00 00 FF FF 03 00 50 4B 03 04 14 00 06 " + + " 00 08 00 00 00 21 00 31 DD 5F 61 D2 00 00 00 8F " + + " 01 00 00 0B 00 00 00 5F 72 65 6C 73 2F 2E 72 65 " + + " 6C 73 A4 90 C1 6A C3 30 0C 86 EF 83 BD 83 D1 BD " + + " 71 DA 43 19 A3 4E 6F 85 5E 4B 07 BB 0A 5B 49 4C " + + " 63 CB 58 26 6D DF BE A6 30 58 46 6F 3B EA 17 FA " + + " 3E F1 EF F6 B7 30 A9 99 B2 78 8E 06 D6 4D 0B 8A " + + " A2 65 E7 E3 60 E0 EB 7C 58 7D 80 92 82 D1 E1 C4 " + + " 91 0C DC 49 60 DF BD BF ED 4E 34 61 A9 47 32 FA " + + " 24 AA 52 A2 18 18 4B 49 9F 5A 8B 1D 29 A0 34 9C " + + " 28 D6 4D CF 39 60 A9 63 1E 74 42 7B C1 81 F4 A6 " + + " 6D B7 3A FF 66 40 B7 60 AA A3 33 90 8F 6E 03 EA " + + " 7C 4F D5 FC 87 1D BC CD 2C DC 97 C6 72 D0 DC F7 " + + " DE BE A2 6A C7 D7 78 A2 B9 52 30 0F 54 0C B8 2C " + + " CF 30 D3 DC D4 E7 40 BF F6 AE FF E9 95 11 13 7D " + + " 57 FE 42 FC 4C AB F5 C7 AC 17 35 76 0F 00 00 00 " + + " FF FF 03 00 50 4B 03 04 14 00 06 00 08 00 00 00 " + + " 21 00 C4 6F 3F 83 76 02 00 00 1C 07 00 00 10 00 " + + " 00 00 64 72 73 2F 73 68 61 70 65 78 6D 6C 2E 78 " + + " 6D 6C BC 55 CD 6E DB 30 0C BE 0F D8 3B 08 BA A7 " + + " B6 83 3A 4D 8D C8 45 97 A2 BB 0C 6B 90 34 0F A0 " + + " C8 72 62 4C 96 0C 49 4D 9C 3E FD 48 C9 49 B7 1E " + + " 76 68 86 5E 64 9A 12 F9 51 1F 7F 34 BB EB 5B 45 " + + " F6 D2 BA C6 68 46 B3 AB 94 12 A9 85 A9 1A BD 65 " + + " 74 FD FC 38 9A 52 E2 3C D7 15 57 46 4B 46 8F D2 " + + " D1 BB F2 EB 97 59 5F D9 82 6B B1 33 96 80 0B ED " + + " 0A 50 30 BA F3 BE 2B 92 C4 89 9D 6C B9 BB 32 9D " + + " D4 B0 5B 1B DB 72 0F BF 76 9B 54 96 1F C0 79 AB " + + " 92 71 9A 4E 12 D7 59 C9 2B B7 93 D2 3F C4 1D 5A " + + " 06 DF 80 36 97 4A DD 07 88 A8 AA AD 69 A3 24 8C " + + " 2A B3 59 82 31 A0 18 0C 40 78 AA EB 72 9C DE E6 " + + " 79 7A DE 43 55 D8 B6 E6 50 66 D7 51 8F F2 49 19 " + + " 6C A6 F9 4D 7E DE 0A 26 C1 F7 1B A0 EC 3D 11 3D " + + " A3 F9 38 1F A7 40 89 38 32 3A 9E 5C E7 93 94 26 " + + " D1 95 EB 48 CB 85 35 8C 52 E2 E1 B8 6A F4 2F 90 " + + " E3 A6 DE AF BA 85 8D B2 F8 B9 5F 58 D2 54 8C 4E " + + " 28 D1 BC 05 52 9F E1 FC 37 D3 93 FC E4 0C CE A0 " + + " 01 F1 3D A8 21 2D A8 0F 11 FD E9 C8 05 97 BC E8 " + + " 6B DB 0E 39 E0 1F C8 40 CB 1B 0D 61 F2 C2 D4 35 " + + " 01 B0 69 76 9B E5 50 06 E1 86 C0 E5 4D 88 8A 17 " + + " FF A6 20 89 71 A0 9F CE 3A FF 5D 9A 8B 63 22 E8 " + + " 88 51 2B 85 0F F1 F1 FD 0F E7 91 88 37 08 84 D3 " + + " E6 B1 51 EA 52 02 4E 04 47 52 B1 B0 9C 3F 2A 89 " + + " 00 4A 2F 25 30 13 8A FC C3 04 43 C2 81 DB 34 5C " + + " C4 09 BB DD CC 95 25 D0 30 40 34 34 1A AC 1B 5C " + + " E3 ED 02 20 22 D7 70 B1 4F C6 1E 20 11 5D D6 35 " + + " 50 FF C9 F8 67 D0 70 7F A3 FF 1F 7E DB 68 63 07 " + + " FE 61 3A 49 4C C0 9E 2B 46 7D 1F FB 0B F8 8E 78 " + + " 43 AF 0D 05 80 B5 80 7D 58 1D 31 A4 0D 7C A1 31 " + + " 2F AD 06 98 B8 FE 09 96 5A 99 03 A3 42 35 1D 25 " + + " 30 4A 5F DF EB 0E 96 77 8C 6A 18 86 94 58 AF E6 " + + " 06 E2 85 62 89 83 17 42 8F F7 E9 EE 5F 3C 74 C1 " + + " D0 1C 31 44 0C 56 39 BF C2 2A BE 34 5C 28 4B E8 " + + " B9 4B BD A0 13 1B 16 20 50 71 7C 60 A4 1E AD 57 " + + " F0 C0 BC C2 8C CB D2 50 FF BC F0 25 CE CF 11 0C " + + " 64 94 71 0D 56 52 57 0B 6E F9 F2 6C 6C 5F 46 CB " + + " F5 DF C6 38 1A 86 EC 9D 52 16 C6 A6 03 6D 78 2C " + + " 54 23 B5 7F E0 9E 9F FA FD DD 33 13 4E 47 76 CB " + + " DF 00 00 00 FF FF 03 00 50 4B 03 04 14 00 06 00 " + + " 08 00 00 00 21 00 AF 34 70 A0 29 01 00 00 AD 01 " + + " 00 00 0F 00 00 00 64 72 73 2F 64 6F 77 6E 72 65 " + + " 76 2E 78 6D 6C 5C 50 5D 4F C2 30 14 7D 37 F1 3F " + + " 34 D7 C4 37 D9 98 2B 4C A4 90 C5 84 88 C9 FC 18 " + + " 92 F0 5A D6 96 2D AE ED 6C 2B 0C 7F BD 1D A8 44 " + + " 1F CF 67 EF E9 78 DA CA 1A 6D B9 B1 95 56 04 FA " + + " BD 10 10 57 85 66 95 DA 10 58 BE CE AE 12 40 D6 " + + " 51 C5 68 AD 15 27 B0 E7 16 A6 93 F3 B3 31 1D 31 " + + " BD 53 39 DF 2E DC 06 F9 12 65 47 94 40 E9 5C 33 " + + " 0A 02 5B 94 5C 52 DB D3 0D 57 5E 13 DA 48 EA 3C " + + " 34 9B 80 19 BA F3 E5 B2 0E A2 30 1C 04 92 56 CA " + + " BF 50 D2 86 DF 95 BC 78 5B 7C 48 02 19 13 8F AB " + + " B8 9A 3D 50 F1 9E CE E3 3C C1 DB 67 86 09 B9 BC " + + " 68 D3 5B 40 8E B7 EE 64 6E EA 55 16 65 D9 FA 65 " + + " 99 FE 18 0E 75 73 46 60 00 48 DC EF D7 A6 62 39 " + + " B5 8E 1B 02 7E 9F 5F EB 97 C2 C4 4F 68 EB 54 15 " + + " A5 36 48 E4 DC 56 9F 7E DF 91 17 46 4B 64 F4 CE " + + " E3 18 50 A1 EB 4E E8 88 27 21 2C 77 04 A2 04 0F " + + " F1 41 F9 65 C2 1B 8C 43 08 BA 5A A7 BF C3 47 8B " + + " B7 FF 09 F7 C3 78 F8 2F DD 8F AE 93 08 77 E9 E0 " + + " 74 D5 01 9C 7E 79 F2 05 00 00 FF FF 03 00 50 4B " + + " 01 02 2D 00 14 00 06 00 08 00 00 00 21 00 F0 F7 " + + " 8A BB FD 00 00 00 E2 01 00 00 13 00 00 00 00 00 " + + " 00 00 00 00 00 00 00 00 00 00 00 00 5B 43 6F 6E " + + " 74 65 6E 74 5F 54 79 70 65 73 5D 2E 78 6D 6C 50 " + + " 4B 01 02 2D 00 14 00 06 00 08 00 00 00 21 00 31 " + + " DD 5F 61 D2 00 00 00 8F 01 00 00 0B 00 00 00 00 " + + " 00 00 00 00 00 00 00 00 00 2E 01 00 00 5F 72 65 " + + " 6C 73 2F 2E 72 65 6C 73 50 4B 01 02 2D 00 14 00 " + + " 06 00 08 00 00 00 21 00 C4 6F 3F 83 76 02 00 00 " + + " 1C 07 00 00 10 00 00 00 00 00 00 00 00 00 00 00 " + + " 00 00 29 02 00 00 64 72 73 2F 73 68 61 70 65 78 " + + " 6D 6C 2E 78 6D 6C 50 4B 01 02 2D 00 14 00 06 00 " + + " 08 00 00 00 21 00 AF 34 70 A0 29 01 00 00 AD 01 " + + " 00 00 0F 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + " CD 04 00 00 64 72 73 2F 64 6F 77 6E 72 65 76 2E " + + " 78 6D 6C 50 4B 05 06 00 00 00 00 04 00 04 00 F5 " + + " 00 00 00 23 06 00 00 00 00 00 00 10 F0 12 00 00 " + + " 00 02 00 01 00 60 01 0E 00 26 00 02 00 D0 00 0F " + + " 00 8D 00 00 00 11 F0 00 00 00 00 5D 00 1A 00 15 " + + " 00 12 00 06 00 13 00 11 60 00 00 00 00 00 00 00 " + + " 00 00 00 00 00 00 00 00 00 3C 00 08 00 00 00 0D " + + " F0 00 00 00 00 B6 01 12 00 12 02 00 00 00 00 00 " + + " 00 00 00 06 00 10 00 00 00 00 00 3C 00 07 00 00 " + + " 74 65 78 74 2D 35 3C 00 10 00 00 00 16 00 00 00 " + + " 00 00 06 00 00 00 00 00 00 00 " + + " "; + + byte[] dgBytes = HexRead.readFromString(data); + List dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes)); + assertEquals(20, dgRecords.size()); + + short[] expectedSids = { + DrawingRecord.sid, + ObjRecord.sid, + DrawingRecord.sid, + TextObjectRecord.sid, + DrawingRecord.sid, + ObjRecord.sid, + DrawingRecord.sid, + TextObjectRecord.sid, + DrawingRecord.sid, + ObjRecord.sid, + DrawingRecord.sid, + TextObjectRecord.sid, + DrawingRecord.sid, + ObjRecord.sid, + DrawingRecord.sid, + TextObjectRecord.sid, + ContinueRecord.sid, + ObjRecord.sid, + ContinueRecord.sid, + TextObjectRecord.sid + }; + for (int i = 0; i < expectedSids.length; i++) { + assertEquals("unexpected record.sid and index[" + i + "]", expectedSids[i], dgRecords.get(i).getSid()); + } + DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord()); + + // create a dummy sheet consisting of our test data + InternalSheet sheet = InternalSheet.createSheet(); + List records = sheet.getRecords(); + records.clear(); + records.addAll(dgRecords); + records.add(EOFRecord.instance); + + + sheet.aggregateDrawingRecords(drawingManager, false); + assertEquals("drawing was not fully aggregated", 2, records.size()); + assertTrue("expected EscherAggregate", records.get(0) instanceof EscherAggregate); + assertTrue("expected EOFRecord", records.get(1) instanceof EOFRecord); + EscherAggregate agg = (EscherAggregate) records.get(0); + + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } + + public void testUnhandledContinue2() { + String data = "EC 00 38 08 0F 00 02 F0 97 37 00 00 10 00 " + + "08 F0 08 00 00 00 08 00 00 00 07 04 00 00 0F 00 " + + "03 F0 7F 37 00 00 0F 00 04 F0 28 00 00 00 01 00 " + + "09 F0 10 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 02 00 0A F0 08 00 00 00 00 04 " + + "00 00 05 00 00 00 0F 00 04 F0 E0 07 00 00 12 00 " + + "0A F0 08 00 00 00 01 04 00 00 00 0A 00 00 83 00 " + + "0B F0 50 00 00 00 BF 00 18 00 1F 00 81 01 4F 81 " + + "BD 00 BF 01 10 00 10 00 C0 01 38 5D 8A 00 CB 01 " + + "38 63 00 00 FF 01 08 00 08 00 80 C3 20 00 00 00 " + + "BF 03 00 00 02 00 1F 04 40 04 4F 04 3C 04 3E 04 " + + "43 04 33 04 3E 04 3B 04 4C 04 3D 04 38 04 3A 04 " + + "20 00 31 00 00 00 23 00 22 F1 4E 07 00 00 FF 01 " + + "00 00 40 00 A9 C3 42 07 00 00 50 4B 03 04 14 00 " + + "06 00 08 00 00 00 21 00 F0 F7 8A BB FD 00 00 00 " + + "E2 01 00 00 13 00 00 00 5B 43 6F 6E 74 65 6E 74 " + + "5F 54 79 70 65 73 5D 2E 78 6D 6C 94 91 CD 4A C4 " + + "30 10 C7 EF 82 EF 10 E6 2A 6D AA 07 11 69 BA 07 " + + "AB 47 15 5D 1F 60 48 A6 6D D8 36 09 99 58 77 DF " + + "DE 74 3F 2E E2 0A 1E 67 E6 FF F1 23 A9 57 DB 69 " + + "14 33 45 B6 DE 29 B8 2E 2B 10 E4 B4 37 D6 F5 0A " + + "3E D6 4F C5 1D 08 4E E8 0C 8E DE 91 82 1D 31 AC " + + "9A CB 8B 7A BD 0B C4 22 BB 1D 2B 18 52 0A F7 52 " + + "B2 1E 68 42 2E 7D 20 97 2F 9D 8F 13 A6 3C C6 5E " + + "06 D4 1B EC 49 DE 54 D5 AD D4 DE 25 72 A9 48 4B " + + "06 34 75 4B 1D 7E 8E 49 3C 6E F3 FA 40 12 69 64 " + + "10 0F 07 E1 D2 A5 00 43 18 AD C6 94 49 E5 EC CC " + + "8F 96 E2 D8 50 66 E7 5E C3 83 0D 7C 95 31 40 FE " + + "DA B0 5C CE 17 1C 7D 2F F9 69 A2 35 24 5E 31 A6 " + + "67 9C 32 86 34 91 25 0F 18 28 6B CA BF 53 16 CC " + + "89 0B DF 75 56 53 D9 46 7E 5F 7C 27 A8 73 E1 C6 " + + "7F B9 48 F3 7F B3 DB 6C 7B A3 F9 94 2E F7 3F D4 " + + "7C 03 00 00 FF FF 03 00 50 4B 03 04 14 00 06 00 " + + "08 00 00 00 21 00 31 DD 5F 61 D2 00 00 00 8F 01 " + + "00 00 0B 00 00 00 5F 72 65 6C 73 2F 2E 72 65 6C " + + "73 A4 90 C1 6A C3 30 0C 86 EF 83 BD 83 D1 BD 71 " + + "DA 43 19 A3 4E 6F 85 5E 4B 07 BB 0A 5B 49 4C 63 " + + "CB 58 26 6D DF BE A6 30 58 46 6F 3B EA 17 FA 3E " + + "F1 EF F6 B7 30 A9 99 B2 78 8E 06 D6 4D 0B 8A A2 " + + "65 E7 E3 60 E0 EB 7C 58 7D 80 92 82 D1 E1 C4 91 " + + "0C DC 49 60 DF BD BF ED 4E 34 61 A9 47 32 FA 24 " + + "AA 52 A2 18 18 4B 49 9F 5A 8B 1D 29 A0 34 9C 28 " + + "D6 4D CF 39 60 A9 63 1E 74 42 7B C1 81 F4 A6 6D " + + "B7 3A FF 66 40 B7 60 AA A3 33 90 8F 6E 03 EA 7C " + + "4F D5 FC 87 1D BC CD 2C DC 97 C6 72 D0 DC F7 DE " + + "BE A2 6A C7 D7 78 A2 B9 52 30 0F 54 0C B8 2C CF " + + "30 D3 DC D4 E7 40 BF F6 AE FF E9 95 11 13 7D 57 " + + "FE 42 FC 4C AB F5 C7 AC 17 35 76 0F 00 00 00 FF " + + "FF 03 00 50 4B 03 04 14 00 06 00 08 00 00 00 21 " + + "00 B5 19 FD 5B 97 02 00 00 FC 06 00 00 10 00 00 " + + "00 64 72 73 2F 73 68 61 70 65 78 6D 6C 2E 78 6D " + + "6C AC 55 49 6E DB 30 14 DD 17 E8 1D 08 EE 13 0D " + + "B6 12 47 B0 14 B4 0E DA 4D D1 18 4E 73 00 56 A2 " + + "6C A1 14 29 90 AC 2D 67 55 A0 DB 02 3D 42 0F D1 " + + "4D D1 21 67 90 6F D4 4F 52 52 DA 74 58 C4 F6 42 " + + "A6 DE 17 FF 7B 7F 22 A7 E7 4D C5 D0 9A 4A 55 0A " + + "9E E0 E0 D8 C7 88 F2 4C E4 25 5F 26 F8 FA D5 B3 " + + "A3 09 46 4A 13 9E 13 26 38 4D F0 96 2A 7C 9E 3E " + + "7E 34 6D 72 19 13 9E AD 84 44 E0 82 AB 18 80 04 " + + "AF B4 AE 63 CF 53 D9 8A 56 44 1D 8B 9A 72 B0 16 " + + "42 56 44 C3 AB 5C 7A B9 24 1B 70 5E 31 2F F4 FD " + + "13 4F D5 92 92 5C AD 28 D5 17 CE 82 53 EB 5B 6F " + + "C4 8C 32 F6 C4 52 38 A8 90 A2 72 AB 4C B0 34 9C " + + "7A 46 83 59 DA 0D B0 B8 2C 8A 74 14 8C 47 61 34 " + + "D8 0C 64 CD 52 6C D2 91 83 CD B2 C7 8C 3D 88 C2 " + + "B1 EF 0F 36 BB C5 FA BE 23 D4 62 20 49 C7 83 F3 " + + "01 33 5B A2 49 E0 FF 8B 38 E8 C4 DE 67 3E 8B FA " + + "1D 60 B9 E3 ED D9 54 8D 2A 92 49 91 60 8C 34 6D " + + "34 2B F9 1B 58 3B 5A BE BE AA E7 B2 93 F0 72 3D " + + "97 A8 CC 13 1C 62 C4 49 05 85 6A 3F ED DE ED 3E " + + "B6 DF DB DB DD FB F6 73 7B DB 7E DB 7D 68 7F B4 " + + "5F DA AF 28 C0 DE B0 CD F8 80 37 1B EE AF 1E 95 " + + "F5 4D E2 A6 90 55 57 60 F2 80 F2 56 A4 E4 A0 97 " + + "C4 A2 28 50 03 0D 16 8D 46 10 33 46 DB 04 9F 86 " + + "A3 33 DF 37 5A 48 0C C1 A1 CC D8 C7 93 C8 80 28 " + + "83 0F 82 E8 34 38 81 8F 8D 3E A7 C4 7C 5A 4B A5 " + + "9F 53 B1 B7 2A 64 1C 25 58 D2 4C 5B 85 64 FD 42 " + + "69 47 D5 53 74 79 71 B9 30 CD A6 F4 96 51 23 82 " + + "F1 05 85 80 6C E3 3F 38 2F 50 30 08 39 B4 EC 76 " + + "62 E8 8C 49 B4 26 2C C1 24 CB 28 D7 81 33 AD 48 " + + "4E 1D 1C F9 F0 EB F2 31 EC B0 D9 B1 82 8C B2 A2 " + + "64 EC 60 DA 3A 01 66 9A FF D4 E6 72 D5 F1 D9 22 " + + "16 05 24 F3 60 E4 FE FF 12 E3 C8 69 CF 68 23 17 " + + "FC 70 E4 55 C9 85 FC 9B 00 06 55 E9 22 77 7C 7D " + + "93 B8 D6 30 5D A2 9B A7 22 DF 1A 49 AF E1 1F 06 " + + "73 DF 3E 81 F3 59 5F C2 A3 60 62 93 E0 8C 95 35 " + + "46 70 F0 DE DC C7 A4 66 33 01 DD 03 F3 E3 8E E6 " + + "04 6B 37 5F 4C E9 2B 23 70 5F 29 10 39 4C E0 BE " + + "5E AC 13 C8 0B 61 4B B8 74 98 93 48 79 3E 27 92 " + + "2C 00 67 C4 DC 3E F2 ED D1 E2 1A 6E 9F 1B 38 09 " + + "82 A1 ED EB 2E DF 7D 92 ED C9 A5 00 B5 97 01 2B " + + "61 6C 2E 88 26 A6 44 B6 16 BF 5F 23 16 73 B9 49 " + + "7F 02 00 00 FF FF 03 00 50 4B 03 04 14 00 06 00 " + + "08 00 00 00 21 00 CA 39 EE E5 1C 01 00 00 8E 01 " + + "00 00 0F 00 00 00 64 72 73 2F 64 6F 77 6E 72 65 " + + "76 2E 78 6D 6C 4C 90 CB 4E C3 30 10 45 F7 48 FC " + + "83 35 48 6C 10 75 9E 28 94 3A 55 41 42 65 53 44 " + + "DA B2 60 67 12 E7 21 62 3B B2 4D 93 FE 3D 93 96 " + + "AA DD F9 CE DC 33 33 D7 B3 F9 20 5B B2 13 C6 36 " + + "5A 31 F0 27 1E 10 A1 72 5D 34 AA 62 B0 DD BC DE " + + "27 40 AC E3 AA E0 AD 56 82 C1 5E 58 98 A7 D7 57 " + + "33 3E 2D 74 AF 32 B1 5B BB 8A E0 10 65 A7 9C 41 " + + "ED 5C 37 A5 D4 E6 B5 90 DC 4E 74 27 14 F6 4A 6D " + + "24 77 28 4D 45 0B C3 7B 1C 2E 5B 1A 78 DE 03 95 " + + "BC 51 B8 A1 E6 9D 78 A9 45 FE B3 FE 95 B8 E4 43 " + + "7E 6E F5 73 F2 B5 A2 77 DB 3E 5B 6E 92 26 0E 13 " + + "C6 6E 6F 86 C5 13 10 27 06 77 36 FF D3 6F 05 83 " + + "00 48 B9 DC 7F 9B A6 C8 B8 75 C2 30 C0 38 18 0E " + + "83 41 8A 17 0F ED 42 E5 B5 36 E3 BB 34 5A 12 A3 " + + "7B 06 21 90 5C B7 07 1A F5 7B 59 5A E1 90 88 83 " + + "C8 43 1C 5B A7 52 E8 47 61 10 03 1D 71 A7 8F B0 " + + "8F 3B 0F 74 04 63 E1 64 7D 8C D1 78 C9 C6 89 EF " + + "1D 59 7A 79 07 8A F3 37 A6 7F 00 00 00 FF FF 03 " + + "00 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 21 " + + "00 F0 F7 8A BB FD 00 00 00 E2 01 00 00 13 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5B " + + "43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 5D 2E 78 " + + "6D 6C 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 " + + "21 00 31 DD 5F 61 D2 00 00 00 8F 01 00 00 0B 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 2E 01 00 00 " + + "5F 72 65 6C 73 2F 2E 72 65 6C 73 50 4B 01 02 2D " + + "00 14 00 06 00 08 00 00 00 21 00 B5 19 FD 5B 97 " + + "02 00 00 FC 06 00 00 10 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 29 02 00 00 64 72 73 2F 73 68 61 " + + "70 65 78 6D 6C 2E 78 6D 6C 50 4B 01 02 2D 00 14 " + + "00 06 00 08 00 00 00 21 00 CA 39 EE E5 1C 01 00 " + + "00 8E 01 00 00 0F 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 EE 04 00 00 64 72 73 2F 64 6F 77 6E 72 " + + "65 76 2E 78 6D 6C 50 4B 05 06 00 00 00 00 04 00 " + + "04 00 F5 00 00 00 37 06 00 00 00 00 00 00 10 F0 " + + "12 00 00 00 00 00 02 00 10 02 03 00 CD 00 04 00 " + + "D0 03 0C 00 0D 00 00 00 11 F0 00 00 00 00 5D 00 " + + "1A 00 15 00 12 00 02 00 01 00 11 60 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 EC 00 E8 07 " + + "0F 00 04 F0 E0 07 00 00 12 00 0A F0 08 00 00 00 " + + "02 04 00 00 00 0A 00 00 83 00 0B F0 50 00 00 00 " + + "BF 00 18 00 1F 00 81 01 4F 81 BD 00 BF 01 10 00 " + + "10 00 C0 01 38 5D 8A 00 CB 01 38 63 00 00 FF 01 " + + "08 00 08 00 80 C3 20 00 00 00 BF 03 00 00 02 00 " + + "1F 04 40 04 4F 04 3C 04 3E 04 43 04 33 04 3E 04 " + + "3B 04 4C 04 3D 04 38 04 3A 04 20 00 32 00 00 00 " + + "23 00 22 F1 4E 07 00 00 FF 01 00 00 40 00 A9 C3 " + + "42 07 00 00 50 4B 03 04 14 00 06 00 08 00 00 00 " + + "21 00 F0 F7 8A BB FD 00 00 00 E2 01 00 00 13 00 " + + "00 00 5B 43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 " + + "5D 2E 78 6D 6C 94 91 CD 4A C4 30 10 C7 EF 82 EF " + + "10 E6 2A 6D AA 07 11 69 BA 07 AB 47 15 5D 1F 60 " + + "48 A6 6D D8 36 09 99 58 77 DF DE 74 3F 2E E2 0A " + + "1E 67 E6 FF F1 23 A9 57 DB 69 14 33 45 B6 DE 29 " + + "B8 2E 2B 10 E4 B4 37 D6 F5 0A 3E D6 4F C5 1D 08 " + + "4E E8 0C 8E DE 91 82 1D 31 AC 9A CB 8B 7A BD 0B " + + "C4 22 BB 1D 2B 18 52 0A F7 52 B2 1E 68 42 2E 7D " + + "20 97 2F 9D 8F 13 A6 3C C6 5E 06 D4 1B EC 49 DE " + + "54 D5 AD D4 DE 25 72 A9 48 4B 06 34 75 4B 1D 7E " + + "8E 49 3C 6E F3 FA 40 12 69 64 10 0F 07 E1 D2 A5 " + + "00 43 18 AD C6 94 49 E5 EC CC 8F 96 E2 D8 50 66 " + + "E7 5E C3 83 0D 7C 95 31 40 FE DA B0 5C CE 17 1C " + + "7D 2F F9 69 A2 35 24 5E 31 A6 67 9C 32 86 34 91 " + + "25 0F 18 28 6B CA BF 53 16 CC 89 0B DF 75 56 53 " + + "D9 46 7E 5F 7C 27 A8 73 E1 C6 7F B9 48 F3 7F B3 " + + "DB 6C 7B A3 F9 94 2E F7 3F D4 7C 03 00 00 FF FF " + + "03 00 50 4B 03 04 14 00 06 00 08 00 00 00 21 00 " + + "31 DD 5F 61 D2 00 00 00 8F 01 00 00 0B 00 00 00 " + + "5F 72 65 6C 73 2F 2E 72 65 6C 73 A4 90 C1 6A C3 " + + "30 0C 86 EF 83 BD 83 D1 BD 71 DA 43 19 A3 4E 6F " + + "85 5E 4B 07 BB 0A 5B 49 4C 63 CB 58 26 6D DF BE " + + "A6 30 58 46 6F 3B EA 17 FA 3E F1 EF F6 B7 30 A9 " + + "99 B2 78 8E 06 D6 4D 0B 8A A2 65 E7 E3 60 E0 EB " + + "7C 58 7D 80 92 82 D1 E1 C4 91 0C DC 49 60 DF BD " + + "BF ED 4E 34 61 A9 47 32 FA 24 AA 52 A2 18 18 4B " + + "49 9F 5A 8B 1D 29 A0 34 9C 28 D6 4D CF 39 60 A9 " + + "63 1E 74 42 7B C1 81 F4 A6 6D B7 3A FF 66 40 B7 " + + "60 AA A3 33 90 8F 6E 03 EA 7C 4F D5 FC 87 1D BC " + + "CD 2C DC 97 C6 72 D0 DC F7 DE BE A2 6A C7 D7 78 " + + "A2 B9 52 30 0F 54 0C B8 2C CF 30 D3 DC D4 E7 40 " + + "BF F6 AE FF E9 95 11 13 7D 57 FE 42 FC 4C AB F5 " + + "C7 AC 17 35 76 0F 00 00 00 FF FF 03 00 50 4B 03 " + + "04 14 00 06 00 08 00 00 00 21 00 54 76 AD 8A 97 " + + "02 00 00 FE 06 00 00 10 00 00 00 64 72 73 2F 73 " + + "68 61 70 65 78 6D 6C 2E 78 6D 6C AC 55 CD 8E D3 " + + "30 10 BE 23 F1 0E 96 EF BB F9 69 9B 2D 51 93 15 " + + "74 05 17 C4 56 5D F6 01 4C E2 B4 11 8E 1D D9 A6 " + + "3F 7B 42 E2 8A B4 8F C0 43 70 41 FC EC 33 A4 6F " + + "C4 D8 4E 52 60 81 C3 B6 3D A4 CE 4C 3C DF 37 DF " + + "CC D8 93 F3 4D C5 D0 8A 4A 55 0A 9E E0 E0 D4 C7 " + + "88 F2 4C E4 25 5F 24 F8 FA F5 F3 93 31 46 4A 13 " + + "9E 13 26 38 4D F0 96 2A 7C 9E 3E 7E 34 D9 E4 32 " + + "26 3C 5B 0A 89 20 04 57 31 18 12 BC D4 BA 8E 3D " + + "4F 65 4B 5A 11 75 2A 6A CA C1 5B 08 59 11 0D AF " + + "72 E1 E5 92 AC 21 78 C5 BC D0 F7 23 4F D5 92 92 " + + "5C 2D 29 D5 17 CE 83 53 1B 5B AF C5 94 32 F6 D4 " + + "42 38 53 21 45 E5 56 99 60 69 38 F1 0C 07 B3 B4 " + + "1B 60 71 59 14 E9 30 8A CE C2 51 EF 33 26 EB 96 " + + "62 9D 0E 9D D9 2C 3B 9B F1 07 C1 70 E0 FB BD CF " + + "6E B1 B1 F7 80 5A F4 20 E9 3E 78 6F B3 51 C2 C1 " + + "F8 5F C0 41 4B F6 1E 72 14 3C E9 F6 80 6F 8F DC " + + "E1 A9 1A 55 24 93 22 C1 18 69 BA D1 AC E4 6F 61 " + + "ED 80 F9 EA AA 9E C9 96 C4 AB D5 4C A2 32 4F F0 " + + "00 23 4E 2A 28 55 F3 69 F7 7E 77 DB 7C 6F EE 76 " + + "1F 9A CF CD 5D F3 6D F7 B1 F9 D1 7C 69 BE A2 10 " + + "7B FD 36 13 03 DE 6C C2 BF 46 54 36 36 89 37 85 " + + "AC DA 12 93 07 14 B8 22 25 07 BE 24 16 45 81 36 " + + "D0 62 D1 78 04 39 63 B4 4D F0 F8 2C 02 E1 0D 17 " + + "12 43 72 28 33 FE 21 F8 C1 88 32 F8 20 18 9D 05 " + + "11 7C 6C F8 39 26 E6 D3 5A 2A FD 82 8A 83 59 21 " + + "13 28 C1 92 66 DA 32 24 AB 97 4A 3B A8 0E A2 D5 " + + "C5 69 61 DA 4D E9 2D A3 86 04 E3 73 0A 09 D9 D6 " + + "7F B0 2E 50 30 48 39 B4 E8 76 66 E8 94 49 B4 22 " + + "2C C1 24 CB 28 D7 81 73 2D 49 4E 9D 79 E4 C3 AF " + + "D5 A3 DF 61 D5 B1 84 0C B3 A2 64 EC 68 DC 5A 02 " + + "66 9E EF 73 73 5A B5 78 B6 88 45 01 62 1E 0D DC " + + "FF 9F 30 0E 9C 76 88 36 73 C1 8F 07 5E 95 5C C8 " + + "BF 11 60 50 95 36 73 87 D7 35 89 6B 0D D3 25 7A " + + "F3 4C E4 5B 43 E9 0D FC C3 60 1E DA 27 70 42 EB " + + "4B 78 14 4C AC 13 9C B1 B2 C6 08 8E DE 9B 3F 6D " + + "52 B3 A9 80 EE 81 F9 71 87 73 82 B5 9B 2F A6 F4 " + + "95 21 78 28 15 C8 1C 26 F0 D0 28 36 08 E8 42 D8 " + + "02 AE 1D E6 28 52 9E CF 88 24 73 B0 33 62 EE 1F " + + "F9 EE 64 7E 0D F7 CF 0D 9C 04 41 DF F6 75 AB 77 " + + "27 B2 3D B9 14 58 ED 75 C0 4A 18 9B 0B A2 89 29 " + + "91 AD C5 EF 17 89 B5 39 6D D2 9F 00 00 00 FF FF " + + "03 00 50 4B 03 04 14 00 06 00 08 00 00 00 21 00 " + + "88 BE 95 E9 1C 01 00 00 90 01 00 00 0F 00 00 00 " + + "64 72 73 2F 64 6F 77 6E 72 65 76 2E 78 6D 6C 54 " + + "90 4D 4F C3 30 0C 86 EF 48 FC 87 C8 48 5C 10 4B " + + "3F D6 52 CA B2 69 20 A1 71 01 B1 AD 1C B8 85 26 " + + "5D 2B 9A A4 4A C2 DA FD 7B 3C 06 DA B8 F9 B5 FD " + + "D8 7E 3D 99 0D AA 25 5B 69 5D 63 34 83 70 14 00 " + + "91 BA 34 A2 D1 1B 06 C5 FA F1 3A 03 E2 3C D7 82 " + + "B7 46 4B 06 3B E9 60 36 3D 3F 9B F0 5C 98 5E 2F " + + "E5 76 E5 37 04 87 68 97 73 06 B5 F7 5D 4E A9 2B " + + "6B A9 B8 1B 99 4E 6A AC 55 C6 2A EE 51 DA 0D 15 " + + "96 F7 38 5C B5 34 0A 82 94 2A DE 68 DC 50 F3 4E " + + "3E D4 B2 FC 5C 7D 29 5C F2 AA DE 0A 73 9F BD 3F " + + "D3 AB A2 5F 2E D6 59 93 C4 19 63 97 17 C3 FC 0E " + + "88 97 83 3F 36 FF D2 4F 82 41 0C A4 5A EC 3E 6C " + + "23 96 DC 79 69 19 A0 1D 34 87 C6 60 8A 17 0F ED " + + "5C 97 B5 B1 FB B8 B2 46 11 6B 7A 06 63 20 A5 69 " + + "19 44 B0 D7 2F 55 E5 A4 47 22 1C C7 01 E2 58 FA " + + "4B 8D D3 F4 26 4A 80 EE 71 6F 0E 70 88 D4 0F 9D " + + "FC A7 D3 F0 16 5B 4F E9 30 8A B3 03 4D 4F 2F 41 " + + "71 7C E4 F4 1B 00 00 FF FF 03 00 50 4B 01 02 2D " + + "00 14 00 06 00 08 00 00 00 21 00 F0 F7 8A BB FD " + + "00 00 00 E2 01 00 00 13 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 5B 43 6F 6E 74 65 6E " + + "74 5F 54 79 70 65 73 5D 2E 78 6D 6C 50 4B 01 02 " + + "2D 00 14 00 06 00 08 00 00 00 21 00 31 DD 5F 61 " + + "D2 00 00 00 8F 01 00 00 0B 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 2E 01 00 00 5F 72 65 6C 73 2F " + + "2E 72 65 6C 73 50 4B 01 02 2D 00 14 00 06 00 08 " + + "00 00 00 21 00 54 76 AD 8A 97 02 00 00 FE 06 00 " + + "00 10 00 00 00 00 00 00 00 00 00 00 00 00 00 29 " + + "02 00 00 64 72 73 2F 73 68 61 70 65 78 6D 6C 2E " + + "78 6D 6C 50 4B 01 02 2D 00 14 00 06 00 08 00 00 " + + "00 21 00 88 BE 95 E9 1C 01 00 00 90 01 00 00 0F " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 EE 04 00 " + + "00 64 72 73 2F 64 6F 77 6E 72 65 76 2E 78 6D 6C " + + "50 4B 05 06 00 00 00 00 04 00 04 00 F5 00 00 00 " + + "37 06 00 00 00 00 00 00 10 F0 12 00 00 00 00 00 " + + "02 00 10 03 04 00 9A 00 05 00 D0 00 0C 00 DA 00 " + + "00 00 11 F0 00 00 00 00 5D 00 1A 00 15 00 12 00 " + + "02 00 02 00 11 60 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 EC 00 E3 07 0F 00 04 F0 DB 07 " + + "00 00 12 00 0A F0 08 00 00 00 03 04 00 00 00 0A " + + "00 00 83 00 0B F0 50 00 00 00 BF 00 18 00 1F 00 " + + "81 01 4F 81 BD 00 BF 01 10 00 10 00 C0 01 38 5D " + + "8A 00 CB 01 38 63 00 00 FF 01 08 00 08 00 80 C3 " + + "20 00 00 00 BF 03 00 00 02 00 1F 04 40 04 4F 04 " + + "3C 04 3E 04 43 04 33 04 3E 04 3B 04 4C 04 3D 04 " + + "38 04 3A 04 20 00 33 00 00 00 23 00 22 F1 49 07 " + + "00 00 FF 01 00 00 40 00 A9 C3 3D 07 00 00 50 4B " + + "03 04 14 00 06 00 08 00 00 00 21 00 F0 F7 8A BB " + + "FD 00 00 00 E2 01 00 00 13 00 00 00 5B 43 6F 6E " + + "74 65 6E 74 5F 54 79 70 65 73 5D 2E 78 6D 6C 94 " + + "91 CD 4A C4 30 10 C7 EF 82 EF 10 E6 2A 6D AA 07 " + + "11 69 BA 07 AB 47 15 5D 1F 60 48 A6 6D D8 36 09 " + + "99 58 77 DF DE 74 3F 2E E2 0A 1E 67 E6 FF F1 23 " + + "A9 57 DB 69 14 33 45 B6 DE 29 B8 2E 2B 10 E4 B4 " + + "37 D6 F5 0A 3E D6 4F C5 1D 08 4E E8 0C 8E DE 91 " + + "82 1D 31 AC 9A CB 8B 7A BD 0B C4 22 BB 1D 2B 18 " + + "52 0A F7 52 B2 1E 68 42 2E 7D 20 97 2F 9D 8F 13 " + + "A6 3C C6 5E 06 D4 1B EC 49 DE 54 D5 AD D4 DE 25 " + + "72 A9 48 4B 06 34 75 4B 1D 7E 8E 49 3C 6E F3 FA " + + "40 12 69 64 10 0F 07 E1 D2 A5 00 43 18 AD C6 94 " + + "49 E5 EC CC 8F 96 E2 D8 50 66 E7 5E C3 83 0D 7C " + + "95 31 40 FE DA B0 5C CE 17 1C 7D 2F F9 69 A2 35 " + + "24 5E 31 A6 67 9C 32 86 34 91 25 0F 18 28 6B CA " + + "BF 53 16 CC 89 0B DF 75 56 53 D9 46 7E 5F 7C 27 " + + "A8 73 E1 C6 7F B9 48 F3 7F B3 DB 6C 7B A3 F9 94 " + + "2E F7 3F D4 7C 03 00 00 FF FF 03 00 50 4B 03 04 " + + "14 00 06 00 08 00 00 00 21 00 31 DD 5F 61 D2 00 " + + "00 00 8F 01 00 00 0B 00 00 00 5F 72 65 6C 73 2F " + + "2E 72 65 6C 73 A4 90 C1 6A C3 30 0C 86 EF 83 BD " + + "83 D1 BD 71 DA 43 19 A3 4E 6F 85 5E 4B 07 BB 0A " + + "5B 49 4C 63 CB 58 26 6D DF BE A6 30 58 46 6F 3B " + + "EA 17 FA 3E F1 EF F6 B7 30 A9 99 B2 78 8E 06 D6 " + + "4D 0B 8A A2 65 E7 E3 60 E0 EB 7C 58 7D 80 92 82 " + + "D1 E1 C4 91 0C DC 49 60 DF BD BF ED 4E 34 61 A9 " + + "47 32 FA 24 AA 52 A2 18 18 4B 49 9F 5A 8B 1D 29 " + + "A0 34 9C 28 D6 4D CF 39 60 A9 63 1E 74 42 7B C1 " + + "81 F4 A6 6D B7 3A FF 66 40 B7 60 AA A3 33 90 8F " + + "6E 03 EA 7C 4F D5 FC 87 1D BC CD 2C DC 97 C6 72 " + + "D0 DC F7 DE BE A2 6A C7 D7 78 A2 B9 52 30 0F 54 " + + "0C B8 2C CF 30 D3 DC D4 E7 40 BF F6 AE FF E9 95 " + + "11 13 7D 57 FE 42 FC 4C AB F5 C7 AC 17 35 76 0F " + + "00 00 00 FF FF 03 00 50 4B 03 04 14 00 06 00 08 " + + "00 00 00 21 00 B1 89 CE 0F 93 02 00 00 FC 06 00 " + + "00 10 00 00 00 64 72 73 2F 73 68 61 70 65 78 6D " + + "6C 2E 78 6D 6C AC 55 4B 6E DB 30 10 DD 17 E8 1D " + + "08 EE 13 7D 1C D9 8A 60 29 68 1D B4 9B A2 31 9C " + + "E6 00 AC 44 D9 42 29 52 20 59 7F B2 2A D0 6D 81 " + + "1E A1 87 E8 A6 E8 27 67 90 6F D4 21 29 B9 8D FB " + + "59 C4 F6 C2 1E CF 70 E6 BD F9 91 E3 8B 75 CD D0 " + + "92 4A 55 09 9E E2 E0 D4 C7 88 F2 5C 14 15 9F A7 " + + "F8 E6 D5 B3 93 18 23 A5 09 2F 08 13 9C A6 78 43 " + + "15 BE C8 1E 3F 1A AF 0B 99 10 9E 2F 84 44 10 82 " + + "AB 04 14 29 5E 68 DD 24 9E A7 F2 05 AD 89 3A 15 " + + "0D E5 60 2D 85 AC 89 86 BF 72 EE 15 92 AC 20 78 " + + "CD BC D0 F7 87 9E 6A 24 25 85 5A 50 AA 2F 9D 05 " + + "67 36 B6 5E 89 09 65 EC 89 85 70 AA 52 8A DA 49 " + + "B9 60 D9 60 EC 19 0E 46 B4 0E 20 5C 95 65 76 1E " + + "85 D1 CE 62 14 D6 28 C5 2A EB D4 46 EC 75 C6 3E " + + "1A 02 11 E7 01 26 EB 61 03 FF 42 D3 62 87 D0 07 " + + "D9 47 0D 21 CA BF 70 83 8E E9 3E 70 10 0E E2 DE " + + "E7 1E 72 8F A7 1A 54 93 5C 8A 14 63 A4 E9 5A B3 " + + "8A BF 01 D9 91 E1 CB EB 66 2A 3B 62 2F 97 53 89 " + + "AA 22 C5 67 18 71 52 43 9F DA 4F DB 77 DB 8F ED " + + "F7 F6 6E FB BE FD DC DE B5 DF B6 1F DA 1F ED 97 " + + "F6 2B 1A 60 6F E7 66 62 C0 3F 9B F0 EF 11 95 8D " + + "4D 92 75 29 EB AE BF E4 01 DD AD 49 C5 81 2F 49 " + + "44 59 A2 35 CC 57 3C 88 07 61 84 D1 06 64 3F 8C " + + "47 BE 6F C8 90 04 B2 43 B9 39 70 16 47 E7 A0 44 " + + "B9 39 11 8D 82 21 9C 36 04 1D 15 73 B4 91 4A 3F " + + "A7 E2 60 5A C8 04 4A B1 A4 B9 B6 14 C9 F2 85 D2 " + + "0E AA 87 E8 0A E3 8A 61 86 4D E9 0D A3 86 04 E3 " + + "33 0A 19 D9 C1 7F 70 61 A0 63 90 72 68 D1 ED C6 " + + "D0 09 93 68 49 58 8A 49 9E 53 AE 03 67 5A 90 82 " + + "3A 75 E4 C3 A7 AB C7 CE C3 56 C7 12 32 CC CA 8A " + + "B1 A3 71 EB 08 98 6D FE 93 9B AB 55 87 67 9B 58 " + + "96 50 CC A3 81 FB FF 2B 8C 03 A7 3D A2 CD 5C F0 " + + "E3 81 D7 15 17 F2 6F 04 18 74 A5 CB DC E1 F5 43 " + + "E2 46 C3 4C 89 5E 3F 15 C5 C6 50 7A 0D BF B0 99 " + + "87 CE 09 DC CF FA 0A BE 4A 26 56 29 CE 59 D5 60 " + + "04 17 EF ED BE 4E 6A 36 11 30 3D B0 3F EE 6A 4E " + + "B1 76 FB C5 94 BE 36 04 0F A5 02 99 C3 06 1E 1A " + + "C5 06 81 BA 10 36 87 47 87 39 8A 94 17 53 22 C9 " + + "0C F4 8C 98 D7 47 BE 3D 99 DD C0 EB 73 0B 37 41 " + + "B0 1B FB A6 AB 77 5F 64 7B 75 29 D0 DA C7 80 55 " + + "B0 36 97 44 13 D3 22 DB 8B FB CF 88 D5 B9 DA 64 " + + "3F 01 00 00 FF FF 03 00 50 4B 03 04 14 00 06 00 " + + "08 00 00 00 21 00 48 FA 47 DC 1B 01 00 00 8D 01 " + + "00 00 0F 00 00 00 64 72 73 2F 64 6F 77 6E 72 65 " + + "76 2E 78 6D 6C 4C 90 CB 4E C3 30 10 45 F7 48 FC " + + "83 35 48 6C 10 75 92 92 12 42 9D AA 20 A1 B2 01 " + + "91 36 2C D8 99 C4 79 88 D8 8E 6C D3 A4 7F CF A4 " + + "A5 6A 77 BE BE 73 EE 3C E6 8B 41 B6 64 2B 8C 6D " + + "B4 62 E0 4F 3C 20 42 E5 BA 68 54 C5 20 DB BC DC " + + "46 40 AC E3 AA E0 AD 56 82 C1 4E 58 58 24 97 17 " + + "73 1E 17 BA 57 A9 D8 AE 5D 45 30 44 D9 98 33 A8 " + + "9D EB 62 4A 6D 5E 0B C9 ED 44 77 42 A1 57 6A 23 " + + "B9 43 69 2A 5A 18 DE 63 B8 6C 69 E0 79 33 2A 79 " + + "A3 B0 43 CD 3B F1 5C 8B FC 67 FD 2B B1 C9 87 FC " + + "CC F4 53 F4 F5 46 6F B2 3E 5D 6D A2 26 9C 46 8C " + + "5D 5F 0D CB 47 20 4E 0C EE 54 FC 4F BF 16 0C EE " + + "80 94 AB DD B7 69 8A 94 5B 27 0C 03 5C 07 97 C3 " + + "C5 20 C1 89 87 76 A9 F2 5A 9B F1 5D 1A 2D 89 D1 " + + "3D 83 10 48 AE 5B 06 53 18 F5 7B 59 5A E1 18 DC " + + "CF 70 BE BD 73 FC 79 08 83 10 E8 C8 3A 7D 20 7D " + + "44 F6 28 46 9C A1 7E 30 8D B0 74 B4 8E 6C 80 71 " + + "07 9A 9E 8F 81 E2 74 C5 E4 0F 00 00 FF FF 03 00 " + + "50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 21 00 " + + "F0 F7 8A BB FD 00 00 00 E2 01 00 00 13 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 5B 43 " + + "6F 6E 74 65 6E 74 5F 54 79 70 65 73 5D 2E 78 6D " + + "6C 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 21 " + + "00 31 DD 5F 61 D2 00 00 00 8F 01 00 00 0B 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 2E 01 00 00 5F " + + "72 65 6C 73 2F 2E 72 65 6C 73 50 4B 01 02 2D 00 " + + "14 00 06 00 08 00 00 00 21 00 B1 89 CE 0F 93 02 " + + "00 00 FC 06 00 00 10 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 29 02 00 00 64 72 73 2F 73 68 61 70 " + + "65 78 6D 6C 2E 78 6D 6C 50 4B 01 02 2D 00 14 00 " + + "06 00 08 00 00 00 21 00 48 FA 47 DC 1B 01 00 00 " + + "8D 01 00 00 0F 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 EA 04 00 00 64 72 73 2F 64 6F 77 6E 72 65 " + + "76 2E 78 6D 6C 50 4B 05 06 00 00 00 00 04 00 04 " + + "00 F5 00 00 00 32 06 00 00 00 00 00 00 10 F0 12 " + + "00 00 00 00 00 03 00 10 00 05 00 66 00 05 00 D0 " + + "01 0D 00 A6 00 00 00 11 F0 00 00 00 00 5D 00 1A " + + "00 15 00 12 00 02 00 03 00 11 60 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 EC 00 E8 07 0F " + + "00 04 F0 E0 07 00 00 12 00 0A F0 08 00 00 00 04 " + + "04 00 00 00 0A 00 00 83 00 0B F0 50 00 00 00 BF " + + "00 18 00 1F 00 81 01 4F 81 BD 00 BF 01 10 00 10 " + + "00 C0 01 38 5D 8A 00 CB 01 38 63 00 00 FF 01 08 " + + "00 08 00 80 C3 20 00 00 00 BF 03 00 00 02 00 1F " + + "04 40 04 4F 04 3C 04 3E 04 43 04 33 04 3E 04 3B " + + "04 4C 04 3D 04 38 04 3A 04 20 00 34 00 00 00 23 " + + "00 22 F1 4E 07 00 00 FF 01 00 00 40 00 A9 C3 42 " + + "07 00 00 50 4B 03 04 14 00 06 00 08 00 00 00 21 " + + "00 F0 F7 8A BB FD 00 00 00 E2 01 00 00 13 00 00 " + + "00 5B 43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 5D " + + "2E 78 6D 6C 94 91 CD 4A C4 30 10 C7 EF 82 EF 10 " + + "E6 2A 6D AA 07 11 69 BA 07 AB 47 15 5D 1F 60 48 " + + "A6 6D D8 36 09 99 58 77 DF DE 74 3F 2E E2 0A 1E " + + "67 E6 FF F1 23 A9 57 DB 69 14 33 45 B6 DE 29 B8 " + + "2E 2B 10 E4 B4 37 D6 F5 0A 3E D6 4F C5 1D 08 4E " + + "E8 0C 8E DE 91 82 1D 31 AC 9A CB 8B 7A BD 0B C4 " + + "22 BB 1D 2B 18 52 0A F7 52 B2 1E 68 42 2E 7D 20 " + + "97 2F 9D 8F 13 A6 3C C6 5E 06 D4 1B EC 49 DE 54 " + + "D5 AD D4 DE 25 72 A9 48 4B 06 34 75 4B 1D 7E 8E " + + "49 3C 6E F3 FA 40 12 69 64 10 0F 07 E1 D2 A5 00 " + + "43 18 AD C6 94 49 E5 EC CC 8F 96 E2 D8 50 66 E7 " + + "5E C3 83 0D 7C 95 31 40 FE DA B0 5C CE 17 1C 7D " + + "2F F9 69 A2 35 24 5E 31 A6 67 9C 32 86 34 91 25 " + + "0F 18 28 6B CA BF 53 16 CC 89 0B DF 75 56 53 D9 " + + "46 7E 5F 7C 27 A8 73 E1 C6 7F B9 48 F3 7F B3 DB " + + "6C 7B A3 F9 94 2E F7 3F D4 7C 03 00 00 FF FF 03 " + + "00 50 4B 03 04 14 00 06 00 08 00 00 00 21 00 31 " + + "DD 5F 61 D2 00 00 00 8F 01 00 00 0B 00 00 00 5F " + + "72 65 6C 73 2F 2E 72 65 6C 73 A4 90 C1 6A C3 30 " + + "0C 86 EF 83 BD 83 D1 BD 71 DA 43 19 A3 4E 6F 85 " + + "5E 4B 07 BB 0A 5B 49 4C 63 CB 58 26 6D DF BE A6 " + + "30 58 46 6F 3B EA 17 FA 3E F1 EF F6 B7 30 A9 99 " + + "B2 78 8E 06 D6 4D 0B 8A A2 65 E7 E3 60 E0 EB 7C " + + "58 7D 80 92 82 D1 E1 C4 91 0C DC 49 60 DF BD BF " + + "ED 4E 34 61 A9 47 32 FA 24 AA 52 A2 18 18 4B 49 " + + "9F 5A 8B 1D 29 A0 34 9C 28 D6 4D CF 39 60 A9 63 " + + "1E 74 42 7B C1 81 F4 A6 6D B7 3A FF 66 40 B7 60 " + + "AA A3 33 90 8F 6E 03 EA 7C 4F D5 FC 87 1D BC CD " + + "2C DC 97 C6 72 D0 DC F7 DE BE A2 6A C7 D7 78 A2 " + + "B9 52 30 0F 54 0C B8 2C CF 30 D3 DC D4 E7 40 BF " + + "F6 AE FF E9 95 11 13 7D 57 FE 42 FC 4C AB F5 C7 " + + "AC 17 35 76 0F 00 00 00 FF FF 03 00 50 4B 03 04 " + + "14 00 06 00 08 00 00 00 21 00 CC C2 D3 C8 98 02 " + + "00 00 FD 06 00 00 10 00 00 00 64 72 73 2F 73 68 " + + "61 70 65 78 6D 6C 2E 78 6D 6C AC 55 4B 6E DB 30 " + + "10 DD 17 E8 1D 08 EE 13 49 8E E5 C8 82 A5 A0 75 " + + "D0 6E 8A C6 70 9A 03 B0 12 65 0B A5 48 81 64 FD " + + "C9 AA 40 B7 05 7A 84 1E A2 9B A2 9F 9C 41 BE 51 " + + "87 A4 24 37 FD 2D 62 7B 61 D3 33 E4 BC 37 6F 66 " + + "C8 C9 C5 A6 62 68 45 A5 2A 05 4F 70 70 EA 63 44 " + + "79 26 F2 92 2F 12 7C F3 EA D9 49 84 91 D2 84 E7 " + + "84 09 4E 13 BC A5 0A 5F A4 8F 1F 4D 36 B9 8C 09 " + + "CF 96 42 22 08 C1 55 0C 86 04 2F B5 AE 63 CF 53 " + + "D9 92 56 44 9D 8A 9A 72 F0 16 42 56 44 C3 5F B9 " + + "F0 72 49 D6 10 BC 62 DE C0 F7 47 9E AA 25 25 B9 " + + "5A 52 AA 2F 9D 07 A7 36 B6 5E 8B 29 65 EC 89 85 " + + "70 A6 42 8A CA AD 32 C1 D2 B3 89 67 38 98 A5 3D " + + "00 8B AB A2 48 83 51 30 1E 84 BD CF 98 AC 5B 8A " + + "75 3A 72 66 B3 EC 6C C6 7F 16 05 BE DF BB EC 09 " + + "1B 7A 8F A7 45 8F 91 EE 63 F7 36 73 64 38 88 46 " + + "FF C2 0D 86 7D F4 7B C0 51 78 DE 1D 01 4E 7B E0 " + + "0E 4E D5 A8 22 99 14 09 C6 48 D3 8D 66 25 7F 03 " + + "6B 17 83 AF AE EB 99 6C 39 BC 5C CD 24 2A F3 04 " + + "87 18 71 52 41 A1 9A 4F BB 77 BB 8F CD F7 E6 6E " + + "F7 BE F9 DC DC 35 DF 76 1F 9A 1F CD 97 E6 2B 1A " + + "62 AF 3F 66 62 C0 3F 9B EF AF 11 95 8D 4D E2 4D " + + "21 AB B6 C0 E4 01 E5 AD 48 C9 81 2F 89 45 51 A0 " + + "0D 34 D8 78 EC 43 CE 18 6D 61 1D 44 01 08 6F C8 " + + "90 18 B2 43 99 D9 30 8C C2 31 18 51 66 76 84 E7 " + + "01 88 6A 76 78 8E 8A D9 5A 4B A5 9F 53 71 30 2D " + + "64 02 25 58 D2 4C 5B 8A 64 F5 42 69 07 D5 41 B4 " + + "C2 38 31 4C B7 29 BD 65 D4 90 60 7C 4E 21 23 DB " + + "F9 0F 16 06 2A 06 29 0F 2C BA 1D 19 3A 65 12 AD " + + "08 4B 30 C9 32 CA 75 E0 5C 4B 92 53 67 0E 7D F8 " + + "B4 7A F4 27 AC 3A 96 90 61 56 94 8C 1D 8D 5B 4B " + + "C0 8C F3 9F DC 9C 56 2D 9E 2D 62 51 80 98 47 03 " + + "F7 FF 27 8C 03 A7 1D A2 CD 5C F0 E3 81 57 25 17 " + + "F2 6F 04 18 54 A5 CD DC E1 75 4D E2 5A C3 74 89 " + + "DE 3C 15 F9 D6 50 7A 0D BF 30 99 87 F6 09 5C D0 " + + "FA 0A BE 0A 26 D6 09 CE 58 59 63 04 37 EF ED EF " + + "36 A9 D9 54 40 F7 C0 FC B8 BB 39 C1 DA CD 17 53 " + + "FA DA 10 3C 94 0A 64 0E 13 78 68 14 1B 04 74 21 " + + "6C 01 AF 0E 73 14 29 CF 67 44 92 39 D8 19 31 CF " + + "8F 7C 7B 32 BF 81 E7 E7 D6 DC 15 7D DB D7 AD DE " + + "9D C8 F6 EA 52 60 B5 AF 01 2B 61 6C 2E 89 26 A6 " + + "44 B6 16 F7 DF 11 6B 73 DA A4 3F 01 00 00 FF FF " + + "03 00 50 4B 03 04 14 00 06 00 08 00 00 00 21 00 " + + "A9 7A 63 39 1B 01 00 00 8E 01 00 00 0F 00 00 00 " + + "64 72 73 2F 64 6F 77 6E 72 65 76 2E 78 6D 6C 4C " + + "90 CD 4E C3 30 10 84 EF 48 BC 83 B5 48 5C 10 75 " + + "D2 36 C1 84 BA 55 41 42 ED 05 44 DA 70 E0 66 12 " + + "E7 47 C4 76 65 9B 26 7D 7B 36 6A 51 7B F3 78 F7 " + + "DB D9 D9 D9 A2 57 2D D9 4B EB 1A A3 39 84 A3 00 " + + "88 D4 B9 29 1A 5D 71 C8 B6 AF F7 0C 88 F3 42 17 " + + "A2 35 5A 72 38 48 07 8B F9 F5 D5 4C 24 85 E9 74 " + + "2A F7 1B 5F 11 1C A2 5D 22 38 D4 DE EF 12 4A 5D " + + "5E 4B 25 DC C8 EC A4 C6 5A 69 AC 12 1E A5 AD 68 " + + "61 45 87 C3 55 4B C7 41 10 53 25 1A 8D 0E B5 D8 " + + "C9 97 5A E6 3F 9B 5F 85 26 1F EA 33 33 CF EC EB " + + "8D DE 65 5D BA DA B2 26 9A 30 CE 6F 6F FA E5 13 " + + "10 2F 7B 7F 6E 3E D1 EB 82 43 04 A4 5C 1D BE 6D " + + "53 A4 C2 79 69 39 60 1C 0C 87 C1 60 8E 1B F7 ED " + + "52 E7 B5 B1 C3 BB B4 46 11 6B 3A 0E 31 90 DC B4 " + + "1C 26 30 E8 F7 B2 74 D2 A3 62 61 80 34 56 FE 7F " + + "C2 38 7C 1C 47 40 07 DA 9B 23 1B 4E 4F 30 5A 5F " + + "C0 2C 7A C0 CE 4B 78 3A 66 F1 11 A6 97 7B A0 38 " + + "9F 71 FE 07 00 00 FF FF 03 00 50 4B 01 02 2D 00 " + + "14 00 06 00 08 00 00 00 21 00 F0 F7 8A BB FD 00 " + + "00 00 E2 01 00 00 13 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 5B 43 6F 6E 74 65 6E 74 " + + "5F 54 79 70 65 73 5D 2E 78 6D 6C 50 4B 01 02 2D " + + "00 14 00 06 00 08 00 00 00 21 00 31 DD 5F 61 D2 " + + "00 00 00 8F 01 00 00 0B 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 2E 01 00 00 5F 72 65 6C 73 2F 2E " + + "72 65 6C 73 50 4B 01 02 2D 00 14 00 06 00 08 00 " + + "00 00 21 00 CC C2 D3 C8 98 02 00 00 FD 06 00 00 " + + "10 00 00 00 00 00 00 00 00 00 00 00 00 00 29 02 " + + "00 00 64 72 73 2F 73 68 61 70 65 78 6D 6C 2E 78 " + + "6D 6C 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 " + + "21 00 A9 7A 63 39 1B 01 00 00 8E 01 00 00 0F 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 EF 04 00 00 " + + "64 72 73 2F 64 6F 77 6E 72 65 76 2E 78 6D 6C 50 " + + "4B 05 06 00 00 00 00 04 00 04 00 F5 00 00 00 37 " + + "06 00 00 00 00 00 00 10 F0 12 00 00 00 00 00 03 " + + "00 10 01 06 00 33 00 05 00 D0 02 0E 00 73 00 00 " + + "00 11 F0 00 00 00 00 5D 00 1A 00 15 00 12 00 02 " + + "00 04 00 11 60 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 3C 00 E5 07 0F 00 04 F0 DD 07 00 " + + "00 12 00 0A F0 08 00 00 00 05 04 00 00 00 0A 00 " + + "00 83 00 0B F0 50 00 00 00 BF 00 18 00 1F 00 81 " + + "01 4F 81 BD 00 BF 01 10 00 10 00 C0 01 38 5D 8A " + + "00 CB 01 38 63 00 00 FF 01 08 00 08 00 80 C3 20 " + + "00 00 00 BF 03 00 00 02 00 1F 04 40 04 4F 04 3C " + + "04 3E 04 43 04 33 04 3E 04 3B 04 4C 04 3D 04 38 " + + "04 3A 04 20 00 35 00 00 00 23 00 22 F1 4B 07 00 " + + "00 FF 01 00 00 40 00 A9 C3 3F 07 00 00 50 4B 03 " + + "04 14 00 06 00 08 00 00 00 21 00 F0 F7 8A BB FD " + + "00 00 00 E2 01 00 00 13 00 00 00 5B 43 6F 6E 74 " + + "65 6E 74 5F 54 79 70 65 73 5D 2E 78 6D 6C 94 91 " + + "CD 4A C4 30 10 C7 EF 82 EF 10 E6 2A 6D AA 07 11 " + + "69 BA 07 AB 47 15 5D 1F 60 48 A6 6D D8 36 09 99 " + + "58 77 DF DE 74 3F 2E E2 0A 1E 67 E6 FF F1 23 A9 " + + "57 DB 69 14 33 45 B6 DE 29 B8 2E 2B 10 E4 B4 37 " + + "D6 F5 0A 3E D6 4F C5 1D 08 4E E8 0C 8E DE 91 82 " + + "1D 31 AC 9A CB 8B 7A BD 0B C4 22 BB 1D 2B 18 52 " + + "0A F7 52 B2 1E 68 42 2E 7D 20 97 2F 9D 8F 13 A6 " + + "3C C6 5E 06 D4 1B EC 49 DE 54 D5 AD D4 DE 25 72 " + + "A9 48 4B 06 34 75 4B 1D 7E 8E 49 3C 6E F3 FA 40 " + + "12 69 64 10 0F 07 E1 D2 A5 00 43 18 AD C6 94 49 " + + "E5 EC CC 8F 96 E2 D8 50 66 E7 5E C3 83 0D 7C 95 " + + "31 40 FE DA B0 5C CE 17 1C 7D 2F F9 69 A2 35 24 " + + "5E 31 A6 67 9C 32 86 34 91 25 0F 18 28 6B CA BF " + + "53 16 CC 89 0B DF 75 56 53 D9 46 7E 5F 7C 27 A8 " + + "73 E1 C6 7F B9 48 F3 7F B3 DB 6C 7B A3 F9 94 2E " + + "F7 3F D4 7C 03 00 00 FF FF 03 00 50 4B 03 04 14 " + + "00 06 00 08 00 00 00 21 00 31 DD 5F 61 D2 00 00 " + + "00 8F 01 00 00 0B 00 00 00 5F 72 65 6C 73 2F 2E " + + "72 65 6C 73 A4 90 C1 6A C3 30 0C 86 EF 83 BD 83 " + + "D1 BD 71 DA 43 19 A3 4E 6F 85 5E 4B 07 BB 0A 5B " + + "49 4C 63 CB 58 26 6D DF BE A6 30 58 46 6F 3B EA " + + "17 FA 3E F1 EF F6 B7 30 A9 99 B2 78 8E 06 D6 4D " + + "0B 8A A2 65 E7 E3 60 E0 EB 7C 58 7D 80 92 82 D1 " + + "E1 C4 91 0C DC 49 60 DF BD BF ED 4E 34 61 A9 47 " + + "32 FA 24 AA 52 A2 18 18 4B 49 9F 5A 8B 1D 29 A0 " + + "34 9C 28 D6 4D CF 39 60 A9 63 1E 74 42 7B C1 81 " + + "F4 A6 6D B7 3A FF 66 40 B7 60 AA A3 33 90 8F 6E " + + "03 EA 7C 4F D5 FC 87 1D BC CD 2C DC 97 C6 72 D0 " + + "DC F7 DE BE A2 6A C7 D7 78 A2 B9 52 30 0F 54 0C " + + "B8 2C CF 30 D3 DC D4 E7 40 BF F6 AE FF E9 95 11 " + + "13 7D 57 FE 42 FC 4C AB F5 C7 AC 17 35 76 0F 00 " + + "00 00 FF FF 03 00 50 4B 03 04 14 00 06 00 08 00 " + + "00 00 21 00 D1 22 B3 C1 96 02 00 00 F9 06 00 00 " + + "10 00 00 00 64 72 73 2F 73 68 61 70 65 78 6D 6C " + + "2E 78 6D 6C AC 55 4B 6E DB 30 10 DD 17 E8 1D 08 " + + "EE 13 49 96 E5 B8 82 A5 A0 75 D0 6E 8A C6 70 9A " + + "03 B0 12 65 0B A5 48 81 64 FD C9 AA 40 B7 05 72 " + + "84 1E A2 9B A2 9F 9C 41 BE 51 87 A4 24 B7 E9 67 " + + "11 7B 63 53 33 9C 79 8F 6F 66 C8 C9 F9 A6 62 68 " + + "45 A5 2A 05 4F 70 70 EA 63 44 79 26 F2 92 2F 12 " + + "7C FD FA F9 C9 18 23 A5 09 CF 09 13 9C 26 78 4B " + + "15 3E 4F 1F 3F 9A 6C 72 19 13 9E 2D 85 44 90 82 " + + "AB 18 0C 09 5E 6A 5D C7 9E A7 B2 25 AD 88 3A 15 " + + "35 E5 E0 2D 84 AC 88 86 4F B9 F0 72 49 D6 90 BC " + + "62 DE C0 F7 47 9E AA 25 25 B9 5A 52 AA 2F 9C 07 " + + "A7 36 B7 5E 8B 29 65 EC A9 85 70 A6 42 8A CA AD " + + "32 C1 D2 70 E2 19 0E 66 69 03 60 71 59 14 69 18 " + + "0C C3 41 D4 FB 8C C9 BA A5 58 A7 67 CE 6C 96 9D " + + "CD F8 FD DE 6C 77 DB B4 7B 2C 2D FA FC E9 3E 6F " + + "6F 33 21 D1 38 F0 FF 85 19 B4 31 F7 41 87 67 A3 " + + "2E 04 5C 7B E0 0E 4E D5 A8 22 99 14 09 C6 48 D3 " + + "8D 66 25 7F 0B 6B 87 CB 57 57 F5 4C B6 1C 5E AD " + + "66 12 95 79 82 47 18 71 52 41 91 9A 4F BB F7 BB " + + "DB E6 7B 73 B7 FB D0 7C 6E EE 9A 6F BB 8F CD 8F " + + "E6 4B F3 15 45 D8 EB C3 4C 0E F8 B2 E7 FD 35 A3 " + + "B2 B9 49 BC 29 64 D5 16 97 3C A0 B4 15 29 39 F0 " + + "25 B1 28 0A B4 49 F0 00 2A 13 0C 22 8C B6 D0 68 " + + "61 18 46 BE 6F C8 90 18 4E 87 32 D8 10 0C C7 D1 " + + "13 30 A2 CC EC 88 CE 02 50 C8 EC F0 1C 15 B3 B5 " + + "96 4A BF A0 E2 60 5A C8 24 4A B0 A4 99 B6 14 C9 " + + "EA A5 D2 0E AA 83 68 85 71 62 98 4E 53 7A CB A8 " + + "21 C1 F8 9C C2 89 6C D7 3F 58 18 A8 98 D1 C4 A2 " + + "DB 71 A1 53 26 D1 8A B0 04 93 2C A3 5C 07 CE B5 " + + "24 39 75 66 D0 CB 29 06 7A F4 11 56 1D 4B C8 30 " + + "2B 4A C6 8E C6 AD 25 60 46 F9 4F 6E 4E AB 16 CF " + + "16 B1 28 40 CC A3 81 FB FF 13 C6 81 D3 0E D1 9E " + + "5C F0 E3 81 57 25 17 F2 6F 04 18 54 A5 3D B9 C3 " + + "EB 9A C4 B5 86 E9 12 BD 79 26 F2 AD A1 F4 06 FE " + + "61 32 0F ED 13 B8 9C F5 25 FC 14 4C AC 13 9C B1 " + + "B2 C6 08 6E DD 9B FB 36 A9 D9 54 40 F7 C0 FC B8 " + + "7B 39 C1 DA CD 17 53 FA CA 10 3C 94 0A 9C 1C 26 " + + "F0 D0 2C 36 09 E8 42 D8 02 5E 1C D6 5E 01 3C 9F " + + "11 49 E6 60 67 C4 3C 3D F2 DD C9 FC 1A 9E 9E 1B " + + "B8 09 82 BE ED EB 56 EF 4E 64 7B 75 29 B0 DA 97 " + + "80 95 30 36 17 44 13 53 22 5B 8B DF DF 10 6B 73 " + + "DA A4 3F 01 00 00 FF FF 03 00 50 4B 03 04 14 00 " + + "06 00 08 00 00 00 21 00 EA 0B 8D A0 1A 01 00 00 " + + "8A 01 00 00 0F 00 00 00 64 72 73 2F 64 6F 77 6E " + + "72 65 76 2E 78 6D 6C 4C 90 5F 4F C2 30 14 C5 DF " + + "4D FC 0E CD 35 F1 C5 48 37 C6 60 99 74 04 4D 0C " + + "BE 68 04 E6 83 6F 75 EB FE C4 B5 5D DA CA C6 B7 " + + "E7 4E 20 F0 D4 9E DB FB 3B F7 9E CE 17 BD 6C C8 " + + "4E 18 5B 6B C5 C0 1F 79 40 84 CA 74 5E AB 92 41 " + + "BA 7D 7D 8C 80 58 C7 55 CE 1B AD 04 83 BD B0 B0 " + + "48 6E 6F E6 3C CE 75 A7 D6 62 B7 71 25 41 13 65 " + + "63 CE A0 72 AE 8D 29 B5 59 25 24 B7 23 DD 0A 85 " + + "6F 85 36 92 3B 94 A6 A4 B9 E1 1D 9A CB 86 8E 3D " + + "6F 4A 25 AF 15 4E A8 78 2B 5E 2A 91 FD 6E FE 24 " + + "0E F9 94 5F A9 7E 8E BE DF E9 43 DA AD 57 DB A8 " + + "0E 83 88 B1 FB BB 7E F9 04 C4 89 DE 5D 9A 4F F4 " + + "5B CE 60 0A A4 58 ED 7F 4C 9D AF B9 75 C2 30 C0 " + + "38 18 0E 83 41 82 1B F7 CD 52 65 95 36 C3 BD 30 " + + "5A 12 A3 3B 06 33 20 99 6E 18 04 30 E8 8F A2 B0 " + + "C2 FD 93 58 3D AB C0 9F 04 E3 10 E8 40 3A 7D E4 " + + "FC F0 04 E2 79 05 4E 66 53 EC 1C 2C CF 70 18 F9 " + + "DE 11 A6 D7 3B A0 B8 7C 61 72 00 00 00 FF FF 03 " + + "00 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 21 " + + "00 F0 F7 8A BB FD 00 00 00 E2 01 00 00 13 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 5B " + + "43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 5D 2E 78 " + + "6D 6C 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 " + + "21 00 31 DD 5F 61 D2 00 00 00 8F 01 00 00 0B 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 2E 01 00 00 " + + "5F 72 65 6C 73 2F 2E 72 65 6C 73 50 4B 01 02 2D " + + "00 14 00 06 00 08 00 00 00 21 00 D1 22 B3 C1 96 " + + "02 00 00 F9 06 00 00 10 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 29 02 00 00 64 72 73 2F 73 68 61 " + + "70 65 78 6D 6C 2E 78 6D 6C 50 4B 01 02 2D 00 14 " + + "00 06 00 08 00 00 00 21 00 EA 0B 8D A0 1A 01 00 " + + "00 8A 01 00 00 0F 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 ED 04 00 00 64 72 73 2F 64 6F 77 6E 72 " + + "65 76 2E 78 6D 6C 50 4B 05 06 00 00 00 00 04 00 " + + "04 00 F5 00 00 00 34 06 00 00 00 00 00 00 10 F0 " + + "12 00 00 00 00 00 03 00 10 02 07 00 00 00 05 00 " + + "D0 03 0F 00 40 00 00 00 11 F0 00 00 00 00 5D 00 " + + "1A 00 15 00 12 00 02 00 05 00 11 60 00 00 00 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 3C 00 E7 07 " + + "0F 00 04 F0 DF 07 00 00 12 00 0A F0 08 00 00 00 " + + "06 04 00 00 00 0A 00 00 83 00 0B F0 50 00 00 00 " + + "BF 00 18 00 1F 00 81 01 4F 81 BD 00 BF 01 10 00 " + + "10 00 C0 01 38 5D 8A 00 CB 01 38 63 00 00 FF 01 " + + "08 00 08 00 80 C3 20 00 00 00 BF 03 00 00 02 00 " + + "1F 04 40 04 4F 04 3C 04 3E 04 43 04 33 04 3E 04 " + + "3B 04 4C 04 3D 04 38 04 3A 04 20 00 36 00 00 00 " + + "23 00 22 F1 4D 07 00 00 FF 01 00 00 40 00 A9 C3 " + + "41 07 00 00 50 4B 03 04 14 00 06 00 08 00 00 00 " + + "21 00 F0 F7 8A BB FD 00 00 00 E2 01 00 00 13 00 " + + "00 00 5B 43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 " + + "5D 2E 78 6D 6C 94 91 CD 4A C4 30 10 C7 EF 82 EF " + + "10 E6 2A 6D AA 07 11 69 BA 07 AB 47 15 5D 1F 60 " + + "48 A6 6D D8 36 09 99 58 77 DF DE 74 3F 2E E2 0A " + + "1E 67 E6 FF F1 23 A9 57 DB 69 14 33 45 B6 DE 29 " + + "B8 2E 2B 10 E4 B4 37 D6 F5 0A 3E D6 4F C5 1D 08 " + + "4E E8 0C 8E DE 91 82 1D 31 AC 9A CB 8B 7A BD 0B " + + "C4 22 BB 1D 2B 18 52 0A F7 52 B2 1E 68 42 2E 7D " + + "20 97 2F 9D 8F 13 A6 3C C6 5E 06 D4 1B EC 49 DE " + + "54 D5 AD D4 DE 25 72 A9 48 4B 06 34 75 4B 1D 7E " + + "8E 49 3C 6E F3 FA 40 12 69 64 10 0F 07 E1 D2 A5 " + + "00 43 18 AD C6 94 49 E5 EC CC 8F 96 E2 D8 50 66 " + + "E7 5E C3 83 0D 7C 95 31 40 FE DA B0 5C CE 17 1C " + + "7D 2F F9 69 A2 35 24 5E 31 A6 67 9C 32 86 34 91 " + + "25 0F 18 28 6B CA BF 53 16 CC 89 0B DF 75 56 53 " + + "D9 46 7E 5F 7C 27 A8 73 E1 C6 7F B9 48 F3 7F B3 " + + "DB 6C 7B A3 F9 94 2E F7 3F D4 7C 03 00 00 FF FF " + + "03 00 50 4B 03 04 14 00 06 00 08 00 00 00 21 00 " + + "31 DD 5F 61 D2 00 00 00 8F 01 00 00 0B 00 00 00 " + + "5F 72 65 6C 73 2F 2E 72 65 6C 73 A4 90 C1 6A C3 " + + "30 0C 86 EF 83 BD 83 D1 BD 71 DA 43 19 A3 4E 6F " + + "85 5E 4B 07 BB 0A 5B 49 4C 63 CB 58 26 6D DF BE " + + "A6 30 58 46 6F 3B EA 17 FA 3E F1 EF F6 B7 30 A9 " + + "99 B2 78 8E 06 D6 4D 0B 8A A2 65 E7 E3 60 E0 EB " + + "7C 58 7D 80 92 82 D1 E1 C4 91 0C DC 49 60 DF BD " + + "BF ED 4E 34 61 A9 47 32 FA 24 AA 52 A2 18 18 4B " + + "49 9F 5A 8B 1D 29 A0 34 9C 28 D6 4D CF 39 60 A9 " + + "63 1E 74 42 7B C1 81 F4 A6 6D B7 3A FF 66 40 B7 " + + "60 AA A3 33 90 8F 6E 03 EA 7C 4F D5 FC 87 1D BC " + + "CD 2C DC 97 C6 72 D0 DC F7 DE BE A2 6A C7 D7 78 " + + "A2 B9 52 30 0F 54 0C B8 2C CF 30 D3 DC D4 E7 40 " + + "BF F6 AE FF E9 95 11 13 7D 57 FE 42 FC 4C AB F5 " + + "C7 AC 17 35 76 0F 00 00 00 FF FF 03 00 50 4B 03 " + + "04 14 00 06 00 08 00 00 00 21 00 9D C5 0E FA 96 " + + "02 00 00 FD 06 00 00 10 00 00 00 64 72 73 2F 73 " + + "68 61 70 65 78 6D 6C 2E 78 6D 6C AC 55 4B 6E DB " + + "30 10 DD 17 E8 1D 08 EE 13 49 8E E5 8F 60 29 68 " + + "1D B4 9B A2 31 9C E6 00 AC 44 D9 42 29 52 20 59 " + + "5B CE AA 40 B7 05 7A 84 1E A2 9B A2 9F 9C 41 BE " + + "51 87 A4 A4 B4 E9 67 11 DB 0B 9B 9E 21 E7 BD 79 " + + "33 43 CE CE EB 92 A1 0D 95 AA 10 3C C6 C1 A9 8F " + + "11 E5 A9 C8 0A BE 8A F1 F5 AB 67 27 13 8C 94 26 " + + "3C 23 4C 70 1A E3 1D 55 F8 3C 79 FC 68 56 67 32 " + + "22 3C 5D 0B 89 20 04 57 11 18 62 BC D6 BA 8A 3C " + + "4F A5 6B 5A 12 75 2A 2A CA C1 9B 0B 59 12 0D 7F " + + "E5 CA CB 24 D9 42 F0 92 79 03 DF 1F 79 AA 92 94 " + + "64 6A 4D A9 BE 70 1E 9C D8 D8 7A 2B E6 94 B1 27 " + + "16 C2 99 72 29 4A B7 4A 05 4B CE 66 9E E1 60 96 " + + "F6 00 2C 2E F3 3C 19 8E 46 E3 41 D8 FB 8C C9 BA " + + "A5 D8 26 63 67 36 CB CE 66 FC 41 38 18 FA 7E EF " + + "B3 47 6C EC 3B 40 2D 7A 90 64 D4 07 EF 6D 36 CA " + + "E0 6C F2 2F E0 A0 3D 73 1F 79 1A 76 27 C0 73 87 " + + "DB A1 A9 0A 95 24 95 22 C6 18 69 5A 6B 56 F0 37 " + + "B0 76 B0 7C 73 55 2D 64 4B E1 E5 66 21 51 91 C5 " + + "78 8C 11 27 25 14 AA F9 B4 7F B7 FF D8 7C 6F 6E " + + "F7 EF 9B CF CD 6D F3 6D FF A1 F9 D1 7C 69 BE A2 " + + "11 F6 FA 63 26 06 FC B3 E9 FE 1A 51 D9 D8 24 AA " + + "73 59 B6 05 26 0F 28 6F 49 0A 0E 7C 49 24 F2 1C " + + "D5 31 1E 0C A6 21 E4 8C D1 0E 9A 6D 38 09 A7 BE " + + "6F C8 90 08 B2 43 29 6C E8 8C 28 35 3B C2 71 30 " + + "82 DD 86 A0 A3 62 B6 56 52 E9 E7 54 1C 4C 0B 99 " + + "40 31 96 34 D5 96 22 D9 BC 50 DA 41 75 10 AD 30 " + + "4E 0C D3 6D 4A EF 18 35 24 18 5F 52 C8 C8 76 FE " + + "83 85 81 8A 19 4D 2C BA 1D 19 3A 67 12 6D 08 8B " + + "31 49 53 CA 75 E0 5C 6B 92 51 67 0E 7D F8 B4 7A " + + "F4 27 AC 3A 96 90 61 96 17 8C 1D 8D 5B 4B C0 8C " + + "F3 9F DC 9C 56 2D 9E 2D 62 9E 83 98 47 03 F7 FF " + + "27 8C 03 A7 1D A2 CD 5C F0 E3 81 97 05 17 F2 6F " + + "04 18 54 A5 CD DC E1 75 4D E2 5A C3 74 89 AE 9F " + + "8A 6C 67 28 BD 86 5F 98 CC 43 FB 04 2E 68 7D 09 " + + "5F 39 13 DB 18 A7 AC A8 30 82 9B F7 E6 BE 4D 6A " + + "36 17 D0 3D 70 8B BB BB 39 C6 DA CD 17 53 FA CA " + + "10 3C 94 0A 64 0E 13 78 68 14 1B 04 74 21 6C 05 " + + "AF 0E 73 14 29 CF 16 44 92 25 D8 19 31 CF 8F 7C " + + "7B B2 BC 86 E7 E7 06 6E 82 A0 6F FB AA D5 BB 13 " + + "D9 5E 5D 0A AC F6 35 60 05 8C CD 05 D1 C4 94 C8 " + + "D6 E2 F7 77 C4 DA 9C 36 C9 4F 00 00 00 FF FF 03 " + + "00 50 4B 03 04 14 00 06 00 08 00 00 00 21 00 A3 " + + "9B 83 A2 1C 01 00 00 8E 01 00 00 0F 00 00 00 64 " + + "72 73 2F 64 6F 77 6E 72 65 76 2E 78 6D 6C 4C 90 " + + "CD 4E C3 30 10 84 EF 48 BC 83 B5 48 5C 10 75 9A " + + "34 69 08 75 AA 82 84 DA 0B 88 B6 E1 C0 CD 24 CE " + + "8F 88 ED CA 36 4D FA F6 6C 5B 50 73 B2 C7 3B DF " + + "EE 8E 67 F3 5E B6 64 2F 8C 6D B4 62 30 1E 79 40 " + + "84 CA 75 D1 A8 8A 41 B6 7D B9 8F 81 58 C7 55 C1 " + + "5B AD 04 83 83 B0 30 4F AF AF 66 3C 29 74 A7 D6 " + + "62 BF 71 15 C1 26 CA 26 9C 41 ED DC 2E A1 D4 E6 " + + "B5 90 DC 8E F4 4E 28 AC 95 DA 48 EE 50 9A 8A 16 " + + "86 77 D8 5C B6 D4 F7 BC 88 4A DE 28 9C 50 F3 9D " + + "78 AE 45 FE BD F9 91 38 E4 5D 7E 64 FA 29 FE 7C " + + "A5 77 59 B7 5E 6E E3 26 0C 62 C6 6E 6F FA C5 23 " + + "10 27 7A 77 31 FF D1 AB 82 C1 14 48 B9 3C 7C 99 " + + "A6 58 73 EB 84 61 80 71 30 1C 06 83 14 37 EE DB " + + "85 CA 6B 6D 8E F7 D2 68 49 8C EE 4E 54 AE 5B 06 " + + "01 1C F5 5B 59 5A E1 90 08 FD 89 87 38 96 FE 9F " + + "26 51 34 F5 43 A0 47 DC E9 33 3C 8E 4E 16 06 78 " + + "0E E8 87 10 8D 43 76 EC 07 F1 99 A5 C3 3D 50 5C " + + "BE 31 FD 05 00 00 FF FF 03 00 50 4B 01 02 2D 00 " + + "14 00 06 00 08 00 00 00 21 00 F0 F7 8A BB FD 00 " + + "00 00 E2 01 00 00 13 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 5B 43 6F 6E 74 65 6E 74 " + + "5F 54 79 70 65 73 5D 2E 78 6D 6C 50 4B 01 02 2D " + + "00 14 00 06 00 08 00 00 00 21 00 31 DD 5F 61 D2 " + + "00 00 00 8F 01 00 00 0B 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 2E 01 00 00 5F 72 65 6C 73 2F 2E " + + "72 65 6C 73 50 4B 01 02 2D 00 14 00 06 00 08 00 " + + "00 00 21 00 9D C5 0E FA 96 02 00 00 FD 06 00 00 " + + "10 00 00 00 00 00 00 00 00 00 00 00 00 00 29 02 " + + "00 00 64 72 73 2F 73 68 61 70 65 78 6D 6C 2E 78 " + + "6D 6C 50 4B 01 02 2D 00 14 00 06 00 08 00 00 00 " + + "21 00 A3 9B 83 A2 1C 01 00 00 8E 01 00 00 0F 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 ED 04 00 00 " + + "64 72 73 2F 64 6F 77 6E 72 65 76 2E 78 6D 6C 50 " + + "4B 05 06 00 00 00 00 04 00 04 00 F5 00 00 00 36 " + + "06 00 00 00 00 00 00 10 F0 12 00 00 00 00 00 03 " + + "00 10 03 07 00 CD 00 06 00 D0 00 10 00 0D 00 00 " + + "00 11 F0 00 00 00 00 5D 00 1A 00 15 00 12 00 02 " + + "00 06 00 11 60 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 00 00 00 3C 00 E8 07 0F 00 04 F0 E0 07 00 " + + "00 12 00 0A F0 08 00 00 00 07 04 00 00 00 0A 00 " + + "00 83 00 0B F0 50 00 00 00 BF 00 18 00 1F 00 81 " + + "01 4F 81 BD 00 BF 01 10 00 10 00 C0 01 38 5D 8A " + + "00 CB 01 38 63 00 00 FF 01 08 00 08 00 80 C3 20 " + + "00 00 00 BF 03 00 00 02 00 1F 04 40 04 4F 04 3C " + + "04 3E 04 43 04 33 04 3E 04 3B 04 4C 04 3D 04 38 " + + "04 3A 04 20 00 37 00 00 00 23 00 22 F1 4E 07 00 " + + "00 FF 01 00 00 40 00 A9 C3 42 07 00 00 50 4B 03 " + + "04 14 00 06 00 08 00 00 00 21 00 F0 F7 8A BB FD " + + "00 00 00 E2 01 00 00 13 00 00 00 5B 43 6F 6E 74 " + + "65 6E 74 5F 54 79 70 65 73 5D 2E 78 6D 6C 94 91 " + + "CD 4A C4 30 10 C7 EF 82 EF 10 E6 2A 6D AA 07 11 " + + "69 BA 07 AB 47 15 5D 1F 60 48 A6 6D D8 36 09 99 " + + "58 77 DF DE 74 3F 2E E2 0A 1E 67 E6 FF F1 23 A9 " + + "57 DB 69 14 33 45 B6 DE 29 B8 2E 2B 10 E4 B4 37 " + + "D6 F5 0A 3E D6 4F C5 1D 08 4E E8 0C 8E DE 91 82 " + + "1D 31 AC 9A CB 8B 7A BD 0B C4 22 BB 1D 2B 18 52 " + + "0A F7 52 B2 1E 68 42 2E 7D 20 97 2F 9D 8F 13 A6 " + + "3C C6 5E 06 D4 1B EC 49 DE 54 D5 AD D4 DE 25 72 " + + "A9 48 4B 06 34 75 4B 1D 7E 8E 49 3C 6E F3 FA 40 " + + "12 69 64 10 0F 07 E1 D2 A5 00 43 18 AD C6 94 49 " + + "E5 EC CC 8F 96 E2 D8 50 66 E7 5E C3 83 0D 7C 95 " + + "31 40 FE DA B0 5C CE 17 1C 7D 2F F9 69 A2 35 24 " + + "5E 31 A6 67 9C 32 86 34 91 25 0F 18 28 6B CA BF " + + "53 16 CC 89 0B DF 75 56 53 D9 46 7E 5F 7C 27 A8 " + + "73 E1 C6 7F B9 48 F3 7F B3 DB 6C 7B A3 F9 94 2E " + + "F7 3F D4 7C 03 00 00 FF FF 03 00 50 4B 03 04 14 " + + "00 06 00 08 00 00 00 21 00 31 DD 5F 61 D2 00 00 " + + "00 8F 01 00 00 0B 00 00 00 5F 72 65 6C 73 2F 2E " + + "72 65 6C 73 A4 90 C1 6A C3 30 0C 86 EF 83 BD 83 " + + "D1 BD 71 DA 43 19 A3 4E 6F 85 5E 4B 07 BB 0A 5B " + + "49 4C 63 CB 58 26 6D DF BE A6 30 58 46 6F 3B EA " + + "17 FA 3E F1 EF F6 B7 30 A9 99 B2 78 8E 06 D6 4D " + + "0B 8A A2 65 E7 E3 60 E0 EB 7C 58 7D 80 92 82 D1 " + + "E1 C4 91 0C DC 49 60 DF BD BF ED 4E 34 61 A9 47 " + + "32 FA 24 AA 52 A2 18 18 4B 49 9F 5A 8B 1D 29 A0 " + + "34 9C 28 D6 4D CF 39 60 A9 63 1E 74 42 7B C1 81 " + + "F4 A6 6D B7 3A FF 66 40 B7 60 AA A3 33 90 8F 6E " + + "03 EA 7C 4F D5 FC 87 1D BC CD 2C DC 97 C6 72 D0 " + + "DC F7 DE BE A2 6A C7 D7 78 A2 B9 52 30 0F 54 0C " + + "B8 2C CF 30 D3 DC D4 E7 40 BF F6 AE FF E9 95 11 " + + "13 7D 57 FE 42 FC 4C AB F5 C7 AC 17 35 76 0F 00 " + + "00 00 FF FF 03 00 50 4B 03 04 14 00 06 00 08 00 " + + "00 00 21 00 33 C2 C5 D0 98 02 00 00 FD 06 00 00 " + + "10 00 00 00 64 72 73 2F 73 68 61 70 65 78 6D 6C " + + "2E 78 6D 6C AC 55 CD 8E D3 30 10 BE 23 F1 0E 96 " + + "EF BB 49 BA 6D DA 46 4D 56 D0 15 5C 10 5B 75 D9 " + + "07 30 89 D3 46 38 76 64 9B 36 DD 13 12 57 24 1E " + + "81 87 E0 82 F8 D9 67 48 DF 88 B1 9D 64 81 05 0E " + + "DB F6 D0 BA 33 F6 7C DF 7C 33 63 CF CE EB 92 A1 " + + "0D 95 AA 10 3C C6 C1 A9 8F 11 E5 A9 C8 0A BE 8A " + + "F1 F5 AB 67 27 13 8C 94 26 3C 23 4C 70 1A E3 1D " + + "55 F8 3C 79 FC 68 56 67 32 22 3C 5D 0B 89 20 04 " + + "57 11 18 62 BC D6 BA 8A 3C 4F A5 6B 5A 12 75 2A " + + "2A CA C1 9B 0B 59 12 0D 7F E5 CA CB 24 D9 42 F0 " + + "92 79 03 DF 0F 3D 55 49 4A 32 B5 A6 54 5F 38 0F " + + "4E 6C 6C BD 15 73 CA D8 13 0B E1 4C B9 14 A5 5B " + + "A5 82 25 C3 99 67 38 98 A5 3D 00 8B CB 3C 4F A6 " + + "A3 C1 A8 F7 18 83 75 4A B1 4D 26 CE 6C 96 9D CD " + + "F8 83 60 78 E6 FB BD CF 1E B1 91 EF E0 B4 E8 21 " + + "92 B0 0F DE DB CC 91 C1 38 1C FC 0B 38 68 CF DC " + + "43 0E 83 69 77 06 7C 77 C8 1D 9E AA 50 49 52 29 " + + "62 8C 91 A6 B5 66 05 7F 03 6B 07 CC 37 57 D5 42 " + + "B6 24 5E 6E 16 12 15 59 8C A1 5C 9C 94 50 A8 E6 " + + "D3 FE DD FE 63 F3 BD B9 DD BF 6F 3E 37 B7 CD B7 " + + "FD 87 E6 47 F3 A5 F9 8A C6 D8 EB 8F 99 18 F0 CF " + + "26 FC 6B 44 65 63 93 A8 CE 65 D9 16 98 3C A0 BC " + + "25 29 38 F0 25 91 C8 73 54 C7 78 30 1C 8E 21 67 " + + "8C 76 D0 6C E1 D9 04 94 37 64 48 04 D9 A1 14 36 " + + "04 C3 C9 68 0A 46 94 9A 1D A3 71 10 C2 6E 43 D0 " + + "51 31 5B 2B A9 F4 73 2A 0E A6 85 4C A0 18 4B 9A " + + "6A 4B 91 6C 5E 28 ED A0 3A 88 56 18 27 86 E9 36 " + + "A5 77 8C 1A 12 8C 2F 29 64 64 3B FF C1 C2 40 C5 " + + "8C 26 16 DD 8E 0C 9D 33 89 36 84 C5 98 A4 29 E5 " + + "3A 70 AE 35 C9 A8 33 8F 7C F8 B4 7A F4 27 AC 3A " + + "96 90 61 96 17 8C 1D 8D 5B 4B C0 8C F3 7D 6E 4E " + + "AB 16 CF 16 31 CF 41 CC A3 81 FB FF 13 C6 81 D3 " + + "0E D1 66 2E F8 F1 C0 CB 82 0B F9 37 02 0C AA D2 " + + "66 EE F0 BA 26 71 AD 61 BA 44 D7 4F 45 B6 33 94 " + + "5E C3 2F 4C E6 A1 7D 02 17 B4 BE 84 AF 9C 89 6D " + + "8C 53 56 54 18 C1 CD 7B F3 A7 4D 6A 36 17 D0 3D " + + "30 3F EE 6E 8E B1 76 F3 C5 94 BE 32 04 0F A5 02 " + + "99 C3 04 1E 1A C5 06 01 5D 08 5B C1 AB C3 1C 45 " + + "CA B3 05 91 64 09 76 46 CC F3 23 DF 9E 2C AF E1 " + + "F9 B9 81 9B 20 E8 DB BE 6A F5 EE 44 B6 57 97 02 " + + "AB 7D 0D 58 01 63 73 41 34 31 25 B2 B5 F8 FD 1D " + + "B1 36 A7 4D F2 13 00 00 FF FF 03 00 50 4B 03 04 " + + "14 00 06 00 08 00 00 00 21 00 6F E2 C1 89 1B 01 " + + "00 00 8E 01 00 00 0F 00 00 00 64 72 73 2F 64 6F " + + "77 6E 72 65 76 2E 78 6D 6C 4C 90 CD 4E C3 30 10 " + + "84 EF 48 BC 43 B4 48 5C 10 75 92 36 21 0D 75 AA " + + "82 84 DA 0B 88 B4 E1 C0 CD 24 CE 8F 88 ED C8 36 " + + "4D FA F6 6C 5A 50 7B B2 C7 BB DF EE 8C 17 CB 41 " + + "B4 CE 9E 6B D3 28 49 C1 9B B8 E0 70 99 AB A2 91 " + + "15 85 6C F7 72 1F 81 63 2C 93 05 6B 95 E4 14 0E " + + "DC C0 32 B9 BE 5A B0 B8 50 BD 4C F9 7E 6B 2B 07 " + + "87 48 13 33 0A B5 B5 5D 4C 88 C9 6B 2E 98 99 A8 " + + "8E 4B AC 95 4A 0B 66 51 EA 8A 14 9A F5 38 5C B4 " + + "C4 77 DD 90 08 D6 48 DC 50 B3 8E 3F D7 3C FF DE " + + "FE 08 5C F2 2E 3E 32 F5 14 7D BE 92 BB AC 4F D7 " + + "BB A8 09 A6 11 A5 B7 37 C3 EA 11 1C CB 07 7B 6E " + + "FE A3 37 05 05 F4 5A AE 0F 5F BA 29 52 66 2C D7 " + + "14 30 0E 86 C3 60 90 A0 E3 A1 5D C9 BC 56 7A BC " + + "97 5A 09 47 AB FE 48 E5 AA A5 30 83 51 BF 95 A5 " + + "E1 16 09 6F 36 75 11 C7 D2 FF D3 3C F0 03 20 23 " + + "6C D5 09 F5 C2 63 03 05 3C 2F D9 D0 9B 63 EB 25 " + + "EB 3F 84 FE 89 26 97 3E 50 9C BF 31 F9 05 00 00 " + + "FF FF 03 00 50 4B 01 02 2D 00 14 00 06 00 08 00 " + + "00 00 21 00 F0 F7 8A BB FD 00 00 00 E2 01 00 00 " + + "13 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + + "00 00 5B 43 6F 6E 74 65 6E 74 5F 54 79 70 65 73 " + + "5D 2E 78 6D 6C 50 4B 01 02 2D 00 14 00 06 00 08 " + + "00 00 00 21 00 31 DD 5F 61 D2 00 00 00 8F 01 00 " + + "00 0B 00 00 00 00 00 00 00 00 00 00 00 00 00 2E " + + "01 00 00 5F 72 65 6C 73 2F 2E 72 65 6C 73 50 4B " + + "01 02 2D 00 14 00 06 00 08 00 00 00 21 00 33 C2 " + + "C5 D0 98 02 00 00 FD 06 00 00 10 00 00 00 00 00 " + + "00 00 00 00 00 00 00 00 29 02 00 00 64 72 73 2F " + + "73 68 61 70 65 78 6D 6C 2E 78 6D 6C 50 4B 01 02 " + + "2D 00 14 00 06 00 08 00 00 00 21 00 6F E2 C1 89 " + + "1B 01 00 00 8E 01 00 00 0F 00 00 00 00 00 00 00 " + + "00 00 00 00 00 00 EF 04 00 00 64 72 73 2F 64 6F " + + "77 6E 72 65 76 2E 78 6D 6C 50 4B 05 06 00 00 00 " + + "00 04 00 04 00 F5 00 00 00 37 06 00 00 00 00 00 " + + "00 10 F0 12 00 00 00 00 00 04 00 10 00 08 00 9A " + + "00 06 00 D0 01 10 00 DA 00 00 00 11 F0 00 00 00 " + + "00 5D 00 1A 00 15 00 12 00 02 00 07 00 11 60 00 " + + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00"; + + + byte[] dgBytes = HexRead.readFromString(data); + List dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes)); + assertEquals(14, dgRecords.size()); + + short[] expectedSids = { + DrawingRecord.sid, + ObjRecord.sid, + DrawingRecord.sid, + ObjRecord.sid, + DrawingRecord.sid, + ObjRecord.sid, + DrawingRecord.sid, + ObjRecord.sid, + ContinueRecord.sid, + ObjRecord.sid, + ContinueRecord.sid, + ObjRecord.sid, + ContinueRecord.sid, + ObjRecord.sid + }; + + for (int i = 0; i < expectedSids.length; i++) { + assertEquals("unexpected record.sid and index[" + i + "]", expectedSids[i], dgRecords.get(i).getSid()); + } + DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord()); + + // create a dummy sheet consisting of our test data + InternalSheet sheet = InternalSheet.createSheet(); + List records = sheet.getRecords(); + records.clear(); + records.addAll(dgRecords); + records.add(EOFRecord.instance); + + sheet.aggregateDrawingRecords(drawingManager, false); + assertEquals("drawing was not fully aggregated", 2, records.size()); + assertTrue("expected EscherAggregate", records.get(0) instanceof EscherAggregate); + assertTrue("expected EOFRecord", records.get(1) instanceof EOFRecord); + + EscherAggregate agg = (EscherAggregate) records.get(0); + + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data brefpore and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } + +} diff --git a/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java b/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java new file mode 100644 index 000000000..dc3711f50 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/model/TestDrawingShapes.java @@ -0,0 +1,665 @@ +/* ==================================================================== + 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 junit.framework.TestCase; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.util.HexDump; + +import java.io.IOException; +import java.util.Arrays; + +import static junit.framework.Assert.assertEquals; + +/** + * @author Evgeniy Berlog + * date: 12.06.12 + */ +public class TestDrawingShapes extends TestCase { + + /** + * HSSFShape tree bust be built correctly + * Check file with such records structure: + * -patriarch + * --shape + * --group + * ---group + * ----shape + * ----shape + * ---shape + * ---group + * ----shape + * ----shape + */ + public void testDrawingGroups() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("groups"); + HSSFPatriarch patriarch = sheet.getDrawingPatriarch(); + assertEquals(patriarch.getChildren().size(), 2); + HSSFShapeGroup group = (HSSFShapeGroup) patriarch.getChildren().get(1); + assertEquals(3, group.getChildren().size()); + HSSFShapeGroup group1 = (HSSFShapeGroup) group.getChildren().get(0); + assertEquals(2, group1.getChildren().size()); + group1 = (HSSFShapeGroup) group.getChildren().get(2); + assertEquals(2, group1.getChildren().size()); + } + + public void testHSSFShapeCompatibility() { + HSSFSimpleShape shape = new HSSFSimpleShape(null, new HSSFClientAnchor()); + shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + assertEquals(0x08000040, shape.getLineStyleColor()); + assertEquals(0x08000009, shape.getFillColor()); + assertEquals(HSSFShape.LINEWIDTH_DEFAULT, shape.getLineWidth()); + assertEquals(HSSFShape.LINESTYLE_SOLID, shape.getLineStyle()); + assertFalse(shape.isNoFill()); + + AbstractShape sp = AbstractShape.createShape(shape, 1); + EscherContainerRecord spContainer = sp.getSpContainer(); + EscherOptRecord opt = + spContainer.getChildById(EscherOptRecord.RECORD_ID); + + assertEquals(7, opt.getEscherProperties().size()); + assertEquals(true, + ((EscherBoolProperty) opt.lookup(EscherProperties.TEXT__SIZE_TEXT_TO_FIT_SHAPE)).isTrue()); + assertEquals(0x00000004, + ((EscherSimpleProperty) opt.lookup(EscherProperties.GEOMETRY__SHAPEPATH)).getPropertyValue()); + assertEquals(0x08000009, + ((EscherSimpleProperty) opt.lookup(EscherProperties.FILL__FILLCOLOR)).getPropertyValue()); + assertEquals(true, + ((EscherBoolProperty) opt.lookup(EscherProperties.FILL__NOFILLHITTEST)).isTrue()); + assertEquals(0x08000040, + ((EscherSimpleProperty) opt.lookup(EscherProperties.LINESTYLE__COLOR)).getPropertyValue()); + assertEquals(true, + ((EscherBoolProperty) opt.lookup(EscherProperties.LINESTYLE__NOLINEDRAWDASH)).isTrue()); + assertEquals(true, + ((EscherBoolProperty) opt.lookup(EscherProperties.GROUPSHAPE__PRINT)).isTrue()); + } + + public void testDefaultPictureSettings() { + HSSFPicture picture = new HSSFPicture(null, new HSSFClientAnchor()); + assertEquals(picture.getLineWidth(), HSSFShape.LINEWIDTH_DEFAULT); + assertEquals(picture.getFillColor(), HSSFShape.FILL__FILLCOLOR_DEFAULT); + assertEquals(picture.getLineStyle(), HSSFShape.LINESTYLE_NONE); + assertEquals(picture.getLineStyleColor(), HSSFShape.LINESTYLE__COLOR_DEFAULT); + assertEquals(picture.isNoFill(), false); + assertEquals(picture.getPictureIndex(), -1);//not set yet + } + + /** + * No NullPointerException should appear + */ + public void testDefaultSettingsWithEmptyContainer() { + EscherContainerRecord container = new EscherContainerRecord(); + EscherOptRecord opt = new EscherOptRecord(); + opt.setRecordId(EscherOptRecord.RECORD_ID); + container.addChildRecord(opt); + ObjRecord obj = new ObjRecord(); + CommonObjectDataSubRecord cod = new CommonObjectDataSubRecord(); + cod.setObjectType(HSSFSimpleShape.OBJECT_TYPE_PICTURE); + obj.addSubRecord(cod); + HSSFPicture picture = new HSSFPicture(container, obj); + + assertEquals(picture.getLineWidth(), HSSFShape.LINEWIDTH_DEFAULT); + assertEquals(picture.getFillColor(), HSSFShape.FILL__FILLCOLOR_DEFAULT); + assertEquals(picture.getLineStyle(), HSSFShape.LINESTYLE_DEFAULT); + assertEquals(picture.getLineStyleColor(), HSSFShape.LINESTYLE__COLOR_DEFAULT); + assertEquals(picture.isNoFill(), HSSFShape.NO_FILL_DEFAULT); + assertEquals(picture.getPictureIndex(), -1);//not set yet + } + + /** + * create a rectangle, save the workbook, read back and verify that all shape properties are there + */ + public void testReadWriteRectangle() throws IOException { + + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + + HSSFPatriarch drawing = sheet.createDrawingPatriarch(); + HSSFClientAnchor anchor = new HSSFClientAnchor(10, 10, 50, 50, (short) 2, 2, (short) 4, 4); + anchor.setAnchorType(2); + assertEquals(anchor.getAnchorType(), 2); + + HSSFSimpleShape rectangle = drawing.createSimpleShape(anchor); + rectangle.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + rectangle.setLineWidth(10000); + rectangle.setFillColor(777); + assertEquals(rectangle.getFillColor(), 777); + assertEquals(10000, rectangle.getLineWidth()); + rectangle.setLineStyle(10); + assertEquals(10, rectangle.getLineStyle()); + assertEquals(rectangle.getWrapText(), HSSFSimpleShape.WRAP_SQUARE); + rectangle.setLineStyleColor(1111); + rectangle.setNoFill(true); + rectangle.setWrapText(HSSFSimpleShape.WRAP_NONE); + rectangle.setString(new HSSFRichTextString("teeeest")); + assertEquals(rectangle.getLineStyleColor(), 1111); + assertEquals(((EscherSimpleProperty)((EscherOptRecord)HSSFTestHelper.getEscherContainer(rectangle).getChildById(EscherOptRecord.RECORD_ID)) + .lookup(EscherProperties.TEXT__TEXTID)).getPropertyValue(), "teeeest".hashCode()); + assertEquals(rectangle.isNoFill(), true); + assertEquals(rectangle.getWrapText(), HSSFSimpleShape.WRAP_NONE); + assertEquals(rectangle.getString().getString(), "teeeest"); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + + HSSFSimpleShape rectangle2 = + (HSSFSimpleShape) drawing.getChildren().get(0); + assertEquals(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE, + rectangle2.getShapeType()); + assertEquals(10000, rectangle2.getLineWidth()); + assertEquals(10, rectangle2.getLineStyle()); + assertEquals(anchor, rectangle2.getAnchor()); + assertEquals(rectangle2.getLineStyleColor(), 1111); + assertEquals(rectangle2.getFillColor(), 777); + assertEquals(rectangle2.isNoFill(), true); + assertEquals(rectangle2.getString().getString(), "teeeest"); + assertEquals(rectangle.getWrapText(), HSSFSimpleShape.WRAP_NONE); + + rectangle2.setFillColor(3333); + rectangle2.setLineStyle(9); + rectangle2.setLineStyleColor(4444); + rectangle2.setNoFill(false); + rectangle2.setLineWidth(77); + rectangle2.getAnchor().setDx1(2); + rectangle2.getAnchor().setDx2(3); + rectangle2.getAnchor().setDy1(4); + rectangle2.getAnchor().setDy2(5); + rectangle.setWrapText(HSSFSimpleShape.WRAP_BY_POINTS); + rectangle2.setString(new HSSFRichTextString("test22")); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + rectangle2 = (HSSFSimpleShape) drawing.getChildren().get(0); + assertEquals(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE, rectangle2.getShapeType()); + assertEquals(rectangle.getWrapText(), HSSFSimpleShape.WRAP_BY_POINTS); + assertEquals(77, rectangle2.getLineWidth()); + assertEquals(9, rectangle2.getLineStyle()); + assertEquals(rectangle2.getLineStyleColor(), 4444); + assertEquals(rectangle2.getFillColor(), 3333); + assertEquals(rectangle2.getAnchor().getDx1(), 2); + assertEquals(rectangle2.getAnchor().getDx2(), 3); + assertEquals(rectangle2.getAnchor().getDy1(), 4); + assertEquals(rectangle2.getAnchor().getDy2(), 5); + assertEquals(rectangle2.isNoFill(), false); + assertEquals(rectangle2.getString().getString(), "test22"); + + HSSFSimpleShape rect3 = drawing.createSimpleShape(new HSSFClientAnchor()); + rect3.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + + drawing = wb.getSheetAt(0).getDrawingPatriarch(); + assertEquals(drawing.getChildren().size(), 2); + } + + public void testReadExistingImage() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("pictures"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + HSSFPicture picture = (HSSFPicture) drawing.getChildren().get(0); + + assertEquals(picture.getPictureIndex(), 2); + assertEquals(picture.getLineStyleColor(), HSSFShape.LINESTYLE__COLOR_DEFAULT); + assertEquals(picture.getFillColor(), 0x5DC943); + assertEquals(picture.getLineWidth(), HSSFShape.LINEWIDTH_DEFAULT); + assertEquals(picture.getLineStyle(), HSSFShape.LINESTYLE_DEFAULT); + assertEquals(picture.isNoFill(), false); + + picture.setPictureIndex(2); + assertEquals(picture.getPictureIndex(), 2); + } + + + /* assert shape properties when reading shapes from a existing workbook */ + public void testReadExistingRectangle() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("rectangles"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + + HSSFSimpleShape shape = (HSSFSimpleShape) drawing.getChildren().get(0); + assertEquals(shape.isNoFill(), false); + assertEquals(shape.getLineStyle(), HSSFShape.LINESTYLE_DASHDOTGEL); + assertEquals(shape.getLineStyleColor(), 0x616161); + assertEquals(HexDump.toHex(shape.getFillColor()), shape.getFillColor(), 0x2CE03D); + assertEquals(shape.getLineWidth(), HSSFShape.LINEWIDTH_ONE_PT * 2); + assertEquals(shape.getString().getString(), "POItest"); + assertEquals(shape.getRotationDegree(), 27); + } + + public void testShapeIds() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet1 = wb.createSheet(); + HSSFPatriarch patriarch1 = sheet1.createDrawingPatriarch(); + for (int i = 0; i < 2; i++) { + patriarch1.createSimpleShape(new HSSFClientAnchor()); + } + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet1 = wb.getSheetAt(0); + patriarch1 = sheet1.getDrawingPatriarch(); + + EscherAggregate agg1 = HSSFTestHelper.getEscherAggregate(patriarch1); + // last shape ID cached in EscherDgRecord + EscherDgRecord dg1 = + agg1.getEscherContainer().getChildById(EscherDgRecord.RECORD_ID); + assertEquals(1026, dg1.getLastMSOSPID()); + + // iterate over shapes and check shapeId + EscherContainerRecord spgrContainer = + agg1.getEscherContainer().getChildContainers().get(0); + // root spContainer + 2 spContainers for shapes + assertEquals(3, spgrContainer.getChildRecords().size()); + + EscherSpRecord sp0 = + ((EscherContainerRecord) spgrContainer.getChild(0)).getChildById(EscherSpRecord.RECORD_ID); + assertEquals(1024, sp0.getShapeId()); + + EscherSpRecord sp1 = + ((EscherContainerRecord) spgrContainer.getChild(1)).getChildById(EscherSpRecord.RECORD_ID); + assertEquals(1025, sp1.getShapeId()); + + EscherSpRecord sp2 = + ((EscherContainerRecord) spgrContainer.getChild(2)).getChildById(EscherSpRecord.RECORD_ID); + assertEquals(1026, sp2.getShapeId()); + } + + /** + * Test get new id for shapes from existing file + * File already have for 1 shape on each sheet, because document must contain EscherDgRecord for each sheet + */ + public void testAllocateNewIds() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("empty.xls"); + HSSFSheet sheet = wb.getSheetAt(0); + HSSFPatriarch patriarch = sheet.getDrawingPatriarch(); + + /** + * 2048 - main SpContainer id + * 2049 - existing shape id + */ + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 2050); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 2051); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 2052); + + sheet = wb.getSheetAt(1); + patriarch = sheet.getDrawingPatriarch(); + + /** + * 3072 - main SpContainer id + * 3073 - existing shape id + */ + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 3074); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 3075); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 3076); + + + sheet = wb.getSheetAt(2); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 1026); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 1027); + assertEquals(HSSFTestHelper.allocateNewShapeId(patriarch), 1028); + } + + public void testOpt() throws Exception { + HSSFWorkbook wb = new HSSFWorkbook(); + + // create a sheet with a text box + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor()); + EscherOptRecord opt1 = HSSFTestHelper.getOptRecord(textbox); + EscherOptRecord opt2 = HSSFTestHelper.getEscherContainer(textbox).getChildById(EscherOptRecord.RECORD_ID); + assertSame(opt1, opt2); + } + + public void testCorrectOrderInOptRecord(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor()); + EscherOptRecord opt = HSSFTestHelper.getOptRecord(textbox); + + String opt1Str = opt.toXml(); + + textbox.setFillColor(textbox.getFillColor()); + EscherContainerRecord container = HSSFTestHelper.getEscherContainer(textbox); + EscherOptRecord optRecord = container.getChildById(EscherOptRecord.RECORD_ID); + assertEquals(opt1Str, optRecord.toXml()); + textbox.setLineStyle(textbox.getLineStyle()); + assertEquals(opt1Str, optRecord.toXml()); + textbox.setLineWidth(textbox.getLineWidth()); + assertEquals(opt1Str, optRecord.toXml()); + textbox.setLineStyleColor(textbox.getLineStyleColor()); + assertEquals(opt1Str, optRecord.toXml()); + } + + public void testDgRecordNumShapes(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + EscherAggregate aggregate = HSSFTestHelper.getEscherAggregate(patriarch); + EscherDgRecord dgRecord = (EscherDgRecord) aggregate.getEscherRecord(0).getChild(0); + assertEquals(dgRecord.getNumShapes(), 1); + } + + public void testTextForSimpleShape(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFSimpleShape shape = patriarch.createSimpleShape(new HSSFClientAnchor()); + shape.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + + EscherAggregate agg = HSSFTestHelper.getEscherAggregate(patriarch); + assertEquals(agg.getShapeToObjMapping().size(), 2); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + shape = (HSSFSimpleShape) patriarch.getChildren().get(0); + + agg = HSSFTestHelper.getEscherAggregate(patriarch); + assertEquals(agg.getShapeToObjMapping().size(), 2); + + shape.setString(new HSSFRichTextString("string1")); + assertEquals(shape.getString().getString(), "string1"); + + assertNotNull(HSSFTestHelper.getEscherContainer(shape).getChildById(EscherTextboxRecord.RECORD_ID)); + assertEquals(agg.getShapeToObjMapping().size(), 2); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + shape = (HSSFSimpleShape) patriarch.getChildren().get(0); + + assertNotNull(HSSFTestHelper.getTextObjRecord(shape)); + assertEquals(shape.getString().getString(), "string1"); + assertNotNull(HSSFTestHelper.getEscherContainer(shape).getChildById(EscherTextboxRecord.RECORD_ID)); + assertEquals(agg.getShapeToObjMapping().size(), 2); + } + + public void testRemoveShapes(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFSimpleShape rectangle = patriarch.createSimpleShape(new HSSFClientAnchor()); + rectangle.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + + int idx = wb.addPicture(new byte[]{1,2,3}, Workbook.PICTURE_TYPE_JPEG); + patriarch.createPicture(new HSSFClientAnchor(), idx); + + patriarch.createCellComment(new HSSFClientAnchor()); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPoints(new int[]{1,2}, new int[]{2,3}); + + patriarch.createTextbox(new HSSFClientAnchor()); + + HSSFShapeGroup group = patriarch.createGroup(new HSSFClientAnchor()); + group.createTextbox(new HSSFChildAnchor()); + group.createPicture(new HSSFChildAnchor(), idx); + + assertEquals(patriarch.getChildren().size(), 6); + assertEquals(group.getChildren().size(), 2); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 12); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 12); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + + assertEquals(patriarch.getChildren().size(), 6); + + group = (HSSFShapeGroup) patriarch.getChildren().get(5); + group.removeShape(group.getChildren().get(0)); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 10); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 10); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + + group = (HSSFShapeGroup) patriarch.getChildren().get(5); + patriarch.removeShape(group); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 8); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 8); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + assertEquals(patriarch.getChildren().size(), 5); + + HSSFShape shape = patriarch.getChildren().get(0); + patriarch.removeShape(shape); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 6); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + assertEquals(patriarch.getChildren().size(), 4); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 6); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + assertEquals(patriarch.getChildren().size(), 4); + + HSSFPicture picture = (HSSFPicture) patriarch.getChildren().get(0); + patriarch.removeShape(picture); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 5); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + assertEquals(patriarch.getChildren().size(), 3); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 5); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 1); + assertEquals(patriarch.getChildren().size(), 3); + + HSSFComment comment = (HSSFComment) patriarch.getChildren().get(0); + patriarch.removeShape(comment); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 3); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 0); + assertEquals(patriarch.getChildren().size(), 2); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 3); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 0); + assertEquals(patriarch.getChildren().size(), 2); + + polygon = (HSSFPolygon) patriarch.getChildren().get(0); + patriarch.removeShape(polygon); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 2); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 0); + assertEquals(patriarch.getChildren().size(), 1); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 2); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 0); + assertEquals(patriarch.getChildren().size(), 1); + + HSSFTextbox textbox = (HSSFTextbox) patriarch.getChildren().get(0); + patriarch.removeShape(textbox); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 0); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 0); + assertEquals(patriarch.getChildren().size(), 0); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getShapeToObjMapping().size(), 0); + assertEquals(HSSFTestHelper.getEscherAggregate(patriarch).getTailRecords().size(), 0); + assertEquals(patriarch.getChildren().size(), 0); + } + + public void testShapeFlip(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFSimpleShape rectangle = patriarch.createSimpleShape(new HSSFClientAnchor()); + rectangle.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + + assertEquals(rectangle.isFlipVertical(), false); + assertEquals(rectangle.isFlipHorizontal(), false); + + rectangle.setFlipVertical(true); + assertEquals(rectangle.isFlipVertical(), true); + rectangle.setFlipHorizontal(true); + assertEquals(rectangle.isFlipHorizontal(), true); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + rectangle = (HSSFSimpleShape) patriarch.getChildren().get(0); + + assertEquals(rectangle.isFlipHorizontal(), true); + rectangle.setFlipHorizontal(false); + assertEquals(rectangle.isFlipHorizontal(), false); + + assertEquals(rectangle.isFlipVertical(), true); + rectangle.setFlipVertical(false); + assertEquals(rectangle.isFlipVertical(), false); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + rectangle = (HSSFSimpleShape) patriarch.getChildren().get(0); + + assertEquals(rectangle.isFlipVertical(), false); + assertEquals(rectangle.isFlipHorizontal(), false); + } + + public void testRotation() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFSimpleShape rectangle = patriarch.createSimpleShape(new HSSFClientAnchor(0,0,100,100, (short) 0,0,(short)5,5)); + rectangle.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + + assertEquals(rectangle.getRotationDegree(), 0); + rectangle.setRotationDegree((short) 45); + assertEquals(rectangle.getRotationDegree(), 45); + rectangle.setFlipHorizontal(true); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + rectangle = (HSSFSimpleShape) patriarch.getChildren().get(0); + assertEquals(rectangle.getRotationDegree(), 45); + rectangle.setRotationDegree((short) 30); + assertEquals(rectangle.getRotationDegree(), 30); + + patriarch.setCoordinates(0, 0, 10, 10); + rectangle.setString(new HSSFRichTextString("1234")); + } + + public void testShapeContainerImplementsIterable(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + patriarch.createSimpleShape(new HSSFClientAnchor()); + patriarch.createSimpleShape(new HSSFClientAnchor()); + + int i=2; + + for (HSSFShape shape: patriarch){ + i--; + } + assertEquals(i, 0); + } + + public void testClearShapesForPatriarch(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + patriarch.createSimpleShape(new HSSFClientAnchor()); + patriarch.createSimpleShape(new HSSFClientAnchor()); + patriarch.createCellComment(new HSSFClientAnchor()); + + EscherAggregate agg = HSSFTestHelper.getEscherAggregate(patriarch); + + assertEquals(agg.getShapeToObjMapping().size(), 6); + assertEquals(agg.getTailRecords().size(), 1); + assertEquals(patriarch.getChildren().size(), 3); + + patriarch.clear(); + + assertEquals(agg.getShapeToObjMapping().size(), 0); + assertEquals(agg.getTailRecords().size(), 0); + assertEquals(patriarch.getChildren().size(), 0); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + assertEquals(agg.getShapeToObjMapping().size(), 0); + assertEquals(agg.getTailRecords().size(), 0); + assertEquals(patriarch.getChildren().size(), 0); + } +} diff --git a/src/testcases/org/apache/poi/hssf/model/TestEscherRecordFactory.java b/src/testcases/org/apache/poi/hssf/model/TestEscherRecordFactory.java new file mode 100644 index 000000000..4ff7c441e --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/model/TestEscherRecordFactory.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.model; + +import junit.framework.TestCase; +import org.apache.poi.ddf.DefaultEscherRecordFactory; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherTextboxRecord; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordBase; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFTestHelper; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Random; + +/** + * @author Evgeniy Berlog + * @date 18.06.12 + */ +public class TestEscherRecordFactory extends TestCase{ + + private static byte[] toByteArray(List records) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + for (RecordBase rb : records) { + Record r = (Record) rb; + try { + out.write(r.serialize()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return out.toByteArray(); + } + + public void testDetectContainer() { + Random rnd = new Random(); + assertEquals(true, DefaultEscherRecordFactory.isContainer((short) 0x0, EscherContainerRecord.DG_CONTAINER)); + assertEquals(true, DefaultEscherRecordFactory.isContainer((short) 0x0, EscherContainerRecord.SOLVER_CONTAINER)); + assertEquals(true, DefaultEscherRecordFactory.isContainer((short) 0x0, EscherContainerRecord.SP_CONTAINER)); + assertEquals(true, DefaultEscherRecordFactory.isContainer((short) 0x0, EscherContainerRecord.DGG_CONTAINER)); + assertEquals(true, DefaultEscherRecordFactory.isContainer((short) 0x0, EscherContainerRecord.BSTORE_CONTAINER)); + assertEquals(true, DefaultEscherRecordFactory.isContainer((short) 0x0, EscherContainerRecord.SPGR_CONTAINER)); + + for (Short i=EscherContainerRecord.DGG_CONTAINER; i<= EscherContainerRecord.SOLVER_CONTAINER; i++){ + assertEquals(true, DefaultEscherRecordFactory.isContainer(Integer.valueOf(rnd.nextInt(Short.MAX_VALUE)).shortValue(), i)); + } + + assertEquals(false, DefaultEscherRecordFactory.isContainer((short) 0x0, Integer.valueOf(EscherContainerRecord.DGG_CONTAINER-1).shortValue())); + assertEquals(false, DefaultEscherRecordFactory.isContainer((short) 0x0, Integer.valueOf(EscherContainerRecord.SOLVER_CONTAINER+1).shortValue())); + + assertEquals(true, DefaultEscherRecordFactory.isContainer((short) 0x000F, Integer.valueOf(EscherContainerRecord.DGG_CONTAINER-1).shortValue())); + assertEquals(true, DefaultEscherRecordFactory.isContainer((short) 0xFFFF, Integer.valueOf(EscherContainerRecord.DGG_CONTAINER-1).shortValue())); + assertEquals(false, DefaultEscherRecordFactory.isContainer((short) 0x000C, Integer.valueOf(EscherContainerRecord.DGG_CONTAINER-1).shortValue())); + assertEquals(false, DefaultEscherRecordFactory.isContainer((short) 0xCCCC, Integer.valueOf(EscherContainerRecord.DGG_CONTAINER-1).shortValue())); + assertEquals(false, DefaultEscherRecordFactory.isContainer((short) 0x000F, EscherTextboxRecord.RECORD_ID)); + assertEquals(false, DefaultEscherRecordFactory.isContainer((short) 0xCCCC, EscherTextboxRecord.RECORD_ID)); + } + + public void testDgContainerMustBeRootOfHSSFSheetEscherRecords() throws IOException { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("47251.xls"); + HSSFSheet sh = wb.getSheetAt(0); + InternalSheet ish = HSSFTestHelper.getSheetForTest(sh); + List records = ish.getRecords(); + // records to be aggregated + List dgRecords = records.subList(19, 23); + byte[] dgBytes = toByteArray(dgRecords); + sh.getDrawingPatriarch(); + EscherAggregate agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid); + assertEquals(true, agg.getEscherRecords().get(0) instanceof EscherContainerRecord); + assertEquals(EscherContainerRecord.DG_CONTAINER, agg.getEscherRecords().get(0).getRecordId()); + assertEquals((short) 0x0, agg.getEscherRecords().get(0).getOptions()); + agg = (EscherAggregate) ish.findFirstRecordBySid(EscherAggregate.sid); + byte[] dgBytesAfterSave = agg.serialize(); + assertEquals("different size of drawing data before and after save", dgBytes.length, dgBytesAfterSave.length); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + } +} diff --git a/src/testcases/org/apache/poi/hssf/model/TestHSSFAnchor.java b/src/testcases/org/apache/poi/hssf/model/TestHSSFAnchor.java new file mode 100644 index 000000000..bda65884c --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/model/TestHSSFAnchor.java @@ -0,0 +1,428 @@ +/* ==================================================================== + 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 junit.framework.TestCase; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.util.HexDump; + +/** + * @author Evgeniy Berlog + * @date 12.06.12 + */ +public class TestHSSFAnchor extends TestCase { + + public void testDefaultValues(){ + HSSFClientAnchor clientAnchor = new HSSFClientAnchor(); + assertEquals(clientAnchor.getAnchorType(), 0); + assertEquals(clientAnchor.getCol1(), 0); + assertEquals(clientAnchor.getCol2(), 0); + assertEquals(clientAnchor.getDx1(), 0); + assertEquals(clientAnchor.getDx2(), 0); + assertEquals(clientAnchor.getDy1(), 0); + assertEquals(clientAnchor.getDy2(), 0); + assertEquals(clientAnchor.getRow1(), 0); + assertEquals(clientAnchor.getRow2(), 0); + + clientAnchor = new HSSFClientAnchor(new EscherClientAnchorRecord()); + assertEquals(clientAnchor.getAnchorType(), 0); + assertEquals(clientAnchor.getCol1(), 0); + assertEquals(clientAnchor.getCol2(), 0); + assertEquals(clientAnchor.getDx1(), 0); + assertEquals(clientAnchor.getDx2(), 0); + assertEquals(clientAnchor.getDy1(), 0); + assertEquals(clientAnchor.getDy2(), 0); + assertEquals(clientAnchor.getRow1(), 0); + assertEquals(clientAnchor.getRow2(), 0); + + HSSFChildAnchor childAnchor = new HSSFChildAnchor(); + assertEquals(childAnchor.getDx1(), 0); + assertEquals(childAnchor.getDx2(), 0); + assertEquals(childAnchor.getDy1(), 0); + assertEquals(childAnchor.getDy2(), 0); + + childAnchor = new HSSFChildAnchor(new EscherChildAnchorRecord()); + assertEquals(childAnchor.getDx1(), 0); + assertEquals(childAnchor.getDx2(), 0); + assertEquals(childAnchor.getDy1(), 0); + assertEquals(childAnchor.getDy2(), 0); + } + + public void testCorrectOrderInSpContainer(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("pictures"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + + HSSFSimpleShape rectangle = (HSSFSimpleShape) drawing.getChildren().get(0); + + assertEquals(HSSFTestHelper.getEscherContainer(rectangle).getChild(0).getRecordId(), EscherSpRecord.RECORD_ID); + assertEquals(HSSFTestHelper.getEscherContainer(rectangle).getChild(1).getRecordId(), EscherOptRecord.RECORD_ID); + assertEquals(HSSFTestHelper.getEscherContainer(rectangle).getChild(2).getRecordId(), EscherClientAnchorRecord.RECORD_ID); + assertEquals(HSSFTestHelper.getEscherContainer(rectangle).getChild(3).getRecordId(), EscherClientDataRecord.RECORD_ID); + + rectangle.setAnchor(new HSSFClientAnchor()); + + assertEquals(HSSFTestHelper.getEscherContainer(rectangle).getChild(0).getRecordId(), EscherSpRecord.RECORD_ID); + assertEquals(HSSFTestHelper.getEscherContainer(rectangle).getChild(1).getRecordId(), EscherOptRecord.RECORD_ID); + assertEquals(HSSFTestHelper.getEscherContainer(rectangle).getChild(2).getRecordId(), EscherClientAnchorRecord.RECORD_ID); + assertEquals(HSSFTestHelper.getEscherContainer(rectangle).getChild(3).getRecordId(), EscherClientDataRecord.RECORD_ID); + } + + public void testCreateClientAnchorFromContainer(){ + EscherContainerRecord container = new EscherContainerRecord(); + EscherClientAnchorRecord escher = new EscherClientAnchorRecord(); + escher.setFlag((short) 3); + escher.setCol1((short)11); + escher.setCol2((short)12); + escher.setRow1((short)13); + escher.setRow2((short) 14); + escher.setDx1((short) 15); + escher.setDx2((short) 16); + escher.setDy1((short) 17); + escher.setDy2((short) 18); + container.addChildRecord(escher); + + HSSFClientAnchor anchor = (HSSFClientAnchor) HSSFAnchor.createAnchorFromEscher(container); + assertEquals(anchor.getCol1(), 11); + assertEquals(escher.getCol1(), 11); + assertEquals(anchor.getCol2(), 12); + assertEquals(escher.getCol2(), 12); + assertEquals(anchor.getRow1(), 13); + assertEquals(escher.getRow1(), 13); + assertEquals(anchor.getRow2(), 14); + assertEquals(escher.getRow2(), 14); + assertEquals(anchor.getDx1(), 15); + assertEquals(escher.getDx1(), 15); + assertEquals(anchor.getDx2(), 16); + assertEquals(escher.getDx2(), 16); + assertEquals(anchor.getDy1(), 17); + assertEquals(escher.getDy1(), 17); + assertEquals(anchor.getDy2(), 18); + assertEquals(escher.getDy2(), 18); + } + + public void testCreateChildAnchorFromContainer(){ + EscherContainerRecord container = new EscherContainerRecord(); + EscherChildAnchorRecord escher = new EscherChildAnchorRecord(); + escher.setDx1((short) 15); + escher.setDx2((short) 16); + escher.setDy1((short) 17); + escher.setDy2((short) 18); + container.addChildRecord(escher); + + HSSFChildAnchor anchor = (HSSFChildAnchor) HSSFAnchor.createAnchorFromEscher(container); + assertEquals(anchor.getDx1(), 15); + assertEquals(escher.getDx1(), 15); + assertEquals(anchor.getDx2(), 16); + assertEquals(escher.getDx2(), 16); + assertEquals(anchor.getDy1(), 17); + assertEquals(escher.getDy1(), 17); + assertEquals(anchor.getDy2(), 18); + assertEquals(escher.getDy2(), 18); + } + + public void testShapeEscherMustHaveAnchorRecord(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + + HSSFPatriarch drawing = sheet.createDrawingPatriarch(); + HSSFClientAnchor anchor = new HSSFClientAnchor(10, 10, 200, 200, (short)2, 2, (short)15, 15); + anchor.setAnchorType(2); + + HSSFSimpleShape rectangle = drawing.createSimpleShape(anchor); + rectangle.setShapeType(HSSFSimpleShape.OBJECT_TYPE_RECTANGLE); + + rectangle.setAnchor(anchor); + + assertNotNull(HSSFTestHelper.getEscherAnchor(anchor)); + assertNotNull(HSSFTestHelper.getEscherContainer(rectangle)); + assertTrue(HSSFTestHelper.getEscherAnchor(anchor).equals(HSSFTestHelper.getEscherContainer(rectangle).getChildById(EscherClientAnchorRecord.RECORD_ID))); + } + + public void testClientAnchorFromEscher(){ + EscherClientAnchorRecord escher = new EscherClientAnchorRecord(); + escher.setCol1((short)11); + escher.setCol2((short)12); + escher.setRow1((short)13); + escher.setRow2((short) 14); + escher.setDx1((short) 15); + escher.setDx2((short) 16); + escher.setDy1((short) 17); + escher.setDy2((short) 18); + + HSSFClientAnchor anchor = new HSSFClientAnchor(escher); + assertEquals(anchor.getCol1(), 11); + assertEquals(escher.getCol1(), 11); + assertEquals(anchor.getCol2(), 12); + assertEquals(escher.getCol2(), 12); + assertEquals(anchor.getRow1(), 13); + assertEquals(escher.getRow1(), 13); + assertEquals(anchor.getRow2(), 14); + assertEquals(escher.getRow2(), 14); + assertEquals(anchor.getDx1(), 15); + assertEquals(escher.getDx1(), 15); + assertEquals(anchor.getDx2(), 16); + assertEquals(escher.getDx2(), 16); + assertEquals(anchor.getDy1(), 17); + assertEquals(escher.getDy1(), 17); + assertEquals(anchor.getDy2(), 18); + assertEquals(escher.getDy2(), 18); + } + + public void testClientAnchorFromScratch(){ + HSSFClientAnchor anchor = new HSSFClientAnchor(); + EscherClientAnchorRecord escher = (EscherClientAnchorRecord) HSSFTestHelper.getEscherAnchor(anchor); + anchor.setAnchor((short)11, 12, 13, 14, (short)15, 16, 17, 18); + + assertEquals(anchor.getCol1(), 11); + assertEquals(escher.getCol1(), 11); + assertEquals(anchor.getCol2(), 15); + assertEquals(escher.getCol2(), 15); + assertEquals(anchor.getRow1(), 12); + assertEquals(escher.getRow1(), 12); + assertEquals(anchor.getRow2(), 16); + assertEquals(escher.getRow2(), 16); + assertEquals(anchor.getDx1(), 13); + assertEquals(escher.getDx1(), 13); + assertEquals(anchor.getDx2(), 17); + assertEquals(escher.getDx2(), 17); + assertEquals(anchor.getDy1(), 14); + assertEquals(escher.getDy1(), 14); + assertEquals(anchor.getDy2(), 18); + assertEquals(escher.getDy2(), 18); + + anchor.setCol1(111); + assertEquals(anchor.getCol1(), 111); + assertEquals(escher.getCol1(), 111); + anchor.setCol2(112); + assertEquals(anchor.getCol2(), 112); + assertEquals(escher.getCol2(), 112); + anchor.setRow1(113); + assertEquals(anchor.getRow1(), 113); + assertEquals(escher.getRow1(), 113); + anchor.setRow2(114); + assertEquals(anchor.getRow2(), 114); + assertEquals(escher.getRow2(), 114); + anchor.setDx1(115); + assertEquals(anchor.getDx1(), 115); + assertEquals(escher.getDx1(), 115); + anchor.setDx2(116); + assertEquals(anchor.getDx2(), 116); + assertEquals(escher.getDx2(), 116); + anchor.setDy1(117); + assertEquals(anchor.getDy1(), 117); + assertEquals(escher.getDy1(), 117); + anchor.setDy2(118); + assertEquals(anchor.getDy2(), 118); + assertEquals(escher.getDy2(), 118); + } + + public void testChildAnchorFromEscher(){ + EscherChildAnchorRecord escher = new EscherChildAnchorRecord(); + escher.setDx1((short) 15); + escher.setDx2((short) 16); + escher.setDy1((short) 17); + escher.setDy2((short) 18); + + HSSFChildAnchor anchor = new HSSFChildAnchor(escher); + assertEquals(anchor.getDx1(), 15); + assertEquals(escher.getDx1(), 15); + assertEquals(anchor.getDx2(), 16); + assertEquals(escher.getDx2(), 16); + assertEquals(anchor.getDy1(), 17); + assertEquals(escher.getDy1(), 17); + assertEquals(anchor.getDy2(), 18); + assertEquals(escher.getDy2(), 18); + } + + public void testChildAnchorFromScratch(){ + HSSFChildAnchor anchor = new HSSFChildAnchor(); + EscherChildAnchorRecord escher = (EscherChildAnchorRecord) HSSFTestHelper.getEscherAnchor(anchor); + anchor.setAnchor(11, 12, 13, 14); + + assertEquals(anchor.getDx1(), 11); + assertEquals(escher.getDx1(), 11); + assertEquals(anchor.getDx2(), 13); + assertEquals(escher.getDx2(), 13); + assertEquals(anchor.getDy1(), 12); + assertEquals(escher.getDy1(), 12); + assertEquals(anchor.getDy2(), 14); + assertEquals(escher.getDy2(), 14); + + anchor.setDx1(115); + assertEquals(anchor.getDx1(), 115); + assertEquals(escher.getDx1(), 115); + anchor.setDx2(116); + assertEquals(anchor.getDx2(), 116); + assertEquals(escher.getDx2(), 116); + anchor.setDy1(117); + assertEquals(anchor.getDy1(), 117); + assertEquals(escher.getDy1(), 117); + anchor.setDy2(118); + assertEquals(anchor.getDy2(), 118); + assertEquals(escher.getDy2(), 118); + } + + public void testEqualsToSelf(){ + HSSFClientAnchor clientAnchor = new HSSFClientAnchor(0, 1, 2, 3, (short)4, 5, (short)6, 7); + assertEquals(clientAnchor, clientAnchor); + + HSSFChildAnchor childAnchor = new HSSFChildAnchor(0, 1, 2, 3); + assertEquals(childAnchor, childAnchor); + } + + public void testPassIncompatibleTypeIsFalse(){ + HSSFClientAnchor clientAnchor = new HSSFClientAnchor(0, 1, 2, 3, (short)4, 5, (short)6, 7); + assertNotSame(clientAnchor, "wrongType"); + + HSSFChildAnchor childAnchor = new HSSFChildAnchor(0, 1, 2, 3); + assertNotSame(childAnchor, "wrongType"); + } + + public void testNullReferenceIsFalse() { + HSSFClientAnchor clientAnchor = new HSSFClientAnchor(0, 1, 2, 3, (short)4, 5, (short)6, 7); + assertFalse("Passing null to equals should return false", clientAnchor.equals(null)); + + HSSFChildAnchor childAnchor = new HSSFChildAnchor(0, 1, 2, 3); + assertFalse("Passing null to equals should return false", childAnchor.equals(null)); + } + + public void testEqualsIsReflexiveIsSymmetric() { + HSSFClientAnchor clientAnchor1 = new HSSFClientAnchor(0, 1, 2, 3, (short)4, 5, (short)6, 7); + HSSFClientAnchor clientAnchor2 = new HSSFClientAnchor(0, 1, 2, 3, (short)4, 5, (short)6, 7); + + assertTrue(clientAnchor1.equals(clientAnchor2)); + assertTrue(clientAnchor1.equals(clientAnchor2)); + + HSSFChildAnchor childAnchor1 = new HSSFChildAnchor(0, 1, 2, 3); + HSSFChildAnchor childAnchor2 = new HSSFChildAnchor(0, 1, 2, 3); + + assertTrue(childAnchor1.equals(childAnchor2)); + assertTrue(childAnchor2.equals(childAnchor1)); + } + + public void testEqualsValues(){ + HSSFClientAnchor clientAnchor1 = new HSSFClientAnchor(0, 1, 2, 3, (short)4, 5, (short)6, 7); + HSSFClientAnchor clientAnchor2 = new HSSFClientAnchor(0, 1, 2, 3, (short)4, 5, (short)6, 7); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setDx1(10); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setDx1(0); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setDy1(10); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setDy1(1); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setDx2(10); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setDx2(2); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setDy2(10); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setDy2(3); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setCol1(10); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setCol1(4); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setRow1(10); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setRow1(5); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setCol2(10); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setCol2(6); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setRow2(10); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setRow2(7); + assertEquals(clientAnchor1, clientAnchor2); + + clientAnchor2.setAnchorType(3); + assertNotSame(clientAnchor1, clientAnchor2); + clientAnchor2.setAnchorType(0); + assertEquals(clientAnchor1, clientAnchor2); + + HSSFChildAnchor childAnchor1 = new HSSFChildAnchor(0, 1, 2, 3); + HSSFChildAnchor childAnchor2 = new HSSFChildAnchor(0, 1, 2, 3); + + childAnchor1.setDx1(10); + assertNotSame(childAnchor1, childAnchor2); + childAnchor1.setDx1(0); + assertEquals(childAnchor1, childAnchor2); + + childAnchor2.setDy1(10); + assertNotSame(childAnchor1, childAnchor2); + childAnchor2.setDy1(1); + assertEquals(childAnchor1, childAnchor2); + + childAnchor2.setDx2(10); + assertNotSame(childAnchor1, childAnchor2); + childAnchor2.setDx2(2); + assertEquals(childAnchor1, childAnchor2); + + childAnchor2.setDy2(10); + assertNotSame(childAnchor1, childAnchor2); + childAnchor2.setDy2(3); + assertEquals(childAnchor1, childAnchor2); + } + + public void testFlipped(){ + HSSFChildAnchor child = new HSSFChildAnchor(2,2,1,1); + assertEquals(child.isHorizontallyFlipped(), true); + assertEquals(child.isVerticallyFlipped(), true); + assertEquals(child.getDx1(), 1); + assertEquals(child.getDx2(), 2); + assertEquals(child.getDy1(), 1); + assertEquals(child.getDy2(), 2); + + child = new HSSFChildAnchor(3,3,4,4); + assertEquals(child.isHorizontallyFlipped(), false); + assertEquals(child.isVerticallyFlipped(), false); + assertEquals(child.getDx1(), 3); + assertEquals(child.getDx2(), 4); + assertEquals(child.getDy1(), 3); + assertEquals(child.getDy2(), 4); + + HSSFClientAnchor client = new HSSFClientAnchor(1,1,1,1, (short)4,4,(short)3,3); + assertEquals(client.isVerticallyFlipped(), true); + assertEquals(client.isHorizontallyFlipped(), true); + assertEquals(client.getCol1(), 3); + assertEquals(client.getCol2(), 4); + assertEquals(client.getRow1(), 3); + assertEquals(client.getRow2(), 4); + + client = new HSSFClientAnchor(1,1,1,1, (short)5,5,(short)6,6); + assertEquals(client.isVerticallyFlipped(), false); + assertEquals(client.isHorizontallyFlipped(), false); + assertEquals(client.getCol1(), 5); + assertEquals(client.getCol2(), 6); + assertEquals(client.getRow1(), 5); + assertEquals(client.getRow2(), 6); + } +} diff --git a/src/testcases/org/apache/poi/hssf/record/TestDrawingRecord.java b/src/testcases/org/apache/poi/hssf/record/TestDrawingRecord.java index f207a0046..38992c0bd 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestDrawingRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestDrawingRecord.java @@ -49,17 +49,12 @@ public final class TestDrawingRecord extends TestCase { out.write(cn.serialize()); List rec = RecordFactory.createRecords(new ByteArrayInputStream(out.toByteArray())); - assertEquals(1, rec.size()); + assertEquals(2, rec.size()); assertTrue(rec.get(0) instanceof DrawingRecord); + assertTrue(rec.get(1) instanceof ContinueRecord); - //DrawingRecord.getData() should return concatenated data1 and data2 - byte[] tmp = new byte[data1.length + data2.length]; - System.arraycopy(data1, 0, tmp, 0, data1.length); - System.arraycopy(data2, 0, tmp, data1.length, data2.length); - - DrawingRecord dg2 = (DrawingRecord)rec.get(0); - assertEquals(data1.length + data2.length, dg2.getData().length); - assertTrue(Arrays.equals(tmp, dg2.getData())); + assertTrue(Arrays.equals(data1, ((DrawingRecord)rec.get(0)).getData())); + assertTrue(Arrays.equals(data2, ((ContinueRecord)rec.get(1)).getData())); } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java b/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java index 7c5a88bd4..a2df1c841 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java @@ -16,8 +16,18 @@ ==================================================================== */ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.*; +import org.apache.poi.hssf.model.DrawingManager2; import org.apache.poi.hssf.model.InternalSheet; import org.apache.poi.hssf.model.InternalWorkbook; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.TextObjectRecord; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; /** * Helper class for HSSF tests that aren't within the @@ -25,6 +35,33 @@ import org.apache.poi.hssf.model.InternalWorkbook; * UserModel things. */ public class HSSFTestHelper { + + public static class MockDrawingManager extends DrawingManager2 { + + public MockDrawingManager (){ + super(null); + } + + @Override + public int allocateShapeId(short drawingGroupId) { + return 1025; //Mock value + } + + @Override + public int allocateShapeId(short drawingGroupId, EscherDgRecord dg) { + return 1025; + } + + public EscherDgRecord createDgRecord() + { + EscherDgRecord dg = new EscherDgRecord(); + dg.setRecordId( EscherDgRecord.RECORD_ID ); + dg.setOptions( (short) (16) ); + dg.setNumShapes( 1 ); + dg.setLastMSOSPID( 1024 ); + return dg; + } + } /** * Lets non UserModel tests at the low level Workbook */ @@ -34,4 +71,55 @@ public class HSSFTestHelper { public static InternalSheet getSheetForTest(HSSFSheet sheet) { return sheet.getSheet(); } + + public static HSSFPatriarch createTestPatriarch(HSSFSheet sheet, EscherAggregate agg){ + return new HSSFPatriarch(sheet, agg); + } + + public static EscherAggregate getEscherAggregate(HSSFPatriarch patriarch){ + return patriarch._getBoundAggregate(); + } + + public static int allocateNewShapeId(HSSFPatriarch patriarch){ + return patriarch.newShapeId(); + } + + public static EscherOptRecord getOptRecord(HSSFShape shape){ + return shape.getOptRecord(); + } + + public static void convertHSSFGroup(HSSFShapeGroup shape, EscherContainerRecord escherParent, Map shapeToObj){ + Class clazz = EscherAggregate.class; + try { + Method method = clazz.getDeclaredMethod("convertGroup", HSSFShapeGroup.class, EscherContainerRecord.class, Map.class); + method.setAccessible(true); + method.invoke(new EscherAggregate(new MockDrawingManager()), shape, escherParent, shapeToObj); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + public static void setShapeId(HSSFShape shape, int id){ + shape.setShapeId(id); + } + + public static EscherContainerRecord getEscherContainer(HSSFShape shape){ + return shape.getEscherContainer(); + } + + public static TextObjectRecord getTextObjRecord(HSSFSimpleShape shape){ + return shape.getTextObjectRecord(); + } + + public static ObjRecord getObjRecord(HSSFShape shape){ + return shape.getObjRecord(); + } + + public static EscherRecord getEscherAnchor(HSSFAnchor anchor){ + return anchor.getEscherAnchor(); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index 0864549f6..69f0e3425 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -24,9 +24,12 @@ import org.apache.poi.hssf.HSSFITestDataProvider; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.OldExcelFormatException; import org.apache.poi.hssf.extractor.ExcelExtractor; +import org.apache.poi.hssf.model.InternalSheet; import org.apache.poi.hssf.model.InternalWorkbook; import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; +import org.apache.poi.hssf.record.aggregates.PageSettingsBlock; +import org.apache.poi.hssf.record.aggregates.RecordAggregate; import org.apache.poi.hssf.record.common.UnicodeString; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; @@ -2238,4 +2241,31 @@ if(1==2) { // make sure we are still readable writeOutAndReadBack(workbook); } + + public void test51675(){ + final List list = new ArrayList(); + HSSFWorkbook workbook = openSample("51675.xls"); + HSSFSheet sh = workbook.getSheetAt(0); + InternalSheet ish = HSSFTestHelper.getSheetForTest(sh); + PageSettingsBlock psb = (PageSettingsBlock) ish.getRecords().get(13); + psb.visitContainedRecords(new RecordAggregate.RecordVisitor() { + public void visitRecord(Record r) { + list.add(r.getSid()); + } + }); + assertTrue(list.get(list.size()-1).intValue() == UnknownRecord.BITMAP_00E9); + assertTrue(list.get(list.size()-2).intValue() == UnknownRecord.HEADER_FOOTER_089C); + } + + public void test52272(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch p = sh.createDrawingPatriarch(); + + HSSFSimpleShape s = p.createSimpleShape(new HSSFClientAnchor()); + s.setShapeType(HSSFSimpleShape.OBJECT_TYPE_LINE); + + HSSFSheet sh2 = wb.cloneSheet(0); + assertNotNull(sh2.getDrawingPatriarch()); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java index 1f276b776..10acaa5e9 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestCloneSheet.java @@ -19,8 +19,14 @@ package org.apache.poi.hssf.usermodel; import junit.framework.TestCase; +import org.apache.poi.ddf.EscherDgRecord; +import org.apache.poi.ddf.EscherSpRecord; +import org.apache.poi.hssf.record.EscherAggregate; import org.apache.poi.ss.util.CellRangeAddress; +import java.io.IOException; +import java.util.Arrays; + /** * Test the ability to clone a sheet. * If adding new records that belong to a sheet (as opposed to a book) @@ -55,4 +61,74 @@ public final class TestCloneSheet extends TestCase { assertTrue("Row 3 still should be broken", clone.isRowBroken(3)); } + + public void testCloneSheetWithoutDrawings(){ + HSSFWorkbook b = new HSSFWorkbook(); + HSSFSheet s = b.createSheet("Test"); + HSSFSheet s2 = s.cloneSheet(b); + + assertNull(s.getDrawingPatriarch()); + assertNull(s2.getDrawingPatriarch()); + assertEquals(HSSFTestHelper.getSheetForTest(s).getRecords().size(), HSSFTestHelper.getSheetForTest(s2).getRecords().size()); + } + + public void testCloneSheetWithEmptyDrawingAggregate(){ + HSSFWorkbook b = new HSSFWorkbook(); + HSSFSheet s = b.createSheet("Test"); + HSSFPatriarch patriarch = s.createDrawingPatriarch(); + + EscherAggregate agg1 = patriarch._getBoundAggregate(); + + HSSFSheet s2 = s.cloneSheet(b); + + patriarch = s2.getDrawingPatriarch(); + + EscherAggregate agg2 = patriarch._getBoundAggregate(); + + EscherSpRecord sp1 = (EscherSpRecord) agg1.getEscherContainer().getChild(1).getChild(0).getChild(1); + EscherSpRecord sp2 = (EscherSpRecord) agg2.getEscherContainer().getChild(1).getChild(0).getChild(1); + + assertEquals(sp1.getShapeId(), 1024); + assertEquals(sp2.getShapeId(), 2048); + + EscherDgRecord dg = (EscherDgRecord) agg2.getEscherContainer().getChild(0); + + assertEquals(dg.getLastMSOSPID(), 2048); + assertEquals(dg.getInstance(), 0x2); + + //everything except id and DgRecord.lastMSOSPID and DgRecord.Instance must be the same + + sp2.setShapeId(1024); + dg.setLastMSOSPID(1024); + dg.setInstance((short) 0x1); + + assertEquals(agg1.serialize().length, agg2.serialize().length); + assertEquals(agg1.toXml(""), agg2.toXml("")); + assertTrue(Arrays.equals(agg1.serialize(), agg2.serialize())); + } + + public void testCloneComment() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch p = sh.createDrawingPatriarch(); + HSSFComment c = p.createComment(new HSSFClientAnchor(0,0,100,100, (short) 0,0,(short)5,5)); + c.setColumn(1); + c.setRow(2); + c.setString(new HSSFRichTextString("qwertyuio")); + + HSSFSheet sh2 = wb.cloneSheet(0); + HSSFPatriarch p2 = sh2.getDrawingPatriarch(); + HSSFComment c2 = (HSSFComment) p2.getChildren().get(0); + + assertTrue(Arrays.equals(c2.getTextObjectRecord().serialize(), c.getTextObjectRecord().serialize())); + assertTrue(Arrays.equals(c2.getObjRecord().serialize(), c.getObjRecord().serialize())); + assertTrue(Arrays.equals(c2.getNoteRecord().serialize(), c.getNoteRecord().serialize())); + + + //everything except spRecord.shapeId must be the same + assertFalse(Arrays.equals(c2.getEscherContainer().serialize(), c.getEscherContainer().serialize())); + EscherSpRecord sp = (EscherSpRecord) c2.getEscherContainer().getChild(0); + sp.setShapeId(1025); + assertTrue(Arrays.equals(c2.getEscherContainer().serialize(), c.getEscherContainer().serialize())); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestComment.java b/src/testcases/org/apache/poi/hssf/usermodel/TestComment.java new file mode 100644 index 000000000..6c0919894 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestComment.java @@ -0,0 +1,268 @@ +/* ==================================================================== + 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 org.apache.poi.ddf.EscherSpRecord; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.model.CommentShape; +import org.apache.poi.hssf.model.HSSFTestModelHelper; +import org.apache.poi.hssf.record.*; + +import java.io.*; +import java.util.Arrays; + +/** + * @author Evgeniy Berlog + * @date 26.06.12 + */ +public class TestComment extends TestCase { + + public void testResultEqualsToAbstractShape() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFComment comment = patriarch.createCellComment(new HSSFClientAnchor()); + HSSFRow row = sh.createRow(0); + HSSFCell cell = row.createCell(0); + cell.setCellComment(comment); + + CommentShape commentShape = HSSFTestModelHelper.createCommentShape(1025, comment); + + assertEquals(comment.getEscherContainer().getChildRecords().size(), 5); + assertEquals(commentShape.getSpContainer().getChildRecords().size(), 5); + + //sp record + byte[] expected = commentShape.getSpContainer().getChild(0).serialize(); + byte[] actual = comment.getEscherContainer().getChild(0).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = commentShape.getSpContainer().getChild(2).serialize(); + actual = comment.getEscherContainer().getChild(2).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = commentShape.getSpContainer().getChild(3).serialize(); + actual = comment.getEscherContainer().getChild(3).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = commentShape.getSpContainer().getChild(4).serialize(); + actual = comment.getEscherContainer().getChild(4).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + ObjRecord obj = comment.getObjRecord(); + ObjRecord objShape = commentShape.getObjRecord(); + /**shapeId = 1025 % 1024**/ + ((CommonObjectDataSubRecord)objShape.getSubRecords().get(0)).setObjectId(1); + + expected = obj.serialize(); + actual = objShape.serialize(); + + TextObjectRecord tor = comment.getTextObjectRecord(); + TextObjectRecord torShape = commentShape.getTextObjectRecord(); + + expected = tor.serialize(); + actual = torShape.serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + NoteRecord note = comment.getNoteRecord(); + NoteRecord noteShape = commentShape.getNoteRecord(); + noteShape.setShapeId(1); + + expected = note.serialize(); + actual = noteShape.serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + } + + public void testAddToExistingFile() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + int idx = wb.addPicture(new byte[]{1,2,3}, HSSFWorkbook.PICTURE_TYPE_PNG); + + HSSFComment comment = patriarch.createCellComment(new HSSFClientAnchor()); + comment.setString(new HSSFRichTextString("comment1")); + comment = patriarch.createCellComment(new HSSFClientAnchor(0,0,100,100,(short)0,0,(short)10,10)); + comment.setString(new HSSFRichTextString("comment2")); + comment.setBackgroundImage(idx); + assertEquals(comment.getBackgroundImageId(), idx); + + assertEquals(patriarch.getChildren().size(), 2); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + comment = (HSSFComment) patriarch.getChildren().get(1); + assertEquals(comment.getBackgroundImageId(), idx); + comment.resetBackgroundImage(); + assertEquals(comment.getBackgroundImageId(), 0); + + assertEquals(patriarch.getChildren().size(), 2); + comment = patriarch.createCellComment(new HSSFClientAnchor()); + comment.setString(new HSSFRichTextString("comment3")); + + assertEquals(patriarch.getChildren().size(), 3); + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + comment = (HSSFComment) patriarch.getChildren().get(1); + assertEquals(comment.getBackgroundImageId(), 0); + assertEquals(patriarch.getChildren().size(), 3); + assertEquals(((HSSFComment) patriarch.getChildren().get(0)).getString().getString(), "comment1"); + assertEquals(((HSSFComment) patriarch.getChildren().get(1)).getString().getString(), "comment2"); + assertEquals(((HSSFComment) patriarch.getChildren().get(2)).getString().getString(), "comment3"); + } + + public void testSetGetProperties() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFComment comment = patriarch.createCellComment(new HSSFClientAnchor()); + comment.setString(new HSSFRichTextString("comment1")); + assertEquals(comment.getString().getString(), "comment1"); + + comment.setAuthor("poi"); + assertEquals(comment.getAuthor(), "poi"); + + comment.setColumn(3); + assertEquals(comment.getColumn(), 3); + + comment.setRow(4); + assertEquals(comment.getRow(), 4); + + comment.setVisible(false); + assertEquals(comment.isVisible(), false); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + comment = (HSSFComment) patriarch.getChildren().get(0); + + assertEquals(comment.getString().getString(), "comment1"); + assertEquals("poi", comment.getAuthor()); + assertEquals(comment.getColumn(), 3); + assertEquals(comment.getRow(), 4); + assertEquals(comment.isVisible(), false); + + comment.setString(new HSSFRichTextString("comment12")); + comment.setAuthor("poi2"); + comment.setColumn(32); + comment.setRow(42); + comment.setVisible(true); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + comment = (HSSFComment) patriarch.getChildren().get(0); + + assertEquals(comment.getString().getString(), "comment12"); + assertEquals("poi2", comment.getAuthor()); + assertEquals(comment.getColumn(), 32); + assertEquals(comment.getRow(), 42); + assertEquals(comment.isVisible(), true); + } + + public void testExistingFileWithComment(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("comments"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + HSSFComment comment = (HSSFComment) drawing.getChildren().get(0); + assertEquals(comment.getAuthor(), "evgeniy"); + assertEquals(comment.getString().getString(), "evgeniy:\npoi test"); + assertEquals(comment.getColumn(), 1); + assertEquals(comment.getRow(), 2); + } + + public void testFindComments(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFComment comment = patriarch.createCellComment(new HSSFClientAnchor()); + HSSFRow row = sh.createRow(5); + HSSFCell cell = row.createCell(4); + cell.setCellComment(comment); + + HSSFTestModelHelper.createCommentShape(0, comment); + + assertNotNull(sh.findCellComment(5, 4)); + assertNull(sh.findCellComment(5, 5)); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + + assertNotNull(sh.findCellComment(5, 4)); + assertNull(sh.findCellComment(5, 5)); + } + + public void testInitState(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + EscherAggregate agg = HSSFTestHelper.getEscherAggregate(patriarch); + assertEquals(agg.getTailRecords().size(), 0); + + HSSFComment comment = patriarch.createCellComment(new HSSFClientAnchor()); + assertEquals(agg.getTailRecords().size(), 1); + + HSSFSimpleShape shape = patriarch.createSimpleShape(new HSSFClientAnchor()); + + assertEquals(comment.getOptRecord().getEscherProperties().size(), 10); + } + + public void testShapeId(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFComment comment = patriarch.createCellComment(new HSSFClientAnchor()); + + comment.setShapeId(2024); + /** + * SpRecord.id == shapeId + * ObjRecord.id == shapeId % 1024 + * NoteRecord.id == ObjectRecord.id == shapeId % 1024 + */ + + assertEquals(comment.getShapeId(), 2024); + + CommonObjectDataSubRecord cod = (CommonObjectDataSubRecord) comment.getObjRecord().getSubRecords().get(0); + assertEquals(cod.getObjectId(), 1000); + EscherSpRecord spRecord = (EscherSpRecord) comment.getEscherContainer().getChild(0); + assertEquals(spRecord.getShapeId(), 2024); + assertEquals(comment.getShapeId(), 2024); + assertEquals(comment.getNoteRecord().getShapeId(), 1000); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestEmbeddedObjects.java b/src/testcases/org/apache/poi/hssf/usermodel/TestEmbeddedObjects.java new file mode 100644 index 000000000..c7d478e4f --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestEmbeddedObjects.java @@ -0,0 +1,41 @@ +/* ==================================================================== + 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 org.apache.poi.hssf.HSSFTestDataSamples; + +import java.io.IOException; +import java.util.List; + +/** + * @author Evgeniy Berlog + * @date 13.07.12 + */ +public class TestEmbeddedObjects extends TestCase{ + + public void testReadExistingObject() throws IOException { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + List list = wb.getAllEmbeddedObjects(); + assertEquals(list.size(), 1); + HSSFObjectData obj = list.get(0); + assertNotNull(obj.getObjectData()); + assertNotNull(obj.getDirectory()); + assertNotNull(obj.getOLE2ClassName()); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java index 7d04110b1..b49f725ba 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFComment.java @@ -32,7 +32,7 @@ public final class TestHSSFComment extends BaseTestCellComment { } public void testDefaultShapeType() { - HSSFComment comment = new HSSFComment((HSSFShape)null, (HSSFAnchor)null); + HSSFComment comment = new HSSFComment((HSSFShape)null, new HSSFClientAnchor()); assertEquals(HSSFSimpleShape.OBJECT_TYPE_COMMENT, comment.getShapeType()); } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java index 6c08a167a..dcc611c05 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPicture.java @@ -17,8 +17,10 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherBSERecord; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.HSSFITestDataProvider; +import org.apache.poi.hssf.model.InternalSheet; import org.apache.poi.ss.usermodel.BaseTestPicture; import org.apache.poi.ss.usermodel.PictureData; import org.apache.poi.ss.usermodel.Workbook; @@ -149,4 +151,63 @@ public final class TestHSSFPicture extends BaseTestPicture { assertTrue(Arrays.equals(data4, ((HSSFPicture)dr.getChildren().get(3)).getPictureData().getData())); } + public void testBSEPictureRef(){ + HSSFWorkbook wb = new HSSFWorkbook(); + + HSSFSheet sh = wb.createSheet("Pictures"); + HSSFPatriarch dr = sh.createDrawingPatriarch(); + HSSFClientAnchor anchor = new HSSFClientAnchor(); + + InternalSheet ish = HSSFTestHelper.getSheetForTest(sh); + + //register a picture + byte[] data1 = new byte[]{1, 2, 3}; + int idx1 = wb.addPicture(data1, Workbook.PICTURE_TYPE_JPEG); + assertEquals(1, idx1); + HSSFPicture p1 = dr.createPicture(anchor, idx1); + + EscherBSERecord bse = wb.getWorkbook().getBSERecord(idx1); + + assertEquals(bse.getRef(), 1); + dr.createPicture(new HSSFClientAnchor(), idx1); + assertEquals(bse.getRef(), 2); + + HSSFShapeGroup gr = dr.createGroup(new HSSFClientAnchor()); + gr.createPicture(new HSSFChildAnchor(), idx1); + assertEquals(bse.getRef(), 3); + } + + public void testReadExistingImage(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("picture"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + + HSSFPicture picture = (HSSFPicture) drawing.getChildren().get(0); + assertEquals(picture.getFileName(), "test"); + } + + public void testSetGetProperties(){ + HSSFWorkbook wb = new HSSFWorkbook(); + + HSSFSheet sh = wb.createSheet("Pictures"); + HSSFPatriarch dr = sh.createDrawingPatriarch(); + HSSFClientAnchor anchor = new HSSFClientAnchor(); + + //register a picture + byte[] data1 = new byte[]{1, 2, 3}; + int idx1 = wb.addPicture(data1, Workbook.PICTURE_TYPE_JPEG); + HSSFPicture p1 = dr.createPicture(anchor, idx1); + + assertEquals(p1.getFileName(), ""); + p1.setFileName("aaa"); + assertEquals(p1.getFileName(), "aaa"); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheet("Pictures"); + dr = sh.getDrawingPatriarch(); + + p1 = (HSSFPicture) dr.getChildren().get(0); + assertEquals(p1.getFileName(), "aaa"); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index 61c9c052c..a640ce971 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -824,8 +824,8 @@ public final class TestHSSFSheet extends BaseTestSheet { HSSFSheet sheet2 = wb2.getSheetAt(1); //check that id of the drawing group was updated - EscherDgRecord dg1 = (EscherDgRecord)sheet1.getDrawingEscherAggregate().findFirstWithId(EscherDgRecord.RECORD_ID); - EscherDgRecord dg2 = (EscherDgRecord)sheet2.getDrawingEscherAggregate().findFirstWithId(EscherDgRecord.RECORD_ID); + EscherDgRecord dg1 = (EscherDgRecord)sheet1.getDrawingPatriarch()._getBoundAggregate().findFirstWithId(EscherDgRecord.RECORD_ID); + EscherDgRecord dg2 = (EscherDgRecord)sheet2.getDrawingPatriarch()._getBoundAggregate().findFirstWithId(EscherDgRecord.RECORD_ID); int dg_id_1 = dg1.getOptions() >> 4; int dg_id_2 = dg2.getOptions() >> 4; assertEquals(dg_id_1 + 1, dg_id_2); diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestPatriarch.java b/src/testcases/org/apache/poi/hssf/usermodel/TestPatriarch.java new file mode 100644 index 000000000..d9eba84bd --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestPatriarch.java @@ -0,0 +1,57 @@ +/* ==================================================================== + 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 org.apache.poi.ddf.EscherDgRecord; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.EscherAggregate; + +/** + * @author Evgeniy Berlog + * @date 01.08.12 + */ +public class TestPatriarch extends TestCase { + + public void testGetPatriarch(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + assertNull(sh.getDrawingPatriarch()); + + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + assertNotNull(patriarch); + patriarch.createSimpleShape(new HSSFClientAnchor()); + patriarch.createSimpleShape(new HSSFClientAnchor()); + + assertSame(patriarch, sh.getDrawingPatriarch()); + + EscherAggregate agg = patriarch._getBoundAggregate(); + + EscherDgRecord dg = agg.getEscherContainer().getChildById(EscherDgRecord.RECORD_ID); + int lastId = dg.getLastMSOSPID(); + + assertSame(patriarch, sh.createDrawingPatriarch()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.createDrawingPatriarch(); + dg = patriarch._getBoundAggregate().getEscherContainer().getChildById(EscherDgRecord.RECORD_ID); + + assertEquals(lastId, dg.getLastMSOSPID()); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestPolygon.java b/src/testcases/org/apache/poi/hssf/usermodel/TestPolygon.java new file mode 100644 index 000000000..e677f97ec --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestPolygon.java @@ -0,0 +1,248 @@ +/* ==================================================================== + 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 org.apache.poi.ddf.EscherArrayProperty; +import org.apache.poi.ddf.EscherOptRecord; +import org.apache.poi.ddf.EscherProperties; +import org.apache.poi.ddf.EscherSpRecord; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.model.HSSFTestModelHelper; +import org.apache.poi.hssf.model.PolygonShape; +import org.apache.poi.hssf.record.ObjRecord; + +import java.io.IOException; +import java.util.Arrays; + +/** + * @author Evgeniy Berlog + * @date 28.06.12 + */ +public class TestPolygon extends TestCase{ + + public void testResultEqualsToAbstractShape() throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 100, 100 ); + polygon.setPoints( new int[]{0, 90, 50}, new int[]{5, 5, 44} ); + PolygonShape polygonShape = HSSFTestModelHelper.createPolygonShape(1024, polygon); + polygon.setShapeId(1024); + + assertEquals(polygon.getEscherContainer().getChildRecords().size(), 4); + assertEquals(polygonShape.getSpContainer().getChildRecords().size(), 4); + + //sp record + byte[] expected = polygonShape.getSpContainer().getChild(0).serialize(); + byte[] actual = polygon.getEscherContainer().getChild(0).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = polygonShape.getSpContainer().getChild(2).serialize(); + actual = polygon.getEscherContainer().getChild(2).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = polygonShape.getSpContainer().getChild(3).serialize(); + actual = polygon.getEscherContainer().getChild(3).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + ObjRecord obj = polygon.getObjRecord(); + ObjRecord objShape = polygonShape.getObjRecord(); + + expected = obj.serialize(); + actual = objShape.serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + } + + public void testPolygonPoints(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 100, 100 ); + polygon.setPoints( new int[]{0, 90, 50, 90}, new int[]{5, 5, 44, 88} ); + + PolygonShape polygonShape = HSSFTestModelHelper.createPolygonShape(0, polygon); + + EscherArrayProperty verticesProp1 = polygon.getOptRecord().lookup(EscherProperties.GEOMETRY__VERTICES); + EscherArrayProperty verticesProp2 = ((EscherOptRecord)polygonShape.getSpContainer().getChildById(EscherOptRecord.RECORD_ID)) + .lookup(EscherProperties.GEOMETRY__VERTICES); + + assertEquals(verticesProp1.getNumberOfElementsInArray(), verticesProp2.getNumberOfElementsInArray()); + assertEquals(verticesProp1.toXml(""), verticesProp2.toXml("")); + + polygon.setPoints(new int[]{1,2,3}, new int[] {4,5,6}); + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{1, 2, 3})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{4, 5, 6})); + + polygonShape = HSSFTestModelHelper.createPolygonShape(0, polygon); + verticesProp1 = polygon.getOptRecord().lookup(EscherProperties.GEOMETRY__VERTICES); + verticesProp2 = ((EscherOptRecord)polygonShape.getSpContainer().getChildById(EscherOptRecord.RECORD_ID)) + .lookup(EscherProperties.GEOMETRY__VERTICES); + + assertEquals(verticesProp1.getNumberOfElementsInArray(), verticesProp2.getNumberOfElementsInArray()); + assertEquals(verticesProp1.toXml(""), verticesProp2.toXml("")); + } + + public void testSetGetProperties(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 102, 101 ); + polygon.setPoints( new int[]{1,2,3}, new int[]{4,5,6} ); + + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{1,2,3})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{4, 5, 6})); + assertEquals(polygon.getDrawAreaHeight(), 101); + assertEquals(polygon.getDrawAreaWidth(), 102); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + polygon = (HSSFPolygon) patriarch.getChildren().get(0); + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{1, 2, 3})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{4, 5, 6})); + assertEquals(polygon.getDrawAreaHeight(), 101); + assertEquals(polygon.getDrawAreaWidth(), 102); + + polygon.setPolygonDrawArea( 1021, 1011 ); + polygon.setPoints( new int[]{11,21,31}, new int[]{41,51,61} ); + + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{11, 21, 31})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{41, 51, 61})); + assertEquals(polygon.getDrawAreaHeight(), 1011); + assertEquals(polygon.getDrawAreaWidth(), 1021); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + polygon = (HSSFPolygon) patriarch.getChildren().get(0); + + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{11, 21, 31})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{41, 51, 61})); + assertEquals(polygon.getDrawAreaHeight(), 1011); + assertEquals(polygon.getDrawAreaWidth(), 1021); + } + + public void testAddToExistingFile(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 102, 101 ); + polygon.setPoints( new int[]{1,2,3}, new int[]{4,5,6} ); + + HSSFPolygon polygon1 = patriarch.createPolygon(new HSSFClientAnchor()); + polygon1.setPolygonDrawArea( 103, 104 ); + polygon1.setPoints( new int[]{11,12,13}, new int[]{14,15,16} ); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 2); + + HSSFPolygon polygon2 = patriarch.createPolygon(new HSSFClientAnchor()); + polygon2.setPolygonDrawArea( 203, 204 ); + polygon2.setPoints( new int[]{21,22,23}, new int[]{24,25,26} ); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 3); + + polygon = (HSSFPolygon) patriarch.getChildren().get(0); + polygon1 = (HSSFPolygon) patriarch.getChildren().get(1); + polygon2 = (HSSFPolygon) patriarch.getChildren().get(2); + + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{1, 2, 3})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{4,5,6})); + assertEquals(polygon.getDrawAreaHeight(), 101); + assertEquals(polygon.getDrawAreaWidth(), 102); + + assertTrue(Arrays.equals(polygon1.getXPoints(), new int[]{11,12,13})); + assertTrue(Arrays.equals(polygon1.getYPoints(), new int[]{14,15,16})); + assertEquals(polygon1.getDrawAreaHeight(), 104); + assertEquals(polygon1.getDrawAreaWidth(), 103); + + assertTrue(Arrays.equals(polygon2.getXPoints(), new int[]{21,22,23})); + assertTrue(Arrays.equals(polygon2.getYPoints(), new int[]{24,25,26})); + assertEquals(polygon2.getDrawAreaHeight(), 204); + assertEquals(polygon2.getDrawAreaWidth(), 203); + } + + public void testExistingFile() throws IOException { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("polygon"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + + HSSFPolygon polygon = (HSSFPolygon) drawing.getChildren().get(0); + assertEquals(polygon.getDrawAreaHeight(), 2466975); + assertEquals(polygon.getDrawAreaWidth(), 3686175); + assertTrue(Arrays.equals(polygon.getXPoints(), new int[]{0, 0, 31479, 16159, 19676, 20502})); + assertTrue(Arrays.equals(polygon.getYPoints(), new int[]{0, 0, 36, 56, 34, 18})); + } + + public void testPolygonType(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + + HSSFPolygon polygon = patriarch.createPolygon(new HSSFClientAnchor()); + polygon.setPolygonDrawArea( 102, 101 ); + polygon.setPoints( new int[]{1,2,3}, new int[]{4,5,6} ); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + HSSFPolygon polygon1 = patriarch.createPolygon(new HSSFClientAnchor()); + polygon1.setPolygonDrawArea( 102, 101 ); + polygon1.setPoints( new int[]{1,2,3}, new int[]{4,5,6} ); + + EscherSpRecord spRecord = polygon1.getEscherContainer().getChildById(EscherSpRecord.RECORD_ID); + + spRecord.setShapeType((short)77/**RANDOM**/); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 2); + assertTrue(patriarch.getChildren().get(0) instanceof HSSFPolygon); + assertTrue(patriarch.getChildren().get(1) instanceof HSSFPolygon); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestShapeGroup.java b/src/testcases/org/apache/poi/hssf/usermodel/TestShapeGroup.java new file mode 100644 index 000000000..7b78e21ed --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestShapeGroup.java @@ -0,0 +1,279 @@ +/* ==================================================================== + 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 org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherSpgrRecord; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.ObjRecord; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * @author Evgeniy Berlog + * @date 29.06.12 + */ +public class TestShapeGroup extends TestCase{ + + public void testSetGetCoordinates(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFShapeGroup group = patriarch.createGroup(new HSSFClientAnchor()); + assertEquals(group.getX1(), 0); + assertEquals(group.getY1(), 0); + assertEquals(group.getX2(), 1023); + assertEquals(group.getY2(), 255); + + group.setCoordinates(1,2,3,4); + + assertEquals(group.getX1(), 1); + assertEquals(group.getY1(), 2); + assertEquals(group.getX2(), 3); + assertEquals(group.getY2(), 4); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + group = (HSSFShapeGroup) patriarch.getChildren().get(0); + assertEquals(group.getX1(), 1); + assertEquals(group.getY1(), 2); + assertEquals(group.getX2(), 3); + assertEquals(group.getY2(), 4); + } + + public void testAddToExistingFile(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFShapeGroup group1 = patriarch.createGroup(new HSSFClientAnchor()); + HSSFShapeGroup group2 = patriarch.createGroup(new HSSFClientAnchor()); + + group1.setCoordinates(1,2,3,4); + group2.setCoordinates(5,6,7,8); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 2); + + HSSFShapeGroup group3 = patriarch.createGroup(new HSSFClientAnchor()); + group3.setCoordinates(9,10,11,12); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 3); + } + + public void testModify() throws Exception { + HSSFWorkbook wb = new HSSFWorkbook(); + + // create a sheet with a text box + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFShapeGroup group1 = patriarch.createGroup(new + HSSFClientAnchor(0,0,0,0, + (short)0, 0, (short)15, 25)); + group1.setCoordinates(0, 0, 792, 612); + + HSSFTextbox textbox1 = group1.createTextbox(new + HSSFChildAnchor(100, 100, 300, 300)); + HSSFRichTextString rt1 = new HSSFRichTextString("Hello, World!"); + textbox1.setString(rt1); + + // write, read back and check that our text box is there + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + assertEquals(1, patriarch.getChildren().size()); + + group1 = (HSSFShapeGroup)patriarch.getChildren().get(0); + assertEquals(1, group1.getChildren().size()); + textbox1 = (HSSFTextbox)group1.getChildren().get(0); + assertEquals("Hello, World!", textbox1.getString().getString()); + + // modify anchor + assertEquals(new HSSFChildAnchor(100, 100, 300, 300), + textbox1.getAnchor()); + HSSFChildAnchor newAnchor = new HSSFChildAnchor(200,200, 400, 400); + textbox1.setAnchor(newAnchor); + // modify text + textbox1.setString(new HSSFRichTextString("Hello, World! (modified)")); + + // add a new text box + HSSFTextbox textbox2 = group1.createTextbox(new + HSSFChildAnchor(400, 400, 600, 600)); + HSSFRichTextString rt2 = new HSSFRichTextString("Hello, World-2"); + textbox2.setString(rt2); + assertEquals(2, group1.getChildren().size()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + assertEquals(1, patriarch.getChildren().size()); + + group1 = (HSSFShapeGroup)patriarch.getChildren().get(0); + assertEquals(2, group1.getChildren().size()); + textbox1 = (HSSFTextbox)group1.getChildren().get(0); + assertEquals("Hello, World! (modified)", + textbox1.getString().getString()); + assertEquals(new HSSFChildAnchor(200,200, 400, 400), + textbox1.getAnchor()); + + textbox2 = (HSSFTextbox)group1.getChildren().get(1); + assertEquals("Hello, World-2", textbox2.getString().getString()); + assertEquals(new HSSFChildAnchor(400, 400, 600, 600), + textbox2.getAnchor()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + group1 = (HSSFShapeGroup)patriarch.getChildren().get(0); + textbox1 = (HSSFTextbox)group1.getChildren().get(0); + textbox2 = (HSSFTextbox)group1.getChildren().get(1); + HSSFTextbox textbox3 = group1.createTextbox(new + HSSFChildAnchor(400,200, 600, 400)); + HSSFRichTextString rt3 = new HSSFRichTextString("Hello, World-3"); + textbox3.setString(rt3); + } + + public void testAddShapesToGroup(){ + HSSFWorkbook wb = new HSSFWorkbook(); + + // create a sheet with a text box + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFShapeGroup group = patriarch.createGroup(new HSSFClientAnchor()); + int index = wb.addPicture(new byte[]{1,2,3}, HSSFWorkbook.PICTURE_TYPE_JPEG); + group.createPicture(new HSSFChildAnchor(), index); + HSSFPolygon polygon = group.createPolygon(new HSSFChildAnchor()); + polygon.setPoints(new int[]{1,100, 1}, new int[]{1, 50, 100}); + group.createTextbox(new HSSFChildAnchor()); + group.createShape(new HSSFChildAnchor()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + assertEquals(1, patriarch.getChildren().size()); + + assertTrue(patriarch.getChildren().get(0) instanceof HSSFShapeGroup); + group = (HSSFShapeGroup) patriarch.getChildren().get(0); + + assertEquals(group.getChildren().size(), 4); + + assertTrue(group.getChildren().get(0) instanceof HSSFPicture); + assertTrue(group.getChildren().get(1) instanceof HSSFPolygon); + assertTrue(group.getChildren().get(2) instanceof HSSFTextbox); + assertTrue(group.getChildren().get(3) instanceof HSSFSimpleShape); + + HSSFShapeGroup group2 = patriarch.createGroup(new HSSFClientAnchor()); + + index = wb.addPicture(new byte[]{2,2,2}, HSSFWorkbook.PICTURE_TYPE_JPEG); + group2.createPicture(new HSSFChildAnchor(), index); + polygon = group2.createPolygon(new HSSFChildAnchor()); + polygon.setPoints(new int[]{1,100, 1}, new int[]{1, 50, 100}); + group2.createTextbox(new HSSFChildAnchor()); + group2.createShape(new HSSFChildAnchor()); + group2.createShape(new HSSFChildAnchor()); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + assertEquals(2, patriarch.getChildren().size()); + + group = (HSSFShapeGroup) patriarch.getChildren().get(1); + + assertEquals(group.getChildren().size(), 5); + + assertTrue(group.getChildren().get(0) instanceof HSSFPicture); + assertTrue(group.getChildren().get(1) instanceof HSSFPolygon); + assertTrue(group.getChildren().get(2) instanceof HSSFTextbox); + assertTrue(group.getChildren().get(3) instanceof HSSFSimpleShape); + assertTrue(group.getChildren().get(4) instanceof HSSFSimpleShape); + + group.getShapeId(); + } + + public void testSpgrRecord(){ + HSSFWorkbook wb = new HSSFWorkbook(); + + // create a sheet with a text box + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + + HSSFShapeGroup group = patriarch.createGroup(new HSSFClientAnchor()); + assertSame(((EscherContainerRecord)group.getEscherContainer().getChild(0)).getChildById(EscherSpgrRecord.RECORD_ID), getSpgrRecord(group)); + } + + private static EscherSpgrRecord getSpgrRecord(HSSFShapeGroup group) { + Field spgrField = null; + try { + spgrField = group.getClass().getDeclaredField("_spgrRecord"); + spgrField.setAccessible(true); + return (EscherSpgrRecord) spgrField.get(group); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return null; + } + + public void testClearShapes(){ + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet(); + HSSFPatriarch patriarch = sheet.createDrawingPatriarch(); + HSSFShapeGroup group = patriarch.createGroup(new HSSFClientAnchor()); + + group.createShape(new HSSFChildAnchor()); + group.createShape(new HSSFChildAnchor()); + + EscherAggregate agg = HSSFTestHelper.getEscherAggregate(patriarch); + + assertEquals(agg.getShapeToObjMapping().size(), 5); + assertEquals(agg.getTailRecords().size(), 0); + assertEquals(group.getChildren().size(), 2); + + group.clear(); + + assertEquals(agg.getShapeToObjMapping().size(), 1); + assertEquals(agg.getTailRecords().size(), 0); + assertEquals(group.getChildren().size(), 0); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sheet = wb.getSheetAt(0); + patriarch = sheet.getDrawingPatriarch(); + + group = (HSSFShapeGroup) patriarch.getChildren().get(0); + + assertEquals(agg.getShapeToObjMapping().size(), 1); + assertEquals(agg.getTailRecords().size(), 0); + assertEquals(group.getChildren().size(), 0); + } +} diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestText.java b/src/testcases/org/apache/poi/hssf/usermodel/TestText.java new file mode 100644 index 000000000..1bbf69f9d --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestText.java @@ -0,0 +1,199 @@ +/* ==================================================================== + 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 org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.model.HSSFTestModelHelper; +import org.apache.poi.hssf.model.TextboxShape; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +import org.apache.poi.hssf.record.ObjRecord; +import org.apache.poi.hssf.record.TextObjectRecord; + +import java.util.Arrays; + +/** + * @author Evgeniy Berlog + * @date 25.06.12 + */ +public class TestText extends TestCase { + + public void testResultEqualsToAbstractShape() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor()); + TextboxShape textboxShape = HSSFTestModelHelper.createTextboxShape(1025, textbox); + + assertEquals(textbox.getEscherContainer().getChildRecords().size(), 5); + assertEquals(textboxShape.getSpContainer().getChildRecords().size(), 5); + + //sp record + byte[] expected = textboxShape.getSpContainer().getChild(0).serialize(); + byte[] actual = textbox.getEscherContainer().getChild(0).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = textboxShape.getSpContainer().getChild(2).serialize(); + actual = textbox.getEscherContainer().getChild(2).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = textboxShape.getSpContainer().getChild(3).serialize(); + actual = textbox.getEscherContainer().getChild(3).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + expected = textboxShape.getSpContainer().getChild(4).serialize(); + actual = textbox.getEscherContainer().getChild(4).serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + + ObjRecord obj = textbox.getObjRecord(); + ObjRecord objShape = textboxShape.getObjRecord(); + + expected = obj.serialize(); + actual = objShape.serialize(); + + TextObjectRecord tor = textbox.getTextObjectRecord(); + TextObjectRecord torShape = textboxShape.getTextObjectRecord(); + + expected = tor.serialize(); + actual = torShape.serialize(); + + assertEquals(expected.length, actual.length); + assertTrue(Arrays.equals(expected, actual)); + } + + public void testAddTextToExistingFile() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor()); + textbox.setString(new HSSFRichTextString("just for test")); + HSSFTextbox textbox2 = patriarch.createTextbox(new HSSFClientAnchor()); + textbox2.setString(new HSSFRichTextString("just for test2")); + + assertEquals(patriarch.getChildren().size(), 2); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 2); + HSSFTextbox text3 = patriarch.createTextbox(new HSSFClientAnchor()); + text3.setString(new HSSFRichTextString("text3")); + assertEquals(patriarch.getChildren().size(), 3); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + + assertEquals(patriarch.getChildren().size(), 3); + assertEquals(((HSSFTextbox) patriarch.getChildren().get(0)).getString().getString(), "just for test"); + assertEquals(((HSSFTextbox) patriarch.getChildren().get(1)).getString().getString(), "just for test2"); + assertEquals(((HSSFTextbox) patriarch.getChildren().get(2)).getString().getString(), "text3"); + } + + public void testSetGetProperties() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sh = wb.createSheet(); + HSSFPatriarch patriarch = sh.createDrawingPatriarch(); + HSSFTextbox textbox = patriarch.createTextbox(new HSSFClientAnchor()); + textbox.setString(new HSSFRichTextString("test")); + assertEquals(textbox.getString().getString(), "test"); + + textbox.setHorizontalAlignment((short) 5); + assertEquals(textbox.getHorizontalAlignment(), 5); + + textbox.setVerticalAlignment((short) 6); + assertEquals(textbox.getVerticalAlignment(), (short) 6); + + textbox.setMarginBottom(7); + assertEquals(textbox.getMarginBottom(), 7); + + textbox.setMarginLeft(8); + assertEquals(textbox.getMarginLeft(), 8); + + textbox.setMarginRight(9); + assertEquals(textbox.getMarginRight(), 9); + + textbox.setMarginTop(10); + assertEquals(textbox.getMarginTop(), 10); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + textbox = (HSSFTextbox) patriarch.getChildren().get(0); + assertEquals(textbox.getString().getString(), "test"); + assertEquals(textbox.getHorizontalAlignment(), 5); + assertEquals(textbox.getVerticalAlignment(), (short) 6); + assertEquals(textbox.getMarginBottom(), 7); + assertEquals(textbox.getMarginLeft(), 8); + assertEquals(textbox.getMarginRight(), 9); + assertEquals(textbox.getMarginTop(), 10); + + textbox.setString(new HSSFRichTextString("test1")); + textbox.setHorizontalAlignment(HSSFTextbox.HORIZONTAL_ALIGNMENT_CENTERED); + textbox.setVerticalAlignment(HSSFTextbox.VERTICAL_ALIGNMENT_TOP); + textbox.setMarginBottom(71); + textbox.setMarginLeft(81); + textbox.setMarginRight(91); + textbox.setMarginTop(101); + + assertEquals(textbox.getString().getString(), "test1"); + assertEquals(textbox.getHorizontalAlignment(), HSSFTextbox.HORIZONTAL_ALIGNMENT_CENTERED); + assertEquals(textbox.getVerticalAlignment(), HSSFTextbox.VERTICAL_ALIGNMENT_TOP); + assertEquals(textbox.getMarginBottom(), 71); + assertEquals(textbox.getMarginLeft(), 81); + assertEquals(textbox.getMarginRight(), 91); + assertEquals(textbox.getMarginTop(), 101); + + wb = HSSFTestDataSamples.writeOutAndReadBack(wb); + sh = wb.getSheetAt(0); + patriarch = sh.getDrawingPatriarch(); + textbox = (HSSFTextbox) patriarch.getChildren().get(0); + + assertEquals(textbox.getString().getString(), "test1"); + assertEquals(textbox.getHorizontalAlignment(), HSSFTextbox.HORIZONTAL_ALIGNMENT_CENTERED); + assertEquals(textbox.getVerticalAlignment(), HSSFTextbox.VERTICAL_ALIGNMENT_TOP); + assertEquals(textbox.getMarginBottom(), 71); + assertEquals(textbox.getMarginLeft(), 81); + assertEquals(textbox.getMarginRight(), 91); + assertEquals(textbox.getMarginTop(), 101); + } + + public void testExistingFileWithText(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("drawings.xls"); + HSSFSheet sheet = wb.getSheet("text"); + HSSFPatriarch drawing = sheet.getDrawingPatriarch(); + assertEquals(1, drawing.getChildren().size()); + HSSFTextbox textbox = (HSSFTextbox) drawing.getChildren().get(0); + assertEquals(textbox.getHorizontalAlignment(), HSSFTextbox.HORIZONTAL_ALIGNMENT_LEFT); + assertEquals(textbox.getVerticalAlignment(), HSSFTextbox.VERTICAL_ALIGNMENT_TOP); + assertEquals(textbox.getMarginTop(), 0); + assertEquals(textbox.getMarginBottom(), 3600000); + assertEquals(textbox.getMarginLeft(), 3600000); + assertEquals(textbox.getMarginRight(), 0); + assertEquals(textbox.getString().getString(), "teeeeesssstttt"); + } +} diff --git a/test-data/spreadsheet/45129.xls b/test-data/spreadsheet/45129.xls new file mode 100755 index 000000000..c3c3a8af2 Binary files /dev/null and b/test-data/spreadsheet/45129.xls differ diff --git a/test-data/spreadsheet/51675.xls b/test-data/spreadsheet/51675.xls new file mode 100644 index 000000000..78df32cb9 Binary files /dev/null and b/test-data/spreadsheet/51675.xls differ diff --git a/test-data/spreadsheet/DrawingAndComments.xls b/test-data/spreadsheet/DrawingAndComments.xls new file mode 100755 index 000000000..8be6c9e52 Binary files /dev/null and b/test-data/spreadsheet/DrawingAndComments.xls differ diff --git a/test-data/spreadsheet/DrawingContinue.xls b/test-data/spreadsheet/DrawingContinue.xls new file mode 100755 index 000000000..131c18cc9 Binary files /dev/null and b/test-data/spreadsheet/DrawingContinue.xls differ diff --git a/test-data/spreadsheet/SolverContainerAfterSPGR.xls b/test-data/spreadsheet/SolverContainerAfterSPGR.xls new file mode 100755 index 000000000..57a1b1704 Binary files /dev/null and b/test-data/spreadsheet/SolverContainerAfterSPGR.xls differ diff --git a/test-data/spreadsheet/dg-text.xls b/test-data/spreadsheet/dg-text.xls new file mode 100755 index 000000000..5fd624507 Binary files /dev/null and b/test-data/spreadsheet/dg-text.xls differ diff --git a/test-data/spreadsheet/drawings.xls b/test-data/spreadsheet/drawings.xls new file mode 100644 index 000000000..69fb3af3d Binary files /dev/null and b/test-data/spreadsheet/drawings.xls differ diff --git a/test-data/spreadsheet/empty.xls b/test-data/spreadsheet/empty.xls new file mode 100644 index 000000000..97732d48d Binary files /dev/null and b/test-data/spreadsheet/empty.xls differ diff --git a/test-data/spreadsheet/text.xls b/test-data/spreadsheet/text.xls new file mode 100644 index 000000000..18df2914a Binary files /dev/null and b/test-data/spreadsheet/text.xls differ