diff --git a/src/java/org/apache/poi/hssf/model/InternalSheet.java b/src/java/org/apache/poi/hssf/model/InternalSheet.java index 75ea355e5..1e0af2453 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 7b94e26c1..9e104de59 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 e2ca3dd47..3bb712735 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 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; + 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; + protected HSSFPatriarch patriarch; - /** Maps shape container objects to their {@link TextObjectRecord} or {@link ObjRecord} */ - Map shapeToObj = new HashMap(); - private DrawingManager2 drawingManager; - private short drawingGroupId; + /** + * 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(); + /** + * 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; - } + public EscherAggregate(DrawingManager2 drawingManager) { + this.drawingManager = drawingManager; + } - /** - * @return Returns the current sid. - */ - public short getSid() - { - return sid; - } + /** + * @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" ); + /** + * 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 ); + 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){ + 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; - } - }; + private static boolean isDrawingLayerRecord(final short sid) { + return sid == DrawingRecord.sid || + sid == ContinueRecord.sid || + sid == ObjRecord.sid || + sid == TextObjectRecord.sid; + } - // 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; + /** + * 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 < dataSize ) - { - EscherRecord r = recordFactory.createRecord( buffer, pos ); - int bytesRead = r.fillFields( buffer, pos, recordFactory ); - agg.addEscherRecord( r ); - pos += bytesRead; - } + // 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; - 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; + // 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++; + } - return agg; + // 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++; + } - } - - /** - * 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]; + int locLastDrawingRecord = loc; + // replace drawing block with the created EscherAggregate + records.subList(locFirstDrawingRecord, locLastDrawingRecord).clear(); + records.add(locFirstDrawingRecord, agg); - // 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 ) - { - } + return agg; + } - 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 ); + /** + * 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(); - // 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(); + // Determine buffer size + List records = getEscherRecords(); + int size = getEscherRecordSize(records); + byte[] buffer = new byte[size]; - // 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; + // 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); - // 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 ); - } + // 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(); - 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; - } + byte[] drawingData = new byte[endOffset - startOffset + 1]; + System.arraycopy(buffer, startOffset, drawingData, 0, drawingData.length); + int temp = 0; - 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; - } + //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); + } + } - /** - * 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; - } + pos += temp; + writtenEscherBytes += drawingData.length; - public void setPatriarch( HSSFPatriarch patriarch ) - { - this.patriarch = patriarch; - } + // Write the matching OBJ record + Record obj = shapeToObj.get(shapes.get(i)); + temp = obj.serialize(pos, data); + pos += temp; - /** - * 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); + // 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); + } - 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!"); - } + int bytesWritten = pos - offset; + if (bytesWritten != getRecordSize()) + throw new RecordFormatException(bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize()); + return bytesWritten; + } - // 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() - ); - } + /** + * 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; + } - convertRecordsToUserModelRecursive(tcc, patriarch, null); + 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) { + } - // Now, clear any trace of what records make up - // the patriarch - // Otherwise, everything will go horribly wrong - // when we try to write out again.... + 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; + } + 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]); + drawingManager.getDgg().setFileIdClusters(new EscherDggRecord.FileIdCluster[0]); - // TODO: Support converting our records - // back into shapes - // log.log(POILogger.WARN, "Not processing objects into Patriarch!"); - } + // 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); + // 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." ); - } + EscherContainerRecord groupContainer = (EscherContainerRecord) shapeContainer.getChild(0); + convertRecordsToUserModel(groupContainer, group); - } else if (shapeContainer.getRecordId() == EscherContainerRecord.SP_CONTAINER) - { - EscherSpRecord spRecord = shapeContainer - .getChildById( EscherSpRecord.RECORD_ID ); - int type = spRecord.getShapeType(); + if (shapeChildren > 1) { + convertRecordsToUserModelRecursive(shapeContainer.getChildRecords(), container, group); + } + } else { + log.log(POILogger.WARN, + "Found drawing group without children."); + } - switch (type) - { - case ST_TEXTBOX: - HSSFTextbox box = new HSSFTextbox( parent, - new HSSFClientAnchor() ); - addToParentOrContainer(box, container, parent); + } else if (shapeContainer.getRecordId() == EscherContainerRecord.SP_CONTAINER) { + EscherSpRecord spRecord = shapeContainer + .getChildById(EscherSpRecord.RECORD_ID); + int type = spRecord.getShapeType(); - 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(); + switch (type) { + case ST_TEXTBOX: + HSSFTextbox box = new HSSFTextbox(parent, + new HSSFClientAnchor()); + addToParentOrContainer(box, container, parent); - EscherClientAnchorRecord anchorRecord = (EscherClientAnchorRecord) getEscherChild( - shapeContainer, - EscherClientAnchorRecord.RECORD_ID ); + 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(); - EscherChildAnchorRecord childRecord = (EscherChildAnchorRecord) getEscherChild( - shapeContainer, - EscherChildAnchorRecord.RECORD_ID ); + EscherClientAnchorRecord anchorRecord = (EscherClientAnchorRecord) getEscherChild( + shapeContainer, + EscherClientAnchorRecord.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); - } + EscherChildAnchorRecord childRecord = (EscherChildAnchorRecord) getEscherChild( + shapeContainer, + EscherChildAnchorRecord.RECORD_ID); - HSSFPicture picture = new HSSFPicture( parent, anchor ); - picture.setPictureIndex( pictureIndex ); + if (anchorRecord != null && childRecord != null) { + log.log(POILogger.WARN, "Picture with both CLIENT and CHILD anchor: " + type); + } - 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." ); - } + 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; + 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 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; + 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); - } - } - } + // 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(); + public void clear() { + clearEscherRecords(); + shapeToObj.clear(); // lastShapeId = 1024; - } + } - protected String getRecordName() - { - return "ESCHERAGGREGATE"; - } + protected String getRecordName() { + return "ESCHERAGGREGATE"; + } - // =============== Private methods ======================== + // =============== Private methods ======================== - private static boolean isObjectRecord( List records, int loc ) - { - return sid( records, loc ) == ObjRecord.sid || sid( records, loc ) == TextObjectRecord.sid; - } + 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 ); + 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; - } - } - } + patriarch = null; + } + } + } - private void convertShapes( HSSFShapeContainer parent, EscherContainerRecord escherParent, Map shapeToObj ) - { - if ( escherParent == null ) throw new IllegalArgumentException( "Parent record required" ); + 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); + 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()); - } + if (shapeModel instanceof CommentShape) { + CommentShape comment = (CommentShape) shapeModel; + tailRec.add(comment.getNoteRecord()); + } - } - escherParent.addChildRecord( shapeModel.getSpContainer() ); - } - } + } + 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(); + 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 ) ); + 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() ); + 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 ); + 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 ); + 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 ); + 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 ); + escherParent.addChildRecord(spgrContainer); - convertShapes( shape, spgrContainer, shapeToObj ); + 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 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(); + 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(); + 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 ); + 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 ); + dgContainer.addChildRecord(dg); + dgContainer.addChildRecord(spgrContainer); + spgrContainer.addChildRecord(spContainer1); + spContainer1.addChildRecord(spgr); + spContainer1.addChildRecord(sp1); - addEscherRecord( dgContainer ); - } + addEscherRecord(dgContainer); + } - private static short sid( List records, int loc ) - { - return ( (Record) records.get( loc ) ).getSid(); - } + private static short sid(List records, int loc) { + return ((Record) records.get(loc)).getSid(); + } - // Duplicated from org.apache.poi.hslf.model.Shape + // 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; - } + /** + * 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 344ad07ef..aac88b80c 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 c5aa5855a..a22ce4a0c 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,125 +26,27 @@ 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 { - /** - * 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) - */ - public static EscherAggregate assertWriteAndReadBack(EscherAggregate agg) { - byte[] dgBytes = agg.serialize(); - - - List dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes)); - - DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord()); - - // 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); - - - 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); - - assertEquals(agg.getEscherRecords().size(), agg2.getEscherRecords().size()); - - // 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); - - assertEquals(r1.toXml(), r2.toXml()); - } - - return agg2; - } - - /** - * assert that mapping of Obj records to escher shape containers is the same in both aggregates - */ - 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()); - } - } - - /** - * 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); - } + 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); + } } - } else { - for (EscherRecord ch : parent.getChildRecords()) { - mapShapeContainers(ch, map); - } - } + return out.toByteArray(); } - } /** * test reading drawing aggregate from a test file from Bugzilla 45129 @@ -170,7 +68,10 @@ public class TestDrawingAggregate extends TestCase { records.get(18) instanceof RowRecordsAggregate); // records to be aggregated - List dgRecords = records.subList(19, 388); + List dgRecords = records.subList(19, 389); + // collect drawing records into a byte buffer. + byte[] dgBytes = toByteArray(dgRecords); + for (RecordBase rb : dgRecords) { Record r = (Record) rb; short sid = r.getSid(); @@ -203,11 +104,162 @@ 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 brefpore and after save is different", Arrays.equals(dgBytes, dgBytesAfterSave)); } + /** + * Try to check file with such record sequence + * ... + * DrawingRecord + * ContinueRecord + * ObjRecord | TextObjRecord + * ... + */ + 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)); + + + } + + + 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())); + } + } + + } + + 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", 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, 39); + 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(39) is expected to be Window2", + records.get(39) 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", 27, 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)); + } + + 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 4eed03d5e..000000000 --- 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 f207a0046..38992c0bd 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 000000000..8be6c9e52 Binary files /dev/null and b/test-data/spreadsheet/DrawingAndComments.xls differ diff --git a/test-data/spreadsheet/DrawingContinue.xls b/test-data/spreadsheet/DrawingContinue.xls new file mode 100755 index 000000000..131c18cc9 Binary files /dev/null and b/test-data/spreadsheet/DrawingContinue.xls differ