From de28fe9aff1aa6cd00689310643acd3b6b428abc Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Mon, 4 Jun 2012 08:08:47 +0000 Subject: [PATCH] Bugzilla 53010, patch from June 3: improved support for Continue records in drawing blocks git-svn-id: https://svn.apache.org/repos/asf/poi/branches/gsoc2012@1345858 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/hssf/model/InternalSheet.java | 25 +- .../apache/poi/hssf/record/DrawingRecord.java | 45 +- .../poi/hssf/record/EscherAggregate.java | 1911 +++++++++-------- .../hssf/record/RecordFactoryInputStream.java | 16 +- .../poi/hssf/model/TestDrawingAggregate.java | 297 +-- .../poi/hssf/record/HSSFRecordTestHelper.java | 34 - .../poi/hssf/record/TestDrawingRecord.java | 13 +- test-data/spreadsheet/DrawingAndComments.xls | Bin 0 -> 43520 bytes test-data/spreadsheet/DrawingContinue.xls | Bin 0 -> 68608 bytes 9 files changed, 1177 insertions(+), 1164 deletions(-) delete mode 100644 src/testcases/org/apache/poi/hssf/record/HSSFRecordTestHelper.java create mode 100755 test-data/spreadsheet/DrawingAndComments.xls create mode 100755 test-data/spreadsheet/DrawingContinue.xls diff --git a/src/java/org/apache/poi/hssf/model/InternalSheet.java b/src/java/org/apache/poi/hssf/model/InternalSheet.java index 75ea355e50..1e0af24530 100644 --- a/src/java/org/apache/poi/hssf/model/InternalSheet.java +++ b/src/java/org/apache/poi/hssf/model/InternalSheet.java @@ -1508,31 +1508,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 ++; - while ( loc + 1 < records.size() - && records.get( loc ) instanceof ContinueRecord - && (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/record/DrawingRecord.java b/src/java/org/apache/poi/hssf/record/DrawingRecord.java index 7b94e26c16..9e104de59e 100644 --- a/src/java/org/apache/poi/hssf/record/DrawingRecord.java +++ b/src/java/org/apache/poi/hssf/record/DrawingRecord.java @@ -20,22 +20,21 @@ 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(); } public void processContinueRecord(byte[] record) { @@ -46,6 +45,7 @@ public final class DrawingRecord extends StandardRecord { public void serialize(LittleEndianOutput out) { out.write(recordData); } + protected int getDataSize() { return recordData.length; } @@ -55,12 +55,12 @@ public final class DrawingRecord extends StandardRecord { } 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; - } +// if (continueData.size() != 0) { +// byte[] newBuffer = new byte[recordData.length + continueData.size()]; +// System.arraycopy(recordData, 0, newBuffer, 0, recordData.length); +// System.arraycopy(continueData.toByteArray(), 0, newBuffer, recordData.length, continueData.size()); +// return newBuffer; +// } return recordData; } @@ -69,21 +69,20 @@ public final class DrawingRecord extends StandardRecord { } 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; } 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 +} diff --git a/src/java/org/apache/poi/hssf/record/EscherAggregate.java b/src/java/org/apache/poi/hssf/record/EscherAggregate.java index e2ca3dd475..3bb7127356 100644 --- a/src/java/org/apache/poi/hssf/record/EscherAggregate.java +++ b/src/java/org/apache/poi/hssf/record/EscherAggregate.java @@ -17,11 +17,9 @@ 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; @@ -65,884 +63,912 @@ 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 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; - - /** Maps shape container objects to their {@link TextObjectRecord} or {@link ObjRecord} */ - Map shapeToObj = new HashMap(); - private DrawingManager2 drawingManager; - private short drawingGroupId; - - /** - * list of "tail" records that need to be serialized after all drawing group records - */ - private List tailRec = new ArrayList(); - - public EscherAggregate( DrawingManager2 drawingManager ) - { - this.drawingManager = drawingManager; - } - - /** - * @return Returns the current sid. - */ - public short getSid() - { - return sid; - } - - /** - * 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" ); - - 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(); - } - - public String toXml(String tab){ + 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; + + protected HSSFPatriarch patriarch; + + /** + * Maps shape container objects to their {@link TextObjectRecord} or {@link ObjRecord} + */ + private final Map shapeToObj = new HashMap(); + private DrawingManager2 drawingManager; + private short drawingGroupId; + + /** + * list of "tail" records that need to be serialized after all drawing group records + */ + private List tailRec = new ArrayList(); + + public EscherAggregate(DrawingManager2 drawingManager) { + this.drawingManager = drawingManager; + } + + /** + * @return Returns the current sid. + */ + public short getSid() { + return sid; + } + + /** + * 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"); + + 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(); + } + + 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 ) ).getRecordData().length; - loc += 2; - while ( loc + 1 < records.size() - && sid( records, loc ) == ContinueRecord.sid - && isObjectRecord( records, loc + 1 ) ) - { - dataSize += ( (ContinueRecord) records.get( loc ) ).getData().length; - loc += 2; + private static boolean isDrawingLayerRecord(final short sid) { + return sid == DrawingRecord.sid || + sid == ContinueRecord.sid || + sid == ObjRecord.sid || + sid == TextObjectRecord.sid; + } + + /** + * Collapses the drawing records into an aggregate. + * read Drawing 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 - 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.getRecordData(), 0, buffer, offset, drawingRecord.getRecordData().length ); - offset += drawingRecord.getRecordData().length; - loc += 2; - while ( loc + 1 < records.size() - && sid( records, loc ) == ContinueRecord.sid - && isObjectRecord( records, loc + 1 ) ) - { - ContinueRecord continueRecord = (ContinueRecord) records.get( loc ); - System.arraycopy( continueRecord.getData(), 0, buffer, offset, continueRecord.getData().length ); - offset += continueRecord.getData().length; - loc += 2; + }; + + // 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.add(r); + } else { + break; + } + loc++; + } + + int locLastDrawingRecord = loc; + // replace drawing block with the created EscherAggregate + records.subList(locFirstDrawingRecord, locLastDrawingRecord).clear(); + records.add(locFirstDrawingRecord, agg); + + + 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; + int writtenEscherBytes = 0; + 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(); + + + byte[] drawingData = new byte[endOffset - startOffset + 1]; + System.arraycopy(buffer, startOffset, drawingData, 0, drawingData.length); + 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) { + ContinueRecord drawing = new ContinueRecord(Arrays.copyOfRange(drawingData, j, Math.min(j + RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length))); + 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(); + drawing.setData(Arrays.copyOfRange(drawingData, j, Math.min(j + RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length))); + temp += drawing.serialize(pos + temp, data); + } else { + ContinueRecord drawing = new ContinueRecord(Arrays.copyOfRange(drawingData, j, Math.min(j + RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length))); + temp += drawing.serialize(pos + temp, data); + } + } + } - } - - // 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; - while ( loc + 1 < records.size() - && sid( records, loc ) == ContinueRecord.sid - && isObjectRecord( records, loc + 1 ) ) - { - objRecord = (Record) records.get( loc + 1 ); - agg.shapeToObj.put( shapeRecords.get( shapeIndex++ ), objRecord ); - loc += 2; + + pos += temp; + writtenEscherBytes += drawingData.length; + + // 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(); + // 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) { + } + + 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); + + for (int i = 1; i < spEndingOffsets.size(); i++) { + if (spEndingOffsets.get(i) - spEndingOffsets.get(i - 1) <= RecordInputStream.MAX_RECORD_DATA_SIZE){ + continue; } - } - - 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.... + continueRecordsHeadersSize += ((spEndingOffsets.get(i) - spEndingOffsets.get(i - 1)) / RecordInputStream.MAX_RECORD_DATA_SIZE)*4; + } + + 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 +continueRecordsHeadersSize; + } + + /** + * 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." ); - } - - } - } + 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 < tcc.size(); i++) { + EscherContainerRecord shapeContainer = (EscherContainerRecord) tcc.get(i); + + // Could be a group, or a base object + if (shapeContainer.getRecordId() == EscherContainerRecord.SPGR_CONTAINER) { + // Group + final int shapeChildren = shapeContainer.getChildRecords().size(); + if (shapeChildren > 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); - } + 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){ + 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() ); + 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 HSSFChildAnchor toChildAnchor(EscherChildAnchorRecord anchorRecord){ + 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.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 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; - - 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; - - 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; - - // 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); - } - } - } - - public void clear() - { - clearEscherRecords(); - shapeToObj.clear(); + 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; + + 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; + + 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; + + // 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); + } + } + } + + public void clear() { + clearEscherRecords(); + shapeToObj.clear(); // lastShapeId = 1024; - } - - protected String getRecordName() - { - return "ESCHERAGGREGATE"; - } - - // =============== Private methods ======================== - - private static boolean isObjectRecord( List records, int loc ) - { - return sid( records, loc ) == ObjRecord.sid || sid( records, loc ) == TextObjectRecord.sid; - } - - 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 ); - - patriarch = null; - } - } - } - - private void convertShapes( HSSFShapeContainer parent, EscherContainerRecord escherParent, Map shapeToObj ) - { - if ( escherParent == null ) throw new IllegalArgumentException( "Parent record required" ); - - 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); - - if ( shapeModel instanceof CommentShape ){ - CommentShape comment = (CommentShape)shapeModel; - tailRec.add(comment.getNoteRecord()); - } - - } - escherParent.addChildRecord( shapeModel.getSpContainer() ); - } - } + } + + protected String getRecordName() { + return "ESCHERAGGREGATE"; + } + + // =============== Private methods ======================== + + private static boolean isObjectRecord(List records, int loc) { + return sid(records, loc) == ObjRecord.sid || sid(records, loc) == TextObjectRecord.sid; + } + + 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); + + patriarch = null; + } + } + } + + private void convertShapes(HSSFShapeContainer parent, EscherContainerRecord escherParent, Map shapeToObj) { + if (escherParent == null) throw new IllegalArgumentException("Parent record required"); + + 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); + + if (shapeModel instanceof CommentShape) { + CommentShape comment = (CommentShape) shapeModel; + tailRec.add(comment.getNoteRecord()); + } + + } + escherParent.addChildRecord(shapeModel.getSpContainer()); + } + } // drawingManager.newCluster( (short)1 ); // drawingManager.newCluster( (short)2 ); - } - - 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(); - - 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 ) ); - - anchor = ConvertAnchor.createAnchor( shape.getAnchor() ); + } + + 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(); + + 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)); + + anchor = ConvertAnchor.createAnchor(shape.getAnchor()); // clientAnchor.setCol1( ( (HSSFClientAnchor) shape.getAnchor() ).getCol1() ); // clientAnchor.setRow1( (short) ( (HSSFClientAnchor) shape.getAnchor() ).getRow1() ); // clientAnchor.setDx1( (short) shape.getAnchor().getDx1() ); @@ -951,113 +977,108 @@ public final class EscherAggregate extends AbstractEscherHolderRecord { // 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 ); - - spgrContainer.addChildRecord( spContainer ); - spContainer.addChildRecord( spgr ); - spContainer.addChildRecord( sp ); - spContainer.addChildRecord( opt ); - spContainer.addChildRecord( anchor ); - spContainer.addChildRecord( clientData ); - - 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 ); - - escherParent.addChildRecord( spgrContainer ); - - convertShapes( shape, spgrContainer, shapeToObj ); - - } - - 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" ); - } - - 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(); - - dgContainer.setRecordId( EscherContainerRecord.DG_CONTAINER ); - dgContainer.setOptions( (short) 0x000F ); - dg = drawingManager.createDgRecord(); - drawingGroupId = dg.getDrawingGroupId(); + 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); + + 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); + + escherParent.addChildRecord(spgrContainer); + + convertShapes(shape, spgrContainer, shapeToObj); + + } + + 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"); + } + + 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(); + + 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(); - } - - - // 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; - } + 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(); + } + + + // 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; + } } diff --git a/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java b/src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java index 344ad07ef3..aac88b80c9 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/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java b/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java index c5aa5855a9..a22ce4a0cc 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java +++ b/src/testcases/org/apache/poi/hssf/model/TestDrawingAggregate.java @@ -17,11 +17,7 @@ package org.apache.poi.hssf.model; import junit.framework.TestCase; -import org.apache.poi.ddf.EscherClientDataRecord; -import org.apache.poi.ddf.EscherContainerRecord; import org.apache.poi.ddf.EscherDggRecord; -import org.apache.poi.ddf.EscherRecord; -import org.apache.poi.ddf.EscherSpRecord; import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.record.*; import org.apache.poi.hssf.record.aggregates.RowRecordsAggregate; @@ -30,147 +26,199 @@ import org.apache.poi.hssf.usermodel.HSSFTestHelper; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.util.HexRead; -import java.io.ByteArrayInputStream; -import java.util.ArrayList; -import java.util.LinkedHashMap; +import java.io.*; +import java.util.Arrays; import java.util.List; -import java.util.Map; /** * @author Yegor Kozlov * @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(); + } + /** - * Serialize escher aggregate, read back and assert that the drawing data is preserved. - * - * @param agg the aggregate to test - * @return verified aggregate (serialized and read back) + * test reading drawing aggregate from a test file from Bugzilla 45129 */ - public static EscherAggregate assertWriteAndReadBack(EscherAggregate agg) { - byte[] dgBytes = agg.serialize(); - + public void test45129() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("45129.xls"); + HSSFSheet sh = wb.getSheetAt(0); - List dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes)); + InternalWorkbook iworkbook = HSSFTestHelper.getWorkbookForTest(wb); + InternalSheet isheet = HSSFTestHelper.getSheetForTest(sh); - DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord()); + List records = isheet.getRecords(); - // 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); + // 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); - 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 agg2 = (EscherAggregate) records.get(0); + 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); + } - assertEquals(agg.getEscherRecords().size(), agg2.getEscherRecords().size()); + // the first record after the drawing block + assertTrue( + "records.get(389) is expected to be Window2", + records.get(389) instanceof WindowTwoRecord); - // assert that both pre- and after- serialize aggregates have the same xml representation - for (int i = 0; i < agg.getEscherRecords().size(); i++) { - EscherRecord r1 = agg.getEscherRecords().get(i); - EscherRecord r2 = agg2.getEscherRecords().get(i); + // 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(r1.toXml(), r2.toXml()); - } + 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); - return agg2; + 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)); } /** - * assert that mapping of Obj records to escher shape containers is the same in both aggregates + * Try to check file with such record sequence + * ... + * DrawingRecord + * ContinueRecord + * ObjRecord | TextObjRecord + * ... */ - public static void assertObjectMappingSame(EscherAggregate agg1, EscherAggregate agg2) { - - // map EscherClientDataRecord and EscherTextboxRecord to their parents - Map map1 = new LinkedHashMap(); - for (EscherRecord r : agg1.getEscherRecords()) mapShapeContainers(r, map1); - - Map map2 = new LinkedHashMap(); - for (EscherRecord r : agg2.getEscherRecords()) mapShapeContainers(r, map2); - - assertEquals("aggregates have different number of shapes", map1.size(), map2.size()); - - // for each EscherClientDataRecord get parent SP_CONTAINER and corresponding ObjRecord - // verify that ObjRecord to - List l1 = new ArrayList(map1.keySet()); - List l2 = new ArrayList(map2.keySet()); - for (int i = 0; i < l1.size(); i++) { - EscherRecord e1 = l1.get(i); - EscherRecord e2 = l2.get(i); - ObjRecord obj1 = (ObjRecord) HSSFRecordTestHelper.getShapeToObjForTest(agg1).get(e1); - ObjRecord obj2 = (ObjRecord) HSSFRecordTestHelper.getShapeToObjForTest(agg2).get(e2); - - CommonObjectDataSubRecord cmo1 = (CommonObjectDataSubRecord) obj1.getSubRecords().get(0); - CommonObjectDataSubRecord cmo2 = (CommonObjectDataSubRecord) obj2.getSubRecords().get(0); - - assertEquals(cmo1.getObjectId(), cmo2.getObjectId()); - assertEquals(obj1.toString(), obj2.toString()); - - // test that obj parents have the same shapeId, that is, that shape is the same - EscherContainerRecord p1 = map1.get(e1); - EscherContainerRecord p2 = map2.get(e2); - EscherSpRecord sp1 = (EscherSpRecord) p1.getChildById(EscherSpRecord.RECORD_ID); - EscherSpRecord sp2 = (EscherSpRecord) p2.getChildById(EscherSpRecord.RECORD_ID); - assertEquals(sp1.getShapeId(), sp2.getShapeId()); - - assertEquals("wrong shape2obj mapping", sp1.getShapeId() % 1024, cmo1.getObjectId()); - assertEquals(p1.toXml(), p2.toXml()); + 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)); + + } - /** - * recursively map EscherClientDataRecords to their parent shape containers: - *

- * EscherClientDataRecord1 --> EscherContainerRecord1 - * EscherClientDataRecord2 --> EscherContainerRecord2 - * ... - *

- * TODO: YK: this method can be avoided if we have EscherRecord.getParent() - */ - private static void mapShapeContainers(EscherRecord parent, Map map) { - if (parent.isContainerRecord()) { - if (parent.getRecordId() == EscherContainerRecord.SP_CONTAINER) { - // iterate over shape's children and search for EscherClientDataRecord - for (EscherRecord r : parent.getChildRecords()) { - if (r.getRecordId() == EscherClientDataRecord.RECORD_ID) { - map.put(r, (EscherContainerRecord) parent); - } - } - } else { - for (EscherRecord ch : parent.getChildRecords()) { - mapShapeContainers(ch, map); - } + + 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())); } } + } - /** - * test reading drawing aggregate from a test file from Bugzilla 45129 - */ - public void test45129() { - HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("45129.xls"); + 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", 394, records.size()); + 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, 388); + List dgRecords = records.subList(19, 39); for (RecordBase rb : dgRecords) { Record r = (Record) rb; short sid = r.getSid(); @@ -180,21 +228,24 @@ public class TestDrawingAggregate extends TestCase { 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(389) is expected to be Window2", - records.get(389) instanceof WindowTwoRecord); + "records.get(39) is expected to be Window2", + records.get(39) instanceof WindowTwoRecord); // aggregate drawing records. - // The subrange [19, 388] is expected to be replaced with a EscherAggregate object + // 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", 25, records.size()); + 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); @@ -203,11 +254,12 @@ public class TestDrawingAggregate extends TestCase { assertTrue("records.get(20) is expected to be Window2 but was " + records.get(20).getClass().getSimpleName(), records.get(20) instanceof WindowTwoRecord); - EscherAggregate agg2 = assertWriteAndReadBack(agg); - - assertObjectMappingSame(agg, agg2); + 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); @@ -225,7 +277,7 @@ public class TestDrawingAggregate extends TestCase { records.get(21) instanceof RowRecordsAggregate); // records to be aggregated - List dgRecords = records.subList(22, 299); + List dgRecords = records.subList(22, 300); for (RecordBase rb : dgRecords) { Record r = (Record) rb; short sid = r.getSid(); @@ -237,6 +289,8 @@ public class TestDrawingAggregate extends TestCase { 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( @@ -244,7 +298,7 @@ public class TestDrawingAggregate extends TestCase { records.get(300) instanceof WindowTwoRecord); // aggregate drawing records. - // The subrange [19, 388] is expected to be replaced with a EscherAggregate object + // 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); @@ -258,9 +312,9 @@ public class TestDrawingAggregate extends TestCase { assertTrue("records.get(23) is expected to be Window2 but was " + records.get(23).getClass().getSimpleName(), records.get(23) instanceof WindowTwoRecord); - EscherAggregate agg2 = assertWriteAndReadBack(agg); - - assertObjectMappingSame(agg, agg2); + 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)); } public void testUnhandledContinue() { @@ -972,10 +1026,9 @@ public class TestDrawingAggregate extends TestCase { assertTrue("expected EOFRecord", records.get(1) instanceof EOFRecord); EscherAggregate agg = (EscherAggregate) records.get(0); - // serialize, read back and assert that the drawing data is preserved - EscherAggregate agg2 = assertWriteAndReadBack(agg); - - assertObjectMappingSame(agg, agg2); + 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() { @@ -1926,8 +1979,8 @@ public class TestDrawingAggregate extends TestCase { EscherAggregate agg = (EscherAggregate) records.get(0); - EscherAggregate agg2 = assertWriteAndReadBack(agg); - - assertObjectMappingSame(agg, agg2); + 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/record/HSSFRecordTestHelper.java b/src/testcases/org/apache/poi/hssf/record/HSSFRecordTestHelper.java deleted file mode 100644 index 4eed03d5e5..0000000000 --- a/src/testcases/org/apache/poi/hssf/record/HSSFRecordTestHelper.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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.record; - -import org.apache.poi.ddf.EscherRecord; - -import java.util.Map; - -/** - * @author Evgeniy Berlog - * date: 30.05.12 - */ -public class HSSFRecordTestHelper { - - public static Map getShapeToObjForTest(EscherAggregate agg){ - return agg.shapeToObj; - } - -} diff --git a/src/testcases/org/apache/poi/hssf/record/TestDrawingRecord.java b/src/testcases/org/apache/poi/hssf/record/TestDrawingRecord.java index f207a0046d..38992c0bdf 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/test-data/spreadsheet/DrawingAndComments.xls b/test-data/spreadsheet/DrawingAndComments.xls new file mode 100755 index 0000000000000000000000000000000000000000..8be6c9e5269041068944e7938873b19c0198d2ac GIT binary patch literal 43520 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


4O4MRZC0ezb-GXSn)S1g`$12{+2x! z+oIKWZeoh_H5?MH|FGEpA#=70f64F(6BFYBhboV-5)uDT?BdsT1G^VUiw;Q^8NJ!|{_Tocy2r_m1NP44+^!3PB$YEY+?xC^u`g>4 zo}StrH(mX>O?0-^`Tb98J_fmHb9gibSf$=o+%w$Kqfl`5vYwCQ!K-Y69NYBj>R7i6 zsTl40=69huXiv|PB$>pe?c9Mi^Bk6)TsGx;qV#)<y3Mg!ML zuW+$_4;MDQyzxl8c?0(bt%57l!Y?1VCc4pUPG`l`RKx9L@{*z_%=wl5`)RiYIb`p& z<=GHfy^Ui^mkp=V^t)3<0;aBzoNE4K016vw4Ieetv$kt-@mFDQd3e@d-%Mxaq2& zM=Kv8D(~T3EO+nnduySUN`*0(w#dxZ>eKW3{PxGo_-g6rJr8yJS&Z_!lLK}|ElZX3 zx}LsGwdR!6(t`|{>z!M}Ij`M6<+O6~0soeUH>C@voSts2xaYGHY4CiKM{7{25{-U! zzrfP)a>)zg<&kyszpo3iKMgmo?2@(O_spsZz7tlbc(OY?(=9YA{MM&TJ0X?#>a{!0 zZBOj=H2g92ZMor(+P==B?+r&QrY7V(jW!Y0^AhK%?9xY1ASKk(a`cOe>Z2sSB$jk# zW*+)7t3}+ttMW-)Vpo4rf7g>P^w2kP%L4X0Ltj?(f7}tIOHgio_CEjhMne%5 z5@zBKT}>-rUOV-uX0Ld4W?}r`s)648h96Pd$3h2&^}8f$6*9kcRrh?YFMn3iFj!Jv znRBpqOT&Oh_0tTEEvFrpbDS+TZu=X=~G;@`lzh7!O@MDy@NVk=R6{{Z#J}~R_G@A-x6|p zcyWrgo!gOi|6}u0>QqwrTaEH;4lHXcewlS-W0zfS`-KP|<+kUu;+nnJ@v>ePjAE$L zZ>!tKH#MZeHT%s}pVJyYdd0ThY%|>+`YEbz5$mz9Mm>=kIj!!EtRA8Qb#$>6!L9l> z`(3R$dMs3?oqwb5a{Ry%|GQ?E-gmVbk2{HyR@D7aNVk11Nn>=^ZDI2@woBJOnmtO~ zbhl)Flu>W_iVilNA0=CP5{>1{eo1oldyZq@mPcSCj{nXVCC6^W_)S&`HTyrkC^=RK zewThF^^Y$~YRgc-f4?ZnNlS}<6N3R3(elDal*#q;V;z(iC7IC21-KUdCPBFqMV}YI zpF+VeSfZ~G{QO!1`t@BN^j;;_T$-`T_;V=uRm2g{rZn7eksc#7$0v07g#he8Uv%5S zNQqO zVN=ZipT7c!zr%xU0TLemBO}?8hADji{tl0q`hWI2JQPg$Otc3PH7CxL)1U1@gML7n z@(mmWfpq{M|E2B0z3G2@d!Wz9{wKeSL=h)G|If6?Z+d_4{wE|bg!(Q$pTr0%3g?fxi;?D}lcf_$z_G68I~D zzY_Q>fxi;?D}lcf_$h(O=KnMgY9FMli*W8qL-T*tfm3+?--rVK3O!yQa6|#G`n#im zK2nRm%}x1oX%M>Nxq1W&c-|k40{(W&W)$#xz)lqK`oIMgMy?0owFG=n=I)dQRvgD) zL&e9~t2iQpVextd<;P5Z-)jH9odT=~5EH_S2zZ^g`qjtY??lnR%LGUqe~-Ts_$z_G z68I~DzY_Q>fxi;?D}lcf_$z_G68I~D|N9ca^LRWp#uH&Y|Hj**@Dv%(sqyo7Jl98` znyx5 zULDFb9DVl(BvFt;K^g@a6l775LqQ$|1r!ufP(nc&1r-!jQBXrc9R&>(G*Qq(0e_=v z3JU0>tms#m0sS5&Wh<5ex*DQ@epD0vST3NSs|A>f0{-nAGZf5Gn1;d-+MhKYU1y+R ziNZ`2W}z?}1uGPAz74wCqF{%DJqiveIHKT$0?z+|0>1ulJDkxkdE%dql|(hizug&z zeqQ!Ja_|EkvA+%18|#rIBR&IMBhG{;6w1>N3_6yj30ylME$KX;h*&XKTAFa0ssI2 literal 0 HcmV?d00001 diff --git a/test-data/spreadsheet/DrawingContinue.xls b/test-data/spreadsheet/DrawingContinue.xls new file mode 100755 index 0000000000000000000000000000000000000000..131c18cc9b09c2d6286e9a2da104bd0d504bfb51 GIT binary patch literal 68608 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ⅆaQ$vI-l_@<|2XHEW2bq`@%G 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

-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 z