From 5de1aec65e9c64f670e2acd205a2fd6def5fff62 Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Fri, 8 Jun 2012 17:47:37 +0000 Subject: [PATCH] initial support for reading shapes, see patch from June 8 in Bugzilla 53372 git-svn-id: https://svn.apache.org/repos/asf/poi/branches/gsoc2012@1348168 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/hssf/record/EscherAggregate.java | 50 ++++-- .../poi/hssf/usermodel/HSSFChildAnchor.java | 9 + .../poi/hssf/usermodel/HSSFClientAnchor.java | 8 + .../poi/hssf/usermodel/HSSFPatriarch.java | 21 +++ .../poi/hssf/usermodel/HSSFRectangle.java | 15 ++ .../apache/poi/hssf/usermodel/HSSFShape.java | 22 ++- .../hssf/usermodel/HSSFShapeContainer.java | 11 ++ .../poi/hssf/usermodel/HSSFShapeFactory.java | 157 ++++++++++++++++++ .../poi/hssf/usermodel/HSSFShapeGroup.java | 40 +++++ .../apache/poi/hssf/usermodel/HSSFSheet.java | 8 +- .../poi/hssf/usermodel/HSSFUnknownShape.java | 33 ++++ .../hssf/usermodel/drawing/HSSFShapeType.java | 29 ++++ .../poi/hssf/model/TestDrawingAggregate.java | 110 +++++++++--- .../poi/hssf/usermodel/HSSFTestHelper.java | 5 + .../spreadsheet/SolverContainerAfterSPGR.xls | Bin 0 -> 26112 bytes 15 files changed, 476 insertions(+), 42 deletions(-) create mode 100644 src/java/org/apache/poi/hssf/usermodel/HSSFRectangle.java create mode 100644 src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java create mode 100644 src/java/org/apache/poi/hssf/usermodel/HSSFUnknownShape.java create mode 100644 src/java/org/apache/poi/hssf/usermodel/drawing/HSSFShapeType.java create mode 100755 test-data/spreadsheet/SolverContainerAfterSPGR.xls diff --git a/src/java/org/apache/poi/hssf/record/EscherAggregate.java b/src/java/org/apache/poi/hssf/record/EscherAggregate.java index 3bb712735..1d48ff751 100644 --- a/src/java/org/apache/poi/hssf/record/EscherAggregate.java +++ b/src/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -43,16 +43,7 @@ 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; @@ -322,7 +313,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { /** * list of "tail" records that need to be serialized after all drawing group records */ - private List tailRec = new ArrayList(); + private List tailRec = new ArrayList(); public EscherAggregate(DrawingManager2 drawingManager) { this.drawingManager = drawingManager; @@ -413,7 +404,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { } // Decode the shapes - // agg.escherRecords = new ArrayList(); + // agg.escherRecords = new ArrayList(); int pos = 0; while (pos < buffer.size()) { EscherRecord r = recordFactory.createRecord(buffer.toByteArray(), pos); @@ -486,7 +477,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { 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)); + spEndingOffsets.add(offset); shapes.add(record); } } @@ -501,12 +492,17 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { pos = offset; int writtenEscherBytes = 0; for (int i = 1; i < shapes.size(); i++) { - int endOffset = ((Integer) spEndingOffsets.get(i)).intValue() - 1; + int endOffset; + if (i == shapes.size()-1){ + endOffset = buffer.length - 1; + } else { + endOffset = (Integer) spEndingOffsets.get(i) - 1; + } int startOffset; if (i == 1) startOffset = 0; else - startOffset = ((Integer) spEndingOffsets.get(i - 1)).intValue(); + startOffset = (Integer) spEndingOffsets.get(i - 1); byte[] drawingData = new byte[endOffset - startOffset + 1]; @@ -790,7 +786,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { container.getChildren().add(shape); } - private static HSSFClientAnchor toClientAnchor(EscherClientAnchorRecord anchorRecord) { + public static HSSFClientAnchor toClientAnchor(EscherClientAnchorRecord anchorRecord) { HSSFClientAnchor anchor = new HSSFClientAnchor(); anchor.setAnchorType(anchorRecord.getFlag()); anchor.setCol1(anchorRecord.getCol1()); @@ -804,7 +800,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { return anchor; } - private static HSSFChildAnchor toChildAnchor(EscherChildAnchorRecord anchorRecord) { + public static HSSFChildAnchor toChildAnchor(EscherChildAnchorRecord anchorRecord) { HSSFChildAnchor anchor = new HSSFChildAnchor(); // anchor.setAnchorType(anchorRecord.getFlag()); // anchor.setCol1( anchorRecord.getCol1() ); @@ -1081,4 +1077,24 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { return null; } + /** + * Returns 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 + * + * @return + */ + public Map getShapeToObjMapping(){ + return Collections.unmodifiableMap(shapeToObj); + } + + /** + * + * @return tails records. We need to access them when building shapes. + * Every HSSFComment shape has a link to a NoteRecord from the tailRec collection. + */ + public List getTailRecords(){ + return Collections.unmodifiableList(tailRec); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java index 513ac619f..ccd0e620c 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFChildAnchor.java @@ -18,7 +18,16 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherChildAnchorRecord; + public final class HSSFChildAnchor extends HSSFAnchor { + + private EscherChildAnchorRecord escherChildAnchorRecord; + + public HSSFChildAnchor(EscherChildAnchorRecord escherChildAnchorRecord) { + this.escherChildAnchorRecord = escherChildAnchorRecord; + } + public HSSFChildAnchor() { } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java b/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java index cffb00cd5..a040144c2 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFClientAnchor.java @@ -17,6 +17,7 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherClientAnchorRecord; import org.apache.poi.ss.usermodel.ClientAnchor; @@ -33,6 +34,13 @@ public final class HSSFClientAnchor extends HSSFAnchor implements ClientAnchor { int row2; int anchorType; + private EscherClientAnchorRecord escherClientAnchorRecord; + + public HSSFClientAnchor(EscherClientAnchorRecord escherClientAnchorRecord) { + this.escherClientAnchorRecord = escherClientAnchorRecord; + //TODO set properties or read properties from EscherRecord ? + } + /** * Creates a new client anchor and defaults all the anchor positions to 0. */ diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index 672f82cc7..71549bbf3 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -22,9 +22,11 @@ import java.util.Iterator; import java.util.List; import org.apache.poi.ddf.EscherComplexProperty; +import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherOptRecord; import org.apache.poi.ddf.EscherProperty; import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.ddf.EscherSpgrRecord; import org.apache.poi.hssf.record.EscherAggregate; import org.apache.poi.ss.usermodel.Chart; import org.apache.poi.util.StringUtil; @@ -314,4 +316,23 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { throw new RuntimeException("NotImplemented"); } + + void buildShapeTree(){ + EscherContainerRecord dgContainer = _boundAggregate.getEscherContainer(); + 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){ + EscherSpgrRecord spgr = (EscherSpgrRecord)spContainer.getChildById(EscherSpgrRecord.RECORD_ID); + setCoordinates( + spgr.getRectX1(), spgr.getRectY1(), + spgr.getRectX2(), spgr.getRectY2() + ); + } else { + HSSFShapeFactory.createShapeTree(spContainer, _boundAggregate, this); + } + } + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRectangle.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRectangle.java new file mode 100644 index 000000000..0a0468c14 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRectangle.java @@ -0,0 +1,15 @@ +package org.apache.poi.hssf.usermodel; + +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.hssf.record.ObjRecord; + +/** + * @author Evgeniy Berlog + * @date 08.06.12 + */ +public class HSSFRectangle extends HSSFShape{ + + public HSSFRectangle(EscherContainerRecord spContainer, ObjRecord objRecord) { + super(spContainer, objRecord); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java index 3c586340f..7ffdfdb9b 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShape.java @@ -17,6 +17,9 @@ package org.apache.poi.hssf.usermodel; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.hssf.record.ObjRecord; + /** * An abstract shape. * @@ -40,7 +43,7 @@ public abstract class HSSFShape { public static final int LINESTYLE_NONE = -1; // TODO - make all these fields private - final HSSFShape parent; + HSSFShape parent; HSSFAnchor anchor; HSSFPatriarch _patriarch; private int _lineStyleColor = 0x08000040; @@ -49,15 +52,30 @@ public abstract class HSSFShape { private int _lineStyle = LINESTYLE_SOLID; private boolean _noFill = false; + private EscherContainerRecord spContainer; + private ObjRecord objRecord; + + public HSSFShape(EscherContainerRecord spContainer, ObjRecord objRecord){ + this.spContainer = spContainer; + this.objRecord = objRecord; + } /** * 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; } + public EscherContainerRecord getSpContainer() { + return spContainer; + } + + public ObjRecord getObjRecord() { + return objRecord; + } + /** * Gets the parent shape. */ diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java index 99e6a5de6..181b3d17a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeContainer.java @@ -31,4 +31,15 @@ public interface HSSFShapeContainer */ 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 ); + } 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..fc88a2a16 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeFactory.java @@ -0,0 +1,157 @@ +/* + * 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.EscherClientDataRecord; +import org.apache.poi.ddf.EscherContainerRecord; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.ddf.EscherSpRecord; +import org.apache.poi.ddf.EscherSpgrRecord; +import org.apache.poi.ddf.EscherTextboxRecord; +import org.apache.poi.hssf.model.TextboxShape; +import org.apache.poi.hssf.record.CommonObjectDataSubRecord; +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.TextObjectRecord; +import org.apache.poi.hssf.usermodel.drawing.HSSFShapeType; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author evgeniy + * date: 05.06.12 + */ +public class HSSFShapeFactory { + + private static final Map shapeTypeToClass = new HashMap(HSSFShapeType.values().length); + private static final ReflectionConstructorShapeCreator shapeCreator = new ReflectionConstructorShapeCreator(shapeTypeToClass); + + static { + for (HSSFShapeType type: HSSFShapeType.values()){ + shapeTypeToClass.put(type.getType(), type.getShape()); + } + } + + private static class ReflectionConstructorShapeCreator { + + private final Map shapeTypeToClass; + + private ReflectionConstructorShapeCreator(Map shapeTypeToClass) { + this.shapeTypeToClass = shapeTypeToClass; + } + + public HSSFShape createNewShape(Short type, EscherContainerRecord spContainer, ObjRecord objRecord){ + if (!shapeTypeToClass.containsKey(type)){ + return new HSSFUnknownShape(spContainer, objRecord); + } + Class clazz = shapeTypeToClass.get(type); + if (null == clazz){ + System.out.println("No class attached to shape type: "+type); + return new HSSFUnknownShape(spContainer, objRecord); + } + try{ + Constructor constructor = clazz.getConstructor(new Class[]{EscherContainerRecord.class, ObjRecord.class}); + return (HSSFShape) constructor.newInstance(spContainer, objRecord); + } catch (NoSuchMethodException e) { + throw new IllegalStateException(clazz.getName() +" doesn't have required for shapes constructor"); + } catch (Exception e) { + throw new IllegalStateException("Couldn't create new instance of " + clazz.getName()); + } + } + } + + public static HSSFShape createShape(EscherRecord container, ObjRecord objRecord){ + if (0 == container.getChildRecords().size()){ + throw new IllegalArgumentException("Couldn't create shape from empty escher container"); + } + if (container.getChild(0) instanceof EscherSpgrRecord){ + return new HSSFShapeGroup((EscherContainerRecord) container, objRecord); + } + + //TODO implement cases for all shapes + return new HSSFUnknownShape(container, objRecord); + } + + public static HSSFShapeGroup createShapeGroup(){ + return null; + } + + public static HSSFShapeGroup createSimpleShape(EscherRecord container, ObjRecord objRecord){ + return null; + } + + public static void createShapeTree(EscherContainerRecord container, EscherAggregate agg, HSSFShapeContainer out){ + if(container.getRecordId() == EscherContainerRecord.SPGR_CONTAINER){ + HSSFShapeGroup group = new HSSFShapeGroup(container, + null /* shape containers don't have a associated Obj record*/); + 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){ + EscherSpgrRecord spgr = (EscherSpgrRecord)spContainer.getChildById(EscherSpgrRecord.RECORD_ID); + group.setCoordinates( + spgr.getRectX1(), spgr.getRectY1(), + spgr.getRectX2(), spgr.getRectY2() + ); + } else { + createShapeTree(spContainer, agg, group); + } + } + out.addShape(group); + } else if (container.getRecordId() == EscherContainerRecord.SP_CONTAINER){ + Map shapeToObj = agg.getShapeToObjMapping(); + EscherSpRecord spRecord = null; + ObjRecord objRecord = null; + TextObjectRecord txtRecord = null; + + for(EscherRecord record : container.getChildRecords()) { + switch(record.getRecordId()) { + case EscherSpRecord.RECORD_ID: + spRecord = (EscherSpRecord)record; + break; + case EscherClientDataRecord.RECORD_ID: + objRecord = (ObjRecord)shapeToObj.get(record); + break; + case EscherTextboxRecord.RECORD_ID: + txtRecord = (TextObjectRecord)shapeToObj.get(record); + break; + } + } + if (null != objRecord){ + HSSFShape shape = shapeCreator.createNewShape(spRecord.getShapeType(), container, objRecord); + out.addShape(shape); + } + if (null != txtRecord){ + //TODO resolve textbox +// TextboxShape shape = new TextboxShape(container, txtRecord); +// out.a + } +// +// //TODO decide what shape to create based on ObjRecord / EscherSpRecord +// HSSFShape shape = new HSSFUnknownShape(container, objRecord); +// out.addShape(shape); + } + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java index 905dad3fc..7cab3e493 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFShapeGroup.java @@ -17,6 +17,15 @@ 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; +import org.apache.poi.ddf.EscherSpgrRecord; +import org.apache.poi.hssf.model.TextboxShape; +import org.apache.poi.hssf.record.EscherAggregate; +import org.apache.poi.hssf.record.ObjRecord; + import java.util.ArrayList; import java.util.List; import java.util.Iterator; @@ -37,6 +46,32 @@ public class HSSFShapeGroup int x2 = 1023; int y2 = 255; + public HSSFShapeGroup(EscherContainerRecord spgrContainer, ObjRecord objRecord) { + super(spgrContainer, objRecord); + + // read internal and external coordinates from spgrContainer + EscherContainerRecord spContainer = spgrContainer.getChildContainers().get(0); + for(EscherRecord ch : spContainer.getChildRecords()){ + switch(ch.getRecordId()) { + case EscherSpgrRecord.RECORD_ID: + EscherSpgrRecord spgr = (EscherSpgrRecord)ch; + setCoordinates( + spgr.getRectX1(), spgr.getRectY1(), + spgr.getRectX2(), spgr.getRectY2() + ); + break; + case EscherClientAnchorRecord.RECORD_ID: + this.anchor = EscherAggregate.toClientAnchor((EscherClientAnchorRecord)ch); + // TODO anchor = new HSSFClientAnchor((EscherChildAnchorRecord)ch); + break; + case EscherChildAnchorRecord.RECORD_ID: + this.anchor = EscherAggregate.toChildAnchor((EscherChildAnchorRecord)ch); + // TODO anchor = new HSSFChildAnchor((EscherClientAnchorRecord)ch); + break; + } + } + + } public HSSFShapeGroup( HSSFShape parent, HSSFAnchor anchor ) { @@ -61,6 +96,11 @@ public class HSSFShapeGroup shapes.add(shape); } + public void addTextBox(TextboxShape textboxShape){ +// HSSFTextbox shape = new HSSFTextbox(this, textboxShape.geanchor); +// shapes.add(textboxShape); + } + /** * Create a new simple shape under this group. * @param anchor the position of the shape. diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index fe332eb83..da7a404b2 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -1738,12 +1738,16 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { if(agg == null) return null; _patriarch = new HSSFPatriarch(this, agg); - agg.setPatriarch(_patriarch); + _patriarch.buildShapeTree(); + + //HSSFShapeFactory.createShapeTree(); + //agg.setPatriarch(_patriarch); + //EscherAggregate.createShapeTree(EscherAggregate.getMainSpgrContainer(agg), agg.getPatriarch(), agg); // 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(); +// agg.convertRecordsToUserModel(); // Return what we could cope with return _patriarch; diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFUnknownShape.java b/src/java/org/apache/poi/hssf/usermodel/HSSFUnknownShape.java new file mode 100644 index 000000000..d4cac9be9 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFUnknownShape.java @@ -0,0 +1,33 @@ +/* + * 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.EscherContainerRecord; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.hssf.record.ObjRecord; + +/** + * @author Evgeniy Berlog + * date: 05.06.12 + */ +public class HSSFUnknownShape extends HSSFShape { + + public HSSFUnknownShape(EscherRecord spContainer, ObjRecord objRecord) { + super((EscherContainerRecord) spContainer, objRecord); + } +} diff --git a/src/java/org/apache/poi/hssf/usermodel/drawing/HSSFShapeType.java b/src/java/org/apache/poi/hssf/usermodel/drawing/HSSFShapeType.java new file mode 100644 index 000000000..281868792 --- /dev/null +++ b/src/java/org/apache/poi/hssf/usermodel/drawing/HSSFShapeType.java @@ -0,0 +1,29 @@ +package org.apache.poi.hssf.usermodel.drawing; + +import org.apache.poi.hssf.usermodel.HSSFRectangle; + +/** + * @author Evgeniy Berlog + * date: 08.06.12 + */ +public enum HSSFShapeType { + NOT_PRIMITIVE(0x0, null), + RECTANGLE(0x1, HSSFRectangle.class), + ROUND_RECTANGLE(0x2, null); + + private Short type; + private Class shape; + + HSSFShapeType(Integer type, Class shape) { + this.type = type.shortValue(); + this.shape = shape; + } + + public Short getType() { + return type; + } + + public Class getShape() { + return shape; + } +} diff --git a/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java b/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java index a22ce4a0c..0608c0eb1 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java +++ b/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java @@ -17,13 +17,13 @@ package org.apache.poi.hssf.model; import junit.framework.TestCase; +import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherDggRecord; +import org.apache.poi.ddf.EscherRecord; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; -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.hssf.usermodel.*; import org.apache.poi.util.HexRead; import java.io.*; @@ -35,18 +35,84 @@ import java.util.List; * @author Evgeniy Berlog */ public class TestDrawingAggregate 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(); + + private int spgrCount = 0; + private int spCount = 0; + private int shapeCount = 0; + private int shGroupCount = 0; + + /* + * EscherAggregate must have for each SpgrContainer HSSFShapeGroup and for each SpContainer HSSFShape + */ + private void checkEscherAndShapesCount(EscherAggregate agg, HSSFSheet sheet) { + /* + HSSFPatriarch patriarch = HSSFTestHelper.createTestPatriarch(sheet, agg); + agg.setPatriarch(patriarch); + EscherAggregate.createShapeTree(EscherAggregate.getMainSpgrContainer(agg), agg.getPatriarch(), agg); + EscherContainerRecord mainContainer = EscherAggregate.getMainSpgrContainer(agg); + calculateShapesCount(agg.getPatriarch()); + calculateEscherContainersCount(mainContainer); + + assertEquals(spgrCount, shGroupCount); + assertEquals(spCount - spgrCount - 1, shapeCount); + */ + } + + private void calculateEscherContainersCount(EscherContainerRecord spgr) { + for (EscherRecord record : spgr.getChildRecords()) { + if (EscherContainerRecord.SP_CONTAINER == record.getRecordId()) { + spCount++; + continue; + } + if (EscherContainerRecord.SPGR_CONTAINER == record.getRecordId()) { + spgrCount++; + calculateEscherContainersCount((EscherContainerRecord) record); + } } + } + + private void calculateShapesCount(HSSFShapeContainer group) { + for (HSSFShape shape : (List) group.getChildren()) { + if (shape instanceof HSSFShapeGroup) { + shGroupCount++; + calculateShapesCount((HSSFShapeGroup) shape); + } else { + shapeCount++; + } + } + } + + + 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(){ + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("SolverContainerAfterSPGR.xls"); + HSSFSheet sh = wb.getSheetAt(0); + InternalSheet ish = HSSFTestHelper.getSheetForTest(sh); + 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); + + } /** * test reading drawing aggregate from a test file from Bugzilla 45129 @@ -107,6 +173,7 @@ public class TestDrawingAggregate extends TestCase { 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)); + checkEscherAndShapesCount(agg, sh); } /** @@ -174,7 +241,7 @@ public class TestDrawingAggregate extends TestCase { 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)); - + checkEscherAndShapesCount(agg, sh); } @@ -185,21 +252,20 @@ public class TestDrawingAggregate extends TestCase { List records = isheet.getRecords(); HSSFWorkbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb); - InternalSheet isheet2 = HSSFTestHelper.getSheetForTest( wb2.getSheetAt(0)); + InternalSheet isheet2 = HSSFTestHelper.getSheetForTest(wb2.getSheetAt(0)); List records2 = isheet2.getRecords(); assertEquals(records.size(), records2.size()); - for(int i = 0; i < records.size(); i++) { + 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()); + 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 { @@ -257,6 +323,7 @@ public class TestDrawingAggregate extends TestCase { 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)); + checkEscherAndShapesCount(agg, sh); } @@ -314,7 +381,8 @@ public class TestDrawingAggregate extends TestCase { 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)); + assertTrue("drawing data before and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); + checkEscherAndShapesCount(agg, sh); } public void testUnhandledContinue() { diff --git a/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java b/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java index 7c5a88bd4..0b79eae50 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/HSSFTestHelper.java @@ -18,6 +18,7 @@ package org.apache.poi.hssf.usermodel; import org.apache.poi.hssf.model.InternalSheet; import org.apache.poi.hssf.model.InternalWorkbook; +import org.apache.poi.hssf.record.EscherAggregate; /** * Helper class for HSSF tests that aren't within the @@ -34,4 +35,8 @@ 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); + } } diff --git a/test-data/spreadsheet/SolverContainerAfterSPGR.xls b/test-data/spreadsheet/SolverContainerAfterSPGR.xls new file mode 100755 index 0000000000000000000000000000000000000000..57a1b17048f306fd02d23f60ce5599783310400a GIT binary patch literal 26112 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