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:
parent
cacf0c4fc3
commit
ae4a40d8ba
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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() {
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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())
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user