Make a start on processing shapes on a sheet out of a record. For now, doesn't actually manage to do this, but has much of the infrastructure that'll be needed. Includes ability to get an existing HSSFPatriarch for a sheet, if there are the required records, and for the HSSFPatriarch to be in a position to be given the shapes that make it up (but this isn't done yet)

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@610608 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2008-01-09 23:21:35 +00:00
parent cacf0c4fc3
commit ae4a40d8ba
10 changed files with 338 additions and 25 deletions

View File

@ -18,11 +18,14 @@
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

View File

@ -2663,12 +2663,26 @@ public class Sheet implements Model
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)

View File

@ -2165,13 +2165,68 @@ public class Workbook implements Model
}
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();
@ -2235,7 +2290,6 @@ public class Workbook implements Model
}
}
}
public WindowOneRecord getWindowOne() {

View File

@ -18,19 +18,18 @@
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.
@ -97,6 +96,9 @@ public abstract class AbstractEscherHolderRecord
}
}
protected void convertRawBytesToEscherRecords() {
convertToEscherRecords(0, rawData.length, rawData);
}
private void convertToEscherRecords( int offset, int size, byte[] data )
{
EscherRecordFactory recordFactory = new DefaultEscherRecordFactory();
@ -264,6 +266,54 @@ public abstract class AbstractEscherHolderRecord
{
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)

View File

@ -72,6 +72,16 @@ public class DrawingGroupRecord extends AbstractEscherHolderRecord
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)

View File

@ -523,7 +523,20 @@ public class EscherAggregate extends AbstractEscherHolderRecord
{
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();

View File

@ -21,6 +21,13 @@ import java.util.ArrayList;
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.
@ -37,13 +44,21 @@ public class HSSFPatriarch
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;
}
@ -173,6 +188,39 @@ public class HSSFPatriarch
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.

View File

@ -1488,7 +1488,7 @@ public class HSSFSheet
*/
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();
@ -1505,9 +1505,10 @@ public class HSSFSheet
}
/**
* 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()
@ -1515,14 +1516,43 @@ public class HSSFSheet
// 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.

View File

@ -407,6 +407,30 @@ public class TestHSSFSheet
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
*/

View File

@ -16,6 +16,8 @@
*/
package org.apache.poi.hssf.usermodel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
@ -129,4 +131,69 @@ public class TestHSSFWorkbook extends TestCase
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())
);
}
}