package org.apache.poi.ddf;
-import org.apache.poi.util.LittleEndian;
-import org.apache.poi.util.HexDump;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
-import java.util.*;
-import java.io.IOException;
+import org.apache.poi.util.HexDump;
+import org.apache.poi.util.LittleEndian;
/**
* The opt record is used to store property values for a shape. It is the key to determining
return margins;
}
- public int aggregateDrawingRecords(DrawingManager2 drawingManager)
+ /**
+ * Finds the DrawingRecord for our sheet, and
+ * attaches it to the DrawingManager (which knows about
+ * the overall DrawingGroup for our workbook).
+ * If requested, will create a new DrawRecord
+ * if none currently exist
+ * @param drawingManager The DrawingManager2 for our workbook
+ * @param createIfMissing Should one be created if missing?
+ */
+ public int aggregateDrawingRecords(DrawingManager2 drawingManager, boolean createIfMissing)
{
int loc = findFirstRecordLocBySid(DrawingRecord.sid);
- boolean noDrawingRecordsFound = loc == -1;
+ boolean noDrawingRecordsFound = (loc == -1);
if (noDrawingRecordsFound)
{
+ if(!createIfMissing) {
+ // None found, and not allowed to add in
+ return -1;
+ }
+
EscherAggregate aggregate = new EscherAggregate( drawingManager );
loc = findFirstRecordLocBySid(EscherAggregate.sid);
if (loc == -1)
}
return palette;
}
+
+ /**
+ * Finds the primary drawing group, if one already exists
+ */
+ public void findDrawingGroup() {
+ // Need to find a DrawingGroupRecord that
+ // contains a EscherDggRecord
+ for(Iterator rit = records.iterator(); rit.hasNext();) {
+ Record r = (Record)rit.next();
+
+ if(r instanceof DrawingGroupRecord) {
+ DrawingGroupRecord dg = (DrawingGroupRecord)r;
+ dg.processChildRecords();
+
+ EscherContainerRecord cr =
+ dg.getEscherContainer();
+ if(cr == null) {
+ continue;
+ }
+
+ EscherDggRecord dgg = null;
+ for(Iterator it = cr.getChildRecords().iterator(); it.hasNext();) {
+ Object er = it.next();
+ if(er instanceof EscherDggRecord) {
+ dgg = (EscherDggRecord)er;
+ }
+ }
+
+ if(dgg != null) {
+ drawingManager = new DrawingManager2(dgg);
+ return;
+ }
+ }
+ }
+
+ // Look for the DrawingGroup record
+ int dgLoc = findFirstRecordLocBySid(DrawingGroupRecord.sid);
+
+ // If there is one, does it have a EscherDggRecord?
+ if(dgLoc != -1) {
+ DrawingGroupRecord dg =
+ (DrawingGroupRecord)records.get(dgLoc);
+ EscherDggRecord dgg = null;
+ for(Iterator it = dg.getEscherRecords().iterator(); it.hasNext();) {
+ Object er = it.next();
+ if(er instanceof EscherDggRecord) {
+ dgg = (EscherDggRecord)er;
+ }
+ }
+
+ if(dgg != null) {
+ drawingManager = new DrawingManager2(dgg);
+ }
+ }
+ }
/**
- * Creates a drawing group record. If it already exists then it's modified.
+ * Creates a primary drawing group record. If it already
+ * exists then it's modified.
*/
public void createDrawingGroup()
{
-
if (drawingManager == null)
{
EscherContainerRecord dggContainer = new EscherContainerRecord();
}
}
-
}
public WindowOneRecord getWindowOne() {
package org.apache.poi.hssf.record;
+import java.io.ByteArrayInputStream;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
import org.apache.poi.ddf.DefaultEscherRecordFactory;
+import org.apache.poi.ddf.EscherContainerRecord;
import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.ddf.EscherRecordFactory;
import org.apache.poi.ddf.NullEscherSerializationListener;
import org.apache.poi.util.LittleEndian;
-import java.io.ByteArrayInputStream;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationTargetException;
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
/**
* The escher container record is used to hold escher records. It is abstract and
* must be subclassed for maximum benefit.
}
}
+ protected void convertRawBytesToEscherRecords() {
+ convertToEscherRecords(0, rawData.length, rawData);
+ }
private void convertToEscherRecords( int offset, int size, byte[] data )
{
EscherRecordFactory recordFactory = new DefaultEscherRecordFactory();
{
escherRecords.clear();
}
+
+ /**
+ * If we have a EscherContainerRecord as one of our
+ * children (and most top level escher holders do),
+ * then return that.
+ */
+ public EscherContainerRecord getEscherContainer() {
+ for(Iterator it = escherRecords.iterator(); it.hasNext();) {
+ Object er = it.next();
+ if(er instanceof EscherContainerRecord) {
+ return (EscherContainerRecord)er;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Descends into all our children, returning the
+ * first EscherRecord with the given id, or null
+ * if none found
+ */
+ public EscherRecord findFirstWithId(short id) {
+ return findFirstWithId(id, getEscherRecords());
+ }
+ private EscherRecord findFirstWithId(short id, List records) {
+ // Check at our level
+ for(Iterator it = records.iterator(); it.hasNext();) {
+ EscherRecord r = (EscherRecord)it.next();
+ if(r.getRecordId() == id) {
+ return r;
+ }
+ }
+
+ // Then check our children in turn
+ for(Iterator it = records.iterator(); it.hasNext();) {
+ EscherRecord r = (EscherRecord)it.next();
+ if(r.isContainerRecord()) {
+ EscherRecord found =
+ findFirstWithId(id, r.getChildRecords());
+ if(found != null) {
+ return found;
+ }
+ }
+ }
+
+ // Not found in this lot
+ return null;
+ }
public EscherRecord getEscherRecord(int index)
return writeData( offset, data, buffer );
}
}
+
+ /**
+ * Process the bytes into escher records.
+ * (Not done by default in case we break things,
+ * unless you set the "poi.deserialize.escher"
+ * system property)
+ */
+ public void processChildRecords() {
+ convertRawBytesToEscherRecords();
+ }
/**
* Size of record (including 4 byte headers for all sections)
{
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");
+ }
+
+ // TODO: Support converting our records
+ // back into shapes
+ }
+
public void clear()
{
clearEscherRecords();
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.hssf.record.EscherAggregate;
+import org.apache.poi.util.LittleEndian;
+import org.apache.poi.util.StringUtil;
+
/**
* The patriarch is the toplevel container for shapes in a sheet. It does
* little other than act as a container for other shapes and groups.
int x2 = 1023;
int y2 = 255;
+ /**
+ * The EscherAggregate we have been bound to.
+ * (This will handle writing us out into records,
+ * and building up our shapes from the records)
+ */
+ private EscherAggregate boundAggregate;
+
/**
* Creates the patriarch.
*
- * @param sheet the sheet this patriarch is stored in.
+ * @param sheet the sheet this patriarch is stored in.
*/
- HSSFPatriarch(HSSFSheet sheet)
+ HSSFPatriarch(HSSFSheet sheet, EscherAggregate boundAggregate)
{
+ this.boundAggregate = boundAggregate;
this.sheet = sheet;
}
this.x2 = x2;
this.y2 = y2;
}
+
+ /**
+ * Does this HSSFPatriarch contain a chart?
+ * (Technically a reference to a chart, since they
+ * get stored in a different block of records)
+ * FIXME - detect chart in all cases (only seems
+ * 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) {
+ // No opt record, can't have chart
+ return false;
+ }
+
+ for(Iterator it = optRecord.getEscherProperties().iterator(); it.hasNext();) {
+ EscherProperty prop = (EscherProperty)it.next();
+ if(prop.getPropertyNumber() == 896 && prop.isComplex()) {
+ EscherComplexProperty cp = (EscherComplexProperty)prop;
+ String str = StringUtil.getFromUnicodeLE(cp.getComplexData());
+ System.err.println(str);
+ if(str.equals("Chart 1\0")) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
/**
* The top left x coordinate of this group.
*/
public void dumpDrawingRecords(boolean fat)
{
- sheet.aggregateDrawingRecords(book.getDrawingManager());
+ sheet.aggregateDrawingRecords(book.getDrawingManager(), false);
EscherAggregate r = (EscherAggregate) getSheet().findFirstRecordBySid(EscherAggregate.sid);
List escherRecords = r.getEscherRecords();
}
/**
- * Creates the toplevel drawing patriarch. This will have the effect of
- * removing any existing drawings on this sheet.
- *
+ * 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()
// Create the drawing group if it doesn't already exist.
book.createDrawingGroup();
- sheet.aggregateDrawingRecords(book.getDrawingManager());
+ sheet.aggregateDrawingRecords(book.getDrawingManager(), true);
EscherAggregate agg = (EscherAggregate) sheet.findFirstRecordBySid(EscherAggregate.sid);
- HSSFPatriarch patriarch = new HSSFPatriarch(this);
+ HSSFPatriarch patriarch = new HSSFPatriarch(this, agg);
agg.clear(); // Initially the behaviour will be to clear out any existing shapes in the sheet when
// creating a new patriarch.
agg.setPatriarch(patriarch);
return patriarch;
}
+
+ /**
+ * Returns the top-level drawing patriach, if there is
+ * one.
+ * This will hold any graphics or charts for the sheet
+ */
+ public HSSFPatriarch getDrawingPatriarch() {
+ book.findDrawingGroup();
+
+ // If there's now no drawing manager, then there's
+ // no drawing escher records on the workbook
+ if(book.getDrawingManager() == null) {
+ return null;
+ }
+
+ int found = sheet.aggregateDrawingRecords(
+ book.getDrawingManager(), false
+ );
+ if(found == -1) {
+ // Workbook has drawing stuff, but this sheet doesn't
+ return null;
+ }
+
+ EscherAggregate agg = (EscherAggregate) sheet.findFirstRecordBySid(EscherAggregate.sid);
+ HSSFPatriarch patriarch = new HSSFPatriarch(this, agg);
+ agg.setPatriarch(patriarch);
+ agg.convertRecordsToUserModel();
+ return patriarch;
+ }
/**
* Expands or collapses a column group.
assertEquals(0, r6.getOutlineLevel());
}
+ public void testGetDrawings() throws Exception {
+ String filename = System.getProperty("HSSF.testdata.path");
+ HSSFWorkbook wb1c = new HSSFWorkbook(
+ new FileInputStream(new File(filename,"WithChart.xls"))
+ );
+ HSSFWorkbook wb2c = new HSSFWorkbook(
+ new FileInputStream(new File(filename,"WithTwoCharts.xls"))
+ );
+
+ // 1 chart sheet -> data on 1st, chart on 2nd
+ assertNotNull(wb1c.getSheetAt(0).getDrawingPatriarch());
+ assertNotNull(wb1c.getSheetAt(1).getDrawingPatriarch());
+ assertFalse(wb1c.getSheetAt(0).getDrawingPatriarch().containsChart());
+ assertTrue(wb1c.getSheetAt(1).getDrawingPatriarch().containsChart());
+
+ // 2 chart sheet -> data on 1st, chart on 2nd+3rd
+ assertNotNull(wb2c.getSheetAt(0).getDrawingPatriarch());
+ assertNotNull(wb2c.getSheetAt(1).getDrawingPatriarch());
+ assertNotNull(wb2c.getSheetAt(2).getDrawingPatriarch());
+ assertFalse(wb2c.getSheetAt(0).getDrawingPatriarch().containsChart());
+ assertTrue(wb2c.getSheetAt(1).getDrawingPatriarch().containsChart());
+ assertTrue(wb2c.getSheetAt(2).getDrawingPatriarch().containsChart());
+ }
+
/**
* Test that the ProtectRecord is included when creating or cloning a sheet
*/
*/
package org.apache.poi.hssf.usermodel;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
b.cloneSheet(0);
assertEquals(2, b.getNumberOfSheets());
}
+
+ public void testReadWriteWithCharts() throws Exception {
+ HSSFWorkbook b;
+ HSSFSheet s;
+
+ // Single chart, two sheets
+ b = new HSSFWorkbook(
+ new FileInputStream(new File(filename,"44010-SingleChart.xls"))
+ );
+ assertEquals(2, b.getNumberOfSheets());
+ s = b.getSheetAt(1);
+ assertEquals(0, s.getFirstRowNum());
+ assertEquals(0, s.getLastRowNum());
+
+ // Has chart on 1st sheet??
+ // FIXME
+ assertNotNull(b.getSheetAt(0).getDrawingPatriarch());
+ assertNull(b.getSheetAt(1).getDrawingPatriarch());
+ assertFalse(b.getSheetAt(0).getDrawingPatriarch().containsChart());
+
+ b = writeRead(b);
+ assertEquals(2, b.getNumberOfSheets());
+ s = b.getSheetAt(1);
+ assertEquals(0, s.getFirstRowNum());
+ assertEquals(0, s.getLastRowNum());
+
+
+ // Two charts, three sheets
+ b = new HSSFWorkbook(
+ new FileInputStream(new File(filename,"44010-TwoCharts.xls"))
+ );
+ assertEquals(3, b.getNumberOfSheets());
+
+ s = b.getSheetAt(1);
+ assertEquals(0, s.getFirstRowNum());
+ assertEquals(0, s.getLastRowNum());
+ s = b.getSheetAt(2);
+ assertEquals(0, s.getFirstRowNum());
+ assertEquals(0, s.getLastRowNum());
+
+ // Has chart on 1st sheet??
+ // FIXME
+ assertNotNull(b.getSheetAt(0).getDrawingPatriarch());
+ assertNull(b.getSheetAt(1).getDrawingPatriarch());
+ assertNull(b.getSheetAt(2).getDrawingPatriarch());
+ assertFalse(b.getSheetAt(0).getDrawingPatriarch().containsChart());
+
+ b = writeRead(b);
+ assertEquals(3, b.getNumberOfSheets());
+
+ s = b.getSheetAt(1);
+ assertEquals(0, s.getFirstRowNum());
+ assertEquals(0, s.getLastRowNum());
+ s = b.getSheetAt(2);
+ assertEquals(0, s.getFirstRowNum());
+ assertEquals(0, s.getLastRowNum());
+ }
+
+ private HSSFWorkbook writeRead(HSSFWorkbook b) throws Exception {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ b.write(baos);
+ return new HSSFWorkbook(
+ new ByteArrayInputStream(baos.toByteArray())
+ );
+ }
}
\ No newline at end of file