mirror of https://github.com/apache/poi.git
#64036 - Replace reflection calls in factories for Java 9+ - Escher factories
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1873187 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
563c29f8cf
commit
8202a34d69
|
@ -17,11 +17,12 @@
|
|||
|
||||
package org.apache.poi.ddf;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.poi.util.BitField;
|
||||
import org.apache.poi.util.BitFieldFactory;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
import org.apache.poi.util.Removal;
|
||||
|
||||
/**
|
||||
* Generates escher records when provided the byte array containing those records.
|
||||
|
@ -29,14 +30,7 @@ import org.apache.poi.util.LittleEndian;
|
|||
* @see EscherRecordFactory
|
||||
*/
|
||||
public class DefaultEscherRecordFactory implements EscherRecordFactory {
|
||||
private static Class<?>[] escherRecordClasses = { EscherBSERecord.class,
|
||||
EscherOptRecord.class, EscherTertiaryOptRecord.class,
|
||||
EscherClientAnchorRecord.class, EscherDgRecord.class,
|
||||
EscherSpgrRecord.class, EscherSpRecord.class,
|
||||
EscherClientDataRecord.class, EscherDggRecord.class,
|
||||
EscherSplitMenuColorsRecord.class, EscherChildAnchorRecord.class,
|
||||
EscherTextboxRecord.class };
|
||||
private static Map<Short, Constructor<? extends EscherRecord>> recordsMap = recordsToMap( escherRecordClasses );
|
||||
private static final BitField IS_CONTAINER = BitFieldFactory.getInstance(0xF);
|
||||
|
||||
/**
|
||||
* Creates an instance of the escher record factory
|
||||
|
@ -51,86 +45,41 @@ public class DefaultEscherRecordFactory implements EscherRecordFactory {
|
|||
short recordId = LittleEndian.getShort( data, offset + 2 );
|
||||
// int remainingBytes = LittleEndian.getInt( data, offset + 4 );
|
||||
|
||||
// Options of 0x000F means container record
|
||||
// However, EscherTextboxRecord are containers of records for the
|
||||
// host application, not of other Escher records, so treat them
|
||||
// differently
|
||||
if (isContainer(options, recordId)) {
|
||||
EscherContainerRecord r = new EscherContainerRecord();
|
||||
r.setRecordId( recordId );
|
||||
r.setOptions( options );
|
||||
return r;
|
||||
}
|
||||
|
||||
if (recordId >= EscherBlipRecord.RECORD_ID_START
|
||||
&& recordId <= EscherBlipRecord.RECORD_ID_END) {
|
||||
EscherBlipRecord r;
|
||||
if (recordId == EscherBitmapBlip.RECORD_ID_DIB ||
|
||||
recordId == EscherBitmapBlip.RECORD_ID_JPEG ||
|
||||
recordId == EscherBitmapBlip.RECORD_ID_PNG)
|
||||
{
|
||||
r = new EscherBitmapBlip();
|
||||
}
|
||||
else if (recordId == EscherMetafileBlip.RECORD_ID_EMF ||
|
||||
recordId == EscherMetafileBlip.RECORD_ID_WMF ||
|
||||
recordId == EscherMetafileBlip.RECORD_ID_PICT)
|
||||
{
|
||||
r = new EscherMetafileBlip();
|
||||
} else {
|
||||
r = new EscherBlipRecord();
|
||||
}
|
||||
r.setRecordId( recordId );
|
||||
r.setOptions( options );
|
||||
return r;
|
||||
}
|
||||
|
||||
Constructor<? extends EscherRecord> recordConstructor = recordsMap.get(Short.valueOf(recordId));
|
||||
final EscherRecord escherRecord;
|
||||
if (recordConstructor == null) {
|
||||
return new UnknownEscherRecord();
|
||||
}
|
||||
try {
|
||||
escherRecord = recordConstructor.newInstance();
|
||||
} catch (Exception e) {
|
||||
return new UnknownEscherRecord();
|
||||
}
|
||||
final EscherRecord escherRecord = getConstructor(options, recordId).get();
|
||||
escherRecord.setRecordId(recordId);
|
||||
escherRecord.setOptions(options);
|
||||
return escherRecord;
|
||||
}
|
||||
|
||||
protected Supplier<? extends EscherRecord> getConstructor(short options, short recordId) {
|
||||
EscherRecordTypes recordTypes = EscherRecordTypes.forTypeID(recordId);
|
||||
|
||||
// Options of 0x000F means container record
|
||||
// However, EscherTextboxRecord are containers of records for the host application,
|
||||
// not of other Escher records, but those are returned by the above anyway
|
||||
if (recordTypes == EscherRecordTypes.UNKNOWN && IS_CONTAINER.isAllSet(options)) {
|
||||
return EscherContainerRecord::new;
|
||||
}
|
||||
|
||||
if (recordTypes.constructor != null) {
|
||||
return recordTypes.constructor;
|
||||
}
|
||||
|
||||
// handle unknown blip records
|
||||
if (EscherBlipRecord.RECORD_ID_START <= recordId && recordId <= EscherBlipRecord.RECORD_ID_END) {
|
||||
return EscherBlipRecord::new;
|
||||
}
|
||||
|
||||
// catch all
|
||||
return UnknownEscherRecord::new;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Converts from a list of classes into a map that contains the record id as the key and
|
||||
* the Constructor in the value part of the map. It does this by using reflection to look up
|
||||
* the RECORD_ID field then using reflection again to find a reference to the constructor.
|
||||
*
|
||||
* @param recClasses The records to convert
|
||||
* @return The map containing the id/constructor pairs.
|
||||
* @deprecated this method is not used anymore to identify container records
|
||||
*/
|
||||
protected static Map<Short, Constructor<? extends EscherRecord>> recordsToMap(Class<?>[] recClasses) {
|
||||
Map<Short, Constructor<? extends EscherRecord>> result = new HashMap<>();
|
||||
final Class<?>[] EMPTY_CLASS_ARRAY = new Class[0];
|
||||
|
||||
for (Class<?> recClass : recClasses) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends EscherRecord> recCls = (Class<? extends EscherRecord>) recClass;
|
||||
short sid;
|
||||
try {
|
||||
sid = recCls.getField("RECORD_ID").getShort(null);
|
||||
} catch (IllegalArgumentException | NoSuchFieldException | IllegalAccessException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
Constructor<? extends EscherRecord> constructor;
|
||||
try {
|
||||
constructor = recCls.getConstructor(EMPTY_CLASS_ARRAY);
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
result.put(Short.valueOf(sid), constructor);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Removal(version = "5.0.0")
|
||||
public static boolean isContainer(short options, short recordId){
|
||||
if(recordId >= EscherContainerRecord.DGG_CONTAINER && recordId
|
||||
<= EscherContainerRecord.SOLVER_CONTAINER){
|
||||
|
|
|
@ -30,11 +30,13 @@ import org.apache.poi.util.LittleEndian;
|
|||
* shape within a container.
|
||||
*/
|
||||
public class EscherClientDataRecord extends EscherRecord {
|
||||
//arbitrarily selected; may need to increase
|
||||
private static final int MAX_RECORD_LENGTH = 100_000;
|
||||
|
||||
public static final short RECORD_ID = EscherRecordTypes.CLIENT_DATA.typeID;
|
||||
|
||||
//arbitrarily selected; may need to increase
|
||||
private static final int MAX_RECORD_LENGTH = 100_000;
|
||||
private static final byte[] EMPTY = {};
|
||||
|
||||
private byte[] remainingData;
|
||||
|
||||
public EscherClientDataRecord() {}
|
||||
|
@ -48,7 +50,7 @@ public class EscherClientDataRecord extends EscherRecord {
|
|||
public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
|
||||
int bytesRemaining = readHeader( data, offset );
|
||||
int pos = offset + 8;
|
||||
remainingData = IOUtils.safelyAllocate(bytesRemaining, MAX_RECORD_LENGTH);
|
||||
remainingData = (bytesRemaining == 0) ? EMPTY : IOUtils.safelyAllocate(bytesRemaining, MAX_RECORD_LENGTH);
|
||||
System.arraycopy( data, pos, remainingData, 0, bytesRemaining );
|
||||
return 8 + bytesRemaining;
|
||||
}
|
||||
|
@ -58,7 +60,7 @@ public class EscherClientDataRecord extends EscherRecord {
|
|||
listener.beforeRecordSerialize( offset, getRecordId(), this );
|
||||
|
||||
if (remainingData == null) {
|
||||
remainingData = new byte[0];
|
||||
remainingData = EMPTY;
|
||||
}
|
||||
LittleEndian.putShort( data, offset, getOptions() );
|
||||
LittleEndian.putShort( data, offset + 2, getRecordId() );
|
||||
|
|
|
@ -1,322 +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.ddf;
|
||||
|
||||
import java.awt.Dimension;
|
||||
import java.awt.Rectangle;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
|
||||
public final class EscherPictBlip extends EscherBlipRecord {
|
||||
private static final POILogger log = POILogFactory.getLogger(EscherPictBlip.class);
|
||||
//arbitrarily selected; may need to increase
|
||||
private static final int MAX_RECORD_LENGTH = 100_000;
|
||||
|
||||
public static final short RECORD_ID_EMF = EscherRecordTypes.BLIP_EMF.typeID;
|
||||
public static final short RECORD_ID_WMF = EscherRecordTypes.BLIP_WMF.typeID;
|
||||
public static final short RECORD_ID_PICT = EscherRecordTypes.BLIP_PICT.typeID;
|
||||
|
||||
private static final int HEADER_SIZE = 8;
|
||||
|
||||
private final byte[] field_1_UID = new byte[16];
|
||||
private int field_2_cb;
|
||||
private int field_3_rcBounds_x1;
|
||||
private int field_3_rcBounds_y1;
|
||||
private int field_3_rcBounds_x2;
|
||||
private int field_3_rcBounds_y2;
|
||||
private int field_4_ptSize_w;
|
||||
private int field_4_ptSize_h;
|
||||
private int field_5_cbSave;
|
||||
private byte field_6_fCompression;
|
||||
private byte field_7_fFilter;
|
||||
|
||||
private byte[] raw_pictureData;
|
||||
|
||||
public EscherPictBlip() {}
|
||||
|
||||
public EscherPictBlip(EscherPictBlip other) {
|
||||
super(other);
|
||||
System.arraycopy(other.field_1_UID, 0, field_1_UID, 0, field_1_UID.length);
|
||||
field_2_cb = other.field_2_cb;
|
||||
field_3_rcBounds_x1 = other.field_3_rcBounds_x1;
|
||||
field_3_rcBounds_y1 = other.field_3_rcBounds_y1;
|
||||
field_3_rcBounds_x2 = other.field_3_rcBounds_x2;
|
||||
field_3_rcBounds_y2 = other.field_3_rcBounds_y2;
|
||||
field_4_ptSize_w = other.field_4_ptSize_w;
|
||||
field_4_ptSize_h = other.field_4_ptSize_h;
|
||||
field_5_cbSave = other.field_5_cbSave;
|
||||
field_6_fCompression = other.field_6_fCompression;
|
||||
field_7_fFilter = other.field_7_fFilter;
|
||||
raw_pictureData = (other.raw_pictureData == null) ? null : other.raw_pictureData.clone();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int fillFields(byte[] data, int offset, EscherRecordFactory recordFactory) {
|
||||
int bytesAfterHeader = readHeader(data, offset);
|
||||
int pos = offset + HEADER_SIZE;
|
||||
|
||||
System.arraycopy( data, pos, field_1_UID, 0, 16 ); pos += 16;
|
||||
field_2_cb = LittleEndian.getInt( data, pos ); pos += 4;
|
||||
field_3_rcBounds_x1 = LittleEndian.getInt( data, pos ); pos += 4;
|
||||
field_3_rcBounds_y1 = LittleEndian.getInt( data, pos ); pos += 4;
|
||||
field_3_rcBounds_x2 = LittleEndian.getInt( data, pos ); pos += 4;
|
||||
field_3_rcBounds_y2 = LittleEndian.getInt( data, pos ); pos += 4;
|
||||
field_4_ptSize_w = LittleEndian.getInt( data, pos ); pos += 4;
|
||||
field_4_ptSize_h = LittleEndian.getInt( data, pos ); pos += 4;
|
||||
field_5_cbSave = LittleEndian.getInt( data, pos ); pos += 4;
|
||||
field_6_fCompression = data[pos]; pos++;
|
||||
field_7_fFilter = data[pos]; pos++;
|
||||
|
||||
raw_pictureData = IOUtils.safelyAllocate(field_5_cbSave, MAX_RECORD_LENGTH);
|
||||
System.arraycopy( data, pos, raw_pictureData, 0, field_5_cbSave );
|
||||
|
||||
// 0 means DEFLATE compression
|
||||
// 0xFE means no compression
|
||||
if (field_6_fCompression == 0)
|
||||
{
|
||||
super.setPictureData(inflatePictureData(raw_pictureData));
|
||||
}
|
||||
else
|
||||
{
|
||||
super.setPictureData(raw_pictureData);
|
||||
}
|
||||
|
||||
return bytesAfterHeader + HEADER_SIZE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int serialize(int offset, byte[] data, EscherSerializationListener listener) {
|
||||
listener.beforeRecordSerialize(offset, getRecordId(), this);
|
||||
|
||||
int pos = offset;
|
||||
LittleEndian.putShort( data, pos, getOptions() ); pos += 2;
|
||||
LittleEndian.putShort( data, pos, getRecordId() ); pos += 2;
|
||||
LittleEndian.putInt( data, 0, getRecordSize() - HEADER_SIZE ); pos += 4;
|
||||
|
||||
System.arraycopy( field_1_UID, 0, data, pos, 16 ); pos += 16;
|
||||
LittleEndian.putInt( data, pos, field_2_cb ); pos += 4;
|
||||
LittleEndian.putInt( data, pos, field_3_rcBounds_x1 ); pos += 4;
|
||||
LittleEndian.putInt( data, pos, field_3_rcBounds_y1 ); pos += 4;
|
||||
LittleEndian.putInt( data, pos, field_3_rcBounds_x2 ); pos += 4;
|
||||
LittleEndian.putInt( data, pos, field_3_rcBounds_y2 ); pos += 4;
|
||||
LittleEndian.putInt( data, pos, field_4_ptSize_w ); pos += 4;
|
||||
LittleEndian.putInt( data, pos, field_4_ptSize_h ); pos += 4;
|
||||
LittleEndian.putInt( data, pos, field_5_cbSave ); pos += 4;
|
||||
data[pos] = field_6_fCompression; pos++;
|
||||
data[pos] = field_7_fFilter; pos++;
|
||||
|
||||
System.arraycopy( raw_pictureData, 0, data, pos, raw_pictureData.length );
|
||||
|
||||
listener.afterRecordSerialize(offset + getRecordSize(), getRecordId(), getRecordSize(), this);
|
||||
return HEADER_SIZE + 16 + 1 + raw_pictureData.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses the provided data, returning the inflated result.
|
||||
*
|
||||
* @param data the deflated picture data.
|
||||
* @return the inflated picture data.
|
||||
*/
|
||||
private static byte[] inflatePictureData(byte[] data) {
|
||||
try {
|
||||
InflaterInputStream in = new InflaterInputStream(new ByteArrayInputStream(data));
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
byte[] buf = new byte[4096];
|
||||
int readBytes;
|
||||
while ((readBytes = in.read(buf)) > 0) {
|
||||
out.write(buf, 0, readBytes);
|
||||
}
|
||||
return out.toByteArray();
|
||||
} catch (IOException e) {
|
||||
log.log(POILogger.INFO, "Possibly corrupt compression or non-compressed data", e);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRecordSize() {
|
||||
return 8 + 50 + raw_pictureData.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the first MD4, that specifies the unique identifier of the
|
||||
* uncompressed blip data
|
||||
*
|
||||
* @return the first MD4
|
||||
*/
|
||||
public byte[] getUID() {
|
||||
return field_1_UID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the first MD4, that specifies the unique identifier of the
|
||||
* uncompressed blip data
|
||||
*
|
||||
* @param uid the first MD4
|
||||
*/
|
||||
public void setUID(byte[] uid) {
|
||||
if (uid == null || uid.length != 16) {
|
||||
throw new IllegalArgumentException("uid must be byte[16]");
|
||||
}
|
||||
System.arraycopy(uid, 0, field_1_UID, 0, field_1_UID.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the uncompressed size (in bytes)
|
||||
*
|
||||
* @return the uncompressed size
|
||||
*/
|
||||
public int getUncompressedSize() {
|
||||
return field_2_cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the uncompressed size (in bytes)
|
||||
*
|
||||
* @param uncompressedSize the uncompressed size
|
||||
*/
|
||||
public void setUncompressedSize(int uncompressedSize) {
|
||||
field_2_cb = uncompressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the clipping region of the pict file
|
||||
*
|
||||
* @return the clipping region
|
||||
*/
|
||||
public Rectangle getBounds() {
|
||||
return new Rectangle(field_3_rcBounds_x1,
|
||||
field_3_rcBounds_y1,
|
||||
field_3_rcBounds_x2 - field_3_rcBounds_x1,
|
||||
field_3_rcBounds_y2 - field_3_rcBounds_y1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the clipping region
|
||||
*
|
||||
* @param bounds the clipping region
|
||||
*/
|
||||
public void setBounds(Rectangle bounds) {
|
||||
field_3_rcBounds_x1 = bounds.x;
|
||||
field_3_rcBounds_y1 = bounds.y;
|
||||
field_3_rcBounds_x2 = bounds.x + bounds.width;
|
||||
field_3_rcBounds_y2 = bounds.y + bounds.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dimensions of the metafile
|
||||
*
|
||||
* @return the dimensions of the metafile
|
||||
*/
|
||||
public Dimension getSizeEMU() {
|
||||
return new Dimension(field_4_ptSize_w, field_4_ptSize_h);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the dimensions of the metafile
|
||||
*
|
||||
* @param sizeEMU the dimensions of the metafile
|
||||
*/
|
||||
public void setSizeEMU(Dimension sizeEMU) {
|
||||
field_4_ptSize_w = sizeEMU.width;
|
||||
field_4_ptSize_h = sizeEMU.height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compressed size of the metafile (in bytes)
|
||||
*
|
||||
* @return the compressed size
|
||||
*/
|
||||
public int getCompressedSize() {
|
||||
return field_5_cbSave;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compressed size of the metafile (in bytes)
|
||||
*
|
||||
* @param compressedSize the compressed size
|
||||
*/
|
||||
public void setCompressedSize(int compressedSize) {
|
||||
field_5_cbSave = compressedSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the compression of the metafile
|
||||
*
|
||||
* @return true, if the metafile is compressed
|
||||
*/
|
||||
public boolean isCompressed() {
|
||||
return (field_6_fCompression == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the compression of the metafile
|
||||
*
|
||||
* @param compressed the compression state, true if it's compressed
|
||||
*/
|
||||
public void setCompressed(boolean compressed) {
|
||||
field_6_fCompression = compressed ? 0 : (byte)0xFE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the filter byte - this is usually 0xFE
|
||||
*
|
||||
* @return the filter byte
|
||||
*/
|
||||
public byte getFilter() {
|
||||
return field_7_fFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the filter byte - this is usually 0xFE
|
||||
*
|
||||
* @param filter the filter byte
|
||||
*/
|
||||
public void setFilter(byte filter) {
|
||||
field_7_fFilter = filter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Supplier<?>> getGenericProperties() {
|
||||
final Map<String, Supplier<?>> m = new LinkedHashMap<>(super.getGenericProperties());
|
||||
m.put("uid", this::getUID);
|
||||
m.put("uncompressedSize", this::getUncompressedSize);
|
||||
m.put("bounds", this::getBounds);
|
||||
m.put("sizeInEMU", this::getSizeEMU);
|
||||
m.put("compressedSize", this::getCompressedSize);
|
||||
m.put("isCompressed", this::isCompressed);
|
||||
m.put("filter", this::getFilter);
|
||||
return Collections.unmodifiableMap(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EscherPictBlip copy() {
|
||||
return new EscherPictBlip(this);
|
||||
}
|
||||
}
|
|
@ -19,62 +19,65 @@ package org.apache.poi.ddf;
|
|||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public enum EscherRecordTypes {
|
||||
// records greater then 0xF000 belong to with Microsoft Office Drawing format also known as Escher
|
||||
DGG_CONTAINER(0xF000, "DggContainer", null),
|
||||
BSTORE_CONTAINER(0xf001, "BStoreContainer", null),
|
||||
DG_CONTAINER(0xf002, "DgContainer", null),
|
||||
SPGR_CONTAINER(0xf003, "SpgrContainer", null),
|
||||
SP_CONTAINER(0xf004, "SpContainer", null),
|
||||
SOLVER_CONTAINER(0xf005, "SolverContainer", null),
|
||||
DGG(0xf006, "Dgg", "MsofbtDgg"),
|
||||
BSE(0xf007, "BSE", "MsofbtBSE"),
|
||||
DG(0xf008, "Dg", "MsofbtDg"),
|
||||
SPGR(0xf009, "Spgr", "MsofbtSpgr"),
|
||||
SP(0xf00a, "Sp", "MsofbtSp"),
|
||||
OPT(0xf00b, "Opt", "msofbtOPT"),
|
||||
TEXTBOX(0xf00c, null, null),
|
||||
CLIENT_TEXTBOX(0xf00d, "ClientTextbox", "msofbtClientTextbox"),
|
||||
ANCHOR(0xf00e, null, null),
|
||||
CHILD_ANCHOR(0xf00f, "ChildAnchor", "MsofbtChildAnchor"),
|
||||
CLIENT_ANCHOR(0xf010, "ClientAnchor", "MsofbtClientAnchor"),
|
||||
CLIENT_DATA(0xf011, "ClientData", "MsofbtClientData"),
|
||||
CONNECTOR_RULE(0xf012, null, null),
|
||||
ALIGN_RULE(0xf013, null, null),
|
||||
ARC_RULE(0xf014, null, null),
|
||||
CLIENT_RULE(0xf015, null, null),
|
||||
CLSID(0xf016, null, null),
|
||||
CALLOUT_RULE(0xf017, null, null),
|
||||
BLIP_START(0xf018, "Blip", "msofbtBlip"),
|
||||
BLIP_EMF(0xf018 + 2, "BlipEmf", null),
|
||||
BLIP_WMF(0xf018 + 3, "BlipWmf", null),
|
||||
BLIP_PICT(0xf018 + 4, "BlipPict", null),
|
||||
BLIP_JPEG(0xf018 + 5, "BlipJpeg", null),
|
||||
BLIP_PNG(0xf018 + 6, "BlipPng", null),
|
||||
BLIP_DIB(0xf018 + 7, "BlipDib", null),
|
||||
BLIP_END(0xf117, "Blip", "msofbtBlip"),
|
||||
REGROUP_ITEMS(0xf118, null, null),
|
||||
SELECTION(0xf119, null, null),
|
||||
COLOR_MRU(0xf11a, null, null),
|
||||
DELETED_PSPL(0xf11d, null, null),
|
||||
SPLIT_MENU_COLORS(0xf11e, "SplitMenuColors", "MsofbtSplitMenuColors"),
|
||||
OLE_OBJECT(0xf11f, null, null),
|
||||
COLOR_SCHEME(0xf120, null, null),
|
||||
// records greater then 0xF000 belong to Microsoft Office Drawing format also known as Escher
|
||||
DGG_CONTAINER(0xF000, "DggContainer", null, EscherContainerRecord::new),
|
||||
BSTORE_CONTAINER(0xf001, "BStoreContainer", null, EscherContainerRecord::new),
|
||||
DG_CONTAINER(0xf002, "DgContainer", null, EscherContainerRecord::new),
|
||||
SPGR_CONTAINER(0xf003, "SpgrContainer", null, EscherContainerRecord::new),
|
||||
SP_CONTAINER(0xf004, "SpContainer", null, EscherContainerRecord::new),
|
||||
SOLVER_CONTAINER(0xf005, "SolverContainer", null, EscherContainerRecord::new),
|
||||
DGG(0xf006, "Dgg", "MsofbtDgg", EscherDggRecord::new),
|
||||
BSE(0xf007, "BSE", "MsofbtBSE", EscherBSERecord::new),
|
||||
DG(0xf008, "Dg", "MsofbtDg", EscherDgRecord::new),
|
||||
SPGR(0xf009, "Spgr", "MsofbtSpgr", EscherSpgrRecord::new),
|
||||
SP(0xf00a, "Sp", "MsofbtSp", EscherSpRecord::new),
|
||||
OPT(0xf00b, "Opt", "msofbtOPT", EscherOptRecord::new),
|
||||
TEXTBOX(0xf00c, null, null, EscherTextboxRecord::new),
|
||||
CLIENT_TEXTBOX(0xf00d, "ClientTextbox", "msofbtClientTextbox", EscherTextboxRecord::new),
|
||||
ANCHOR(0xf00e, null, null, null),
|
||||
CHILD_ANCHOR(0xf00f, "ChildAnchor", "MsofbtChildAnchor", EscherChildAnchorRecord::new),
|
||||
CLIENT_ANCHOR(0xf010, "ClientAnchor", "MsofbtClientAnchor", EscherClientAnchorRecord::new),
|
||||
CLIENT_DATA(0xf011, "ClientData", "MsofbtClientData", EscherClientDataRecord::new),
|
||||
CONNECTOR_RULE(0xf012, null, null, null),
|
||||
ALIGN_RULE(0xf013, null, null, null),
|
||||
ARC_RULE(0xf014, null, null, null),
|
||||
CLIENT_RULE(0xf015, null, null, null),
|
||||
CLSID(0xf016, null, null, null),
|
||||
CALLOUT_RULE(0xf017, null, null, null),
|
||||
BLIP_START(0xf018, "Blip", "msofbtBlip", null),
|
||||
BLIP_EMF(0xf018 + 2, "BlipEmf", null, EscherMetafileBlip::new),
|
||||
BLIP_WMF(0xf018 + 3, "BlipWmf", null, EscherMetafileBlip::new),
|
||||
BLIP_PICT(0xf018 + 4, "BlipPict", null, EscherMetafileBlip::new),
|
||||
BLIP_JPEG(0xf018 + 5, "BlipJpeg", null, EscherBitmapBlip::new),
|
||||
BLIP_PNG(0xf018 + 6, "BlipPng", null, EscherBitmapBlip::new),
|
||||
BLIP_DIB(0xf018 + 7, "BlipDib", null, EscherBitmapBlip::new),
|
||||
BLIP_END(0xf117, "Blip", "msofbtBlip", null),
|
||||
REGROUP_ITEMS(0xf118, null, null, null),
|
||||
SELECTION(0xf119, null, null, null),
|
||||
COLOR_MRU(0xf11a, null, null, null),
|
||||
DELETED_PSPL(0xf11d, null, null, null),
|
||||
SPLIT_MENU_COLORS(0xf11e, "SplitMenuColors", "MsofbtSplitMenuColors", EscherSplitMenuColorsRecord::new),
|
||||
OLE_OBJECT(0xf11f, null, null, null),
|
||||
COLOR_SCHEME(0xf120, null, null, null),
|
||||
// same as EscherTertiaryOptRecord.RECORD_ID
|
||||
USER_DEFINED(0xf122, "TertiaryOpt", null),
|
||||
UNKNOWN(0xffff, "unknown", "unknown");
|
||||
USER_DEFINED(0xf122, "TertiaryOpt", null, EscherTertiaryOptRecord::new),
|
||||
UNKNOWN(0xffff, "unknown", "unknown", UnknownEscherRecord::new);
|
||||
|
||||
public final short typeID;
|
||||
public final String recordName;
|
||||
public final String description;
|
||||
public final Supplier<? extends EscherRecord> constructor;
|
||||
|
||||
EscherRecordTypes(int typeID, String recordName, String description) {
|
||||
EscherRecordTypes(int typeID, String recordName, String description, Supplier<? extends EscherRecord> constructor) {
|
||||
this.typeID = (short) typeID;
|
||||
this.recordName = recordName;
|
||||
this.description = description;
|
||||
this.constructor = constructor;
|
||||
}
|
||||
|
||||
private Short getTypeId() {
|
||||
|
|
|
@ -31,7 +31,7 @@ public final class ContinueRecord extends StandardRecord {
|
|||
private byte[] _data;
|
||||
|
||||
public ContinueRecord(byte[] data) {
|
||||
_data = data;
|
||||
_data = data.clone();
|
||||
}
|
||||
|
||||
public ContinueRecord(ContinueRecord other) {
|
||||
|
|
|
@ -43,6 +43,10 @@ public final class DrawingRecord extends StandardRecord {
|
|||
recordData = in.readRemainder();
|
||||
}
|
||||
|
||||
public DrawingRecord(byte[] data) {
|
||||
recordData = data.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated POI 3.9
|
||||
*/
|
||||
|
|
|
@ -17,11 +17,15 @@
|
|||
|
||||
package org.apache.poi.hssf.record;
|
||||
|
||||
import static org.apache.poi.hssf.record.RecordInputStream.MAX_RECORD_DATA_SIZE;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -31,14 +35,11 @@ import org.apache.poi.ddf.EscherClientDataRecord;
|
|||
import org.apache.poi.ddf.EscherContainerRecord;
|
||||
import org.apache.poi.ddf.EscherDgRecord;
|
||||
import org.apache.poi.ddf.EscherRecord;
|
||||
import org.apache.poi.ddf.EscherRecordFactory;
|
||||
import org.apache.poi.ddf.EscherSerializationListener;
|
||||
import org.apache.poi.ddf.EscherSpRecord;
|
||||
import org.apache.poi.ddf.EscherSpgrRecord;
|
||||
import org.apache.poi.ddf.EscherTextboxRecord;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.POILogFactory;
|
||||
import org.apache.poi.util.POILogger;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
|
||||
/**
|
||||
|
@ -84,8 +85,8 @@ import org.apache.poi.util.RecordFormatException;
|
|||
*/
|
||||
|
||||
public final class EscherAggregate extends AbstractEscherHolderRecord {
|
||||
public static final short sid = 9876; // not a real sid - dummy value
|
||||
private static final POILogger log = POILogFactory.getLogger(EscherAggregate.class);
|
||||
// not a real sid - dummy value
|
||||
public static final short sid = 9876;
|
||||
//arbitrarily selected; may need to increase
|
||||
private static final int MAX_RECORD_LENGTH = 100_000_000;
|
||||
|
||||
|
@ -364,17 +365,6 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
return builder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param sid - record sid we want to check if it belongs to drawing layer
|
||||
* @return true if record is instance of DrawingRecord or ContinueRecord or ObjRecord or TextObjRecord
|
||||
*/
|
||||
private static boolean isDrawingLayerRecord(final short sid) {
|
||||
return sid == DrawingRecord.sid ||
|
||||
sid == ContinueRecord.sid ||
|
||||
sid == ObjRecord.sid ||
|
||||
sid == TextObjectRecord.sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapses the drawing records into an aggregate.
|
||||
* read Drawing, Obj, TxtObj, Note and Continue records into single byte array,
|
||||
|
@ -384,84 +374,85 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
* @param locFirstDrawingRecord - location of the first DrawingRecord inside sheet
|
||||
* @return new EscherAggregate create from all aggregated records which belong to drawing layer
|
||||
*/
|
||||
public static EscherAggregate createAggregate(List<RecordBase> records, int locFirstDrawingRecord) {
|
||||
// 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<EscherRecord> 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
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
public static EscherAggregate createAggregate(final List<RecordBase> records, final int locFirstDrawingRecord) {
|
||||
EscherAggregate agg = new EscherAggregate(false);
|
||||
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++;
|
||||
|
||||
ShapeCollector recordFactory = new ShapeCollector();
|
||||
List<Record> objectRecords = new ArrayList<>();
|
||||
|
||||
int nextIdx = locFirstDrawingRecord;
|
||||
for (RecordBase rb : records.subList(locFirstDrawingRecord, records.size())) {
|
||||
nextIdx++;
|
||||
switch (sid(rb)) {
|
||||
case DrawingRecord.sid:
|
||||
recordFactory.addBytes(((DrawingRecord)rb).getRecordData());
|
||||
continue;
|
||||
case ContinueRecord.sid:
|
||||
recordFactory.addBytes(((ContinueRecord)rb).getData());
|
||||
continue;
|
||||
case ObjRecord.sid:
|
||||
case TextObjectRecord.sid:
|
||||
objectRecords.add((org.apache.poi.hssf.record.Record)rb);
|
||||
continue;
|
||||
case NoteRecord.sid:
|
||||
// any NoteRecords that follow the drawing block must be aggregated and saved in the tailRec collection
|
||||
NoteRecord r = (NoteRecord)rb;
|
||||
agg.tailRec.put(r.getShapeId(), r);
|
||||
continue;
|
||||
default:
|
||||
nextIdx--;
|
||||
break;
|
||||
}
|
||||
if (sid(records, loc) == DrawingRecord.sid) {
|
||||
buffer.write(((DrawingRecord) records.get(loc)).getRecordData());
|
||||
} else {
|
||||
buffer.write(((ContinueRecord) records.get(loc)).getData());
|
||||
break;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't get data from drawing/continue records", e);
|
||||
}
|
||||
loc++;
|
||||
|
||||
// replace drawing block with the created EscherAggregate
|
||||
records.set(locFirstDrawingRecord, agg);
|
||||
if (locFirstDrawingRecord+1 <= nextIdx) {
|
||||
records.subList(locFirstDrawingRecord + 1, nextIdx).clear();
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
Iterator<EscherRecord> shapeIter = recordFactory.parse(agg).iterator();
|
||||
|
||||
// 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 = (org.apache.poi.hssf.record.Record) records.get(loc);
|
||||
agg.shapeToObj.put(shapeRecords.get(shapeIndex++), objRecord);
|
||||
loc++;
|
||||
}
|
||||
objectRecords.forEach(or -> agg.shapeToObj.put(shapeIter.next(), or));
|
||||
|
||||
// any NoteRecords that follow the drawing block must be aggregated and and saved in the tailRec collection
|
||||
while (loc < records.size()) {
|
||||
if (sid(records, loc) == NoteRecord.sid) {
|
||||
NoteRecord r = (NoteRecord) records.get(loc);
|
||||
agg.tailRec.put(r.getShapeId(), r);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
loc++;
|
||||
}
|
||||
|
||||
int locLastDrawingRecord = loc;
|
||||
// replace drawing block with the created EscherAggregate
|
||||
records.subList(locFirstDrawingRecord, locLastDrawingRecord).clear();
|
||||
records.add(locFirstDrawingRecord, agg);
|
||||
return agg;
|
||||
}
|
||||
|
||||
private static class ShapeCollector extends DefaultEscherRecordFactory {
|
||||
final List<EscherRecord> objShapes = new ArrayList<>();
|
||||
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
|
||||
void addBytes(byte[] data) {
|
||||
try {
|
||||
buffer.write(data);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't get data from drawing/continue records", e);
|
||||
}
|
||||
}
|
||||
|
||||
public EscherRecord createRecord(byte[] data, int offset) {
|
||||
EscherRecord r = super.createRecord(data, offset);
|
||||
short rid = r.getRecordId();
|
||||
if (rid == EscherClientDataRecord.RECORD_ID || rid == EscherTextboxRecord.RECORD_ID) {
|
||||
objShapes.add(r);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
List<EscherRecord> parse(EscherAggregate agg) {
|
||||
byte[] buf = buffer.toByteArray();
|
||||
for (int pos = 0, bytesRead; pos < buf.length; pos += bytesRead) {
|
||||
EscherRecord r = createRecord(buf, pos);
|
||||
bytesRead = r.fillFields(buf, pos, this);
|
||||
agg.addEscherRecord(r);
|
||||
}
|
||||
return objShapes;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes this aggregate to a byte array. Since this is an aggregate
|
||||
* record it will effectively serialize the aggregated records.
|
||||
|
@ -470,7 +461,7 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
* @param data The byte array to serialize to.
|
||||
* @return The number of bytes serialized.
|
||||
*/
|
||||
public int serialize(int offset, byte[] data) {
|
||||
public int serialize(final int offset, final byte[] data) {
|
||||
// Determine buffer size
|
||||
List <EscherRecord>records = getEscherRecords();
|
||||
int size = getEscherRecordSize(records);
|
||||
|
@ -501,18 +492,14 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
// the first one because it's the patriach).
|
||||
pos = offset;
|
||||
int writtenEscherBytes = 0;
|
||||
int i;
|
||||
for (i = 1; i < shapes.size(); i++) {
|
||||
int endOffset = spEndingOffsets.get(i) - 1;
|
||||
int startOffset;
|
||||
if (i == 1)
|
||||
startOffset = 0;
|
||||
else
|
||||
startOffset = spEndingOffsets.get(i - 1);
|
||||
boolean isFirst = true;
|
||||
int endOffset = 0;
|
||||
for (int i = 1; i < shapes.size(); i++) {
|
||||
int startOffset = endOffset;
|
||||
endOffset = spEndingOffsets.get(i);
|
||||
|
||||
byte[] drawingData = new byte[endOffset - startOffset + 1];
|
||||
System.arraycopy(buffer, startOffset, drawingData, 0, drawingData.length);
|
||||
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i);
|
||||
byte[] drawingData = Arrays.copyOfRange(buffer, startOffset, endOffset);
|
||||
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, isFirst);
|
||||
|
||||
writtenEscherBytes += drawingData.length;
|
||||
|
||||
|
@ -520,24 +507,22 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
Record obj = shapeToObj.get(shapes.get(i));
|
||||
pos += obj.serialize(pos, data);
|
||||
|
||||
if (i == shapes.size() - 1 && endOffset < buffer.length - 1) {
|
||||
drawingData = new byte[buffer.length - endOffset - 1];
|
||||
System.arraycopy(buffer, endOffset + 1, drawingData, 0, drawingData.length);
|
||||
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i);
|
||||
isFirst = false;
|
||||
}
|
||||
}
|
||||
if ((pos - offset) < buffer.length - 1) {
|
||||
byte[] drawingData = new byte[buffer.length - (pos - offset)];
|
||||
System.arraycopy(buffer, (pos - offset), drawingData, 0, drawingData.length);
|
||||
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, i);
|
||||
|
||||
if (endOffset < buffer.length - 1) {
|
||||
byte[] drawingData = Arrays.copyOfRange(buffer, endOffset, buffer.length);
|
||||
pos += writeDataIntoDrawingRecord(drawingData, writtenEscherBytes, pos, data, isFirst);
|
||||
}
|
||||
|
||||
for (NoteRecord noteRecord : tailRec.values()) {
|
||||
pos += noteRecord.serialize(pos, data);
|
||||
}
|
||||
|
||||
int bytesWritten = pos - offset;
|
||||
if (bytesWritten != getRecordSize())
|
||||
if (bytesWritten != getRecordSize()) {
|
||||
throw new RecordFormatException(bytesWritten + " bytes written but getRecordSize() reports " + getRecordSize());
|
||||
}
|
||||
return bytesWritten;
|
||||
}
|
||||
|
||||
|
@ -547,34 +532,19 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
* drawing or continue record)
|
||||
* @param pos current position of data array
|
||||
* @param data - array of bytes where drawing records must be serialized
|
||||
* @param i - number of shape, saved into data array
|
||||
* @param isFirst - is it the first shape, saved into data array
|
||||
* @return offset of data array after serialization
|
||||
*/
|
||||
private int writeDataIntoDrawingRecord(byte[] drawingData, int writtenEscherBytes, int pos, byte[] data, int i) {
|
||||
private int writeDataIntoDrawingRecord(final byte[] drawingData, final int writtenEscherBytes, final int pos, final byte[] data, final boolean isFirst) {
|
||||
int temp = 0;
|
||||
//First record in drawing layer MUST be DrawingRecord
|
||||
if (writtenEscherBytes + drawingData.length > RecordInputStream.MAX_RECORD_DATA_SIZE && i != 1) {
|
||||
for (int j = 0; j < drawingData.length; j += RecordInputStream.MAX_RECORD_DATA_SIZE) {
|
||||
byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)];
|
||||
System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j));
|
||||
ContinueRecord drawing = new ContinueRecord(buf);
|
||||
boolean useDrawingRecord = isFirst || (writtenEscherBytes + drawingData.length) <= MAX_RECORD_DATA_SIZE;
|
||||
|
||||
for (int j = 0; j < drawingData.length; j += MAX_RECORD_DATA_SIZE) {
|
||||
byte[] buf = Arrays.copyOfRange(drawingData, j, Math.min(j+MAX_RECORD_DATA_SIZE, drawingData.length));
|
||||
Record drawing = (useDrawingRecord) ? new DrawingRecord(buf) : new ContinueRecord(buf);
|
||||
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();
|
||||
byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)];
|
||||
System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j));
|
||||
drawing.setData(buf);
|
||||
temp += drawing.serialize(pos + temp, data);
|
||||
} else {
|
||||
byte[] buf = new byte[Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j)];
|
||||
System.arraycopy(drawingData, j, buf, 0, Math.min(RecordInputStream.MAX_RECORD_DATA_SIZE, drawingData.length - j));
|
||||
ContinueRecord drawing = new ContinueRecord(buf);
|
||||
temp += drawing.serialize(pos + temp, data);
|
||||
}
|
||||
}
|
||||
useDrawingRecord = false;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
|
@ -624,14 +594,15 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
if (i == spEndingOffsets.size() - 1 && spEndingOffsets.get(i) < pos) {
|
||||
continueRecordsHeadersSize += 4;
|
||||
}
|
||||
if (spEndingOffsets.get(i) - spEndingOffsets.get(i - 1) <= RecordInputStream.MAX_RECORD_DATA_SIZE) {
|
||||
if (spEndingOffsets.get(i) - spEndingOffsets.get(i - 1) <= MAX_RECORD_DATA_SIZE) {
|
||||
continue;
|
||||
}
|
||||
continueRecordsHeadersSize += ((spEndingOffsets.get(i) - spEndingOffsets.get(i - 1)) / RecordInputStream.MAX_RECORD_DATA_SIZE) * 4;
|
||||
continueRecordsHeadersSize += ((spEndingOffsets.get(i) - spEndingOffsets.get(i - 1)) / MAX_RECORD_DATA_SIZE) * 4;
|
||||
}
|
||||
|
||||
int drawingRecordSize = rawEscherSize + (shapeToObj.size()) * 4;
|
||||
if (rawEscherSize != 0 && spEndingOffsets.size() == 1/**EMPTY**/) {
|
||||
if (rawEscherSize != 0 && spEndingOffsets.size() == 1) {
|
||||
// EMPTY
|
||||
continueRecordsHeadersSize += 4;
|
||||
}
|
||||
int objRecordSize = 0;
|
||||
|
@ -671,16 +642,6 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
|
||||
// =============== Private methods ========================
|
||||
|
||||
/**
|
||||
*
|
||||
* @param records list of the record to look inside
|
||||
* @param loc location of the checked record
|
||||
* @return true if record is instance of ObjRecord or TextObjectRecord
|
||||
*/
|
||||
private static boolean isObjectRecord(List <RecordBase>records, int loc) {
|
||||
return sid(records, loc) == ObjRecord.sid || sid(records, loc) == TextObjectRecord.sid;
|
||||
}
|
||||
|
||||
/**
|
||||
* create base tree with such structure:
|
||||
* EscherDgContainer
|
||||
|
@ -741,8 +702,10 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
public void setDgId(short dgId) {
|
||||
EscherContainerRecord dgContainer = getEscherContainer();
|
||||
EscherDgRecord dg = dgContainer.getChildById(EscherDgRecord.RECORD_ID);
|
||||
if (dg != null) {
|
||||
dg.setOptions((short) (dgId << 4));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* EscherDgContainer
|
||||
|
@ -757,26 +720,26 @@ public final class EscherAggregate extends AbstractEscherHolderRecord {
|
|||
*/
|
||||
public void setMainSpRecordId(int shapeId) {
|
||||
EscherContainerRecord dgContainer = getEscherContainer();
|
||||
EscherContainerRecord spgrConatiner = dgContainer.getChildById(EscherContainerRecord.SPGR_CONTAINER);
|
||||
EscherContainerRecord spContainer = (EscherContainerRecord) spgrConatiner.getChild(0);
|
||||
EscherContainerRecord spgrContainer = dgContainer.getChildById(EscherContainerRecord.SPGR_CONTAINER);
|
||||
if (spgrContainer != null) {
|
||||
EscherContainerRecord spContainer = (EscherContainerRecord) spgrContainer.getChild(0);
|
||||
EscherSpRecord sp = spContainer.getChildById(EscherSpRecord.RECORD_ID);
|
||||
if (sp != null) {
|
||||
sp.setShapeId(shapeId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param records list of records to look into
|
||||
* @param loc - location of the record which sid must be returned
|
||||
* @return sid of the record with selected location
|
||||
* @param record the record to look into
|
||||
* @return sid of the record
|
||||
*/
|
||||
private static short sid(List<RecordBase> records, int loc) {
|
||||
RecordBase record = records.get(loc);
|
||||
if (record instanceof Record) {
|
||||
return ((org.apache.poi.hssf.record.Record)record).getSid();
|
||||
} else {
|
||||
private static short sid(RecordBase record) {
|
||||
// Aggregates don't have a sid
|
||||
// We could step into them, but for these needs we don't care
|
||||
return -1;
|
||||
}
|
||||
return (record instanceof org.apache.poi.hssf.record.Record)
|
||||
? ((org.apache.poi.hssf.record.Record)record).getSid()
|
||||
: -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -58,11 +58,8 @@ public class HSSFShapeFactory {
|
|||
HSSFShapeGroup group = new HSSFShapeGroup(container, obj);
|
||||
List<EscherContainerRecord> children = container.getChildContainers();
|
||||
// skip the first child record, it is group descriptor
|
||||
for (int i = 0; i < children.size(); i++) {
|
||||
EscherContainerRecord spContainer = children.get(i);
|
||||
if (i != 0) {
|
||||
createShapeTree(spContainer, agg, group, root);
|
||||
}
|
||||
if (children.size() > 1) {
|
||||
children.subList(1, children.size()).forEach(c -> createShapeTree(c, agg, group, root));
|
||||
}
|
||||
out.addShape(group);
|
||||
} else if (container.getRecordId() == EscherContainerRecord.SP_CONTAINER) {
|
||||
|
|
|
@ -17,11 +17,11 @@
|
|||
|
||||
package org.apache.poi.hslf.record;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.util.Map;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.poi.ddf.*;
|
||||
import org.apache.poi.util.LittleEndian;
|
||||
import org.apache.poi.ddf.DefaultEscherRecordFactory;
|
||||
import org.apache.poi.ddf.EscherRecord;
|
||||
import org.apache.poi.ddf.EscherRecordFactory;
|
||||
|
||||
/**
|
||||
* Generates escher records when provided the byte array containing those records.
|
||||
|
@ -29,10 +29,6 @@ import org.apache.poi.util.LittleEndian;
|
|||
* @see EscherRecordFactory
|
||||
*/
|
||||
public class HSLFEscherRecordFactory extends DefaultEscherRecordFactory {
|
||||
private static Class<?>[] escherRecordClasses = { EscherPlaceholder.class, HSLFEscherClientDataRecord.class };
|
||||
private static Map<Short, Constructor<? extends EscherRecord>> recordsMap = recordsToMap( escherRecordClasses );
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance of the escher record factory
|
||||
*/
|
||||
|
@ -41,27 +37,12 @@ public class HSLFEscherRecordFactory extends DefaultEscherRecordFactory {
|
|||
}
|
||||
|
||||
@Override
|
||||
public EscherRecord createRecord(byte[] data, int offset) {
|
||||
short options = LittleEndian.getShort( data, offset );
|
||||
short recordId = LittleEndian.getShort( data, offset + 2 );
|
||||
// int remainingBytes = LittleEndian.getInt( data, offset + 4 );
|
||||
|
||||
Constructor<? extends EscherRecord> recordConstructor = recordsMap.get(Short.valueOf(recordId));
|
||||
if (recordConstructor == null) {
|
||||
return super.createRecord(data, offset);
|
||||
protected Supplier<? extends EscherRecord> getConstructor(short options, short recordId) {
|
||||
if (recordId == EscherPlaceholder.RECORD_ID) {
|
||||
return EscherPlaceholder::new;
|
||||
} else if (recordId == HSLFEscherClientDataRecord.RECORD_ID) {
|
||||
return HSLFEscherClientDataRecord::new;
|
||||
}
|
||||
EscherRecord escherRecord = null;
|
||||
try {
|
||||
escherRecord = recordConstructor.newInstance(new Object[] {});
|
||||
} catch (Exception e) {
|
||||
return super.createRecord(data, offset);
|
||||
}
|
||||
escherRecord.setRecordId(recordId);
|
||||
escherRecord.setOptions(options);
|
||||
if (escherRecord instanceof EscherContainerRecord) {
|
||||
escherRecord.fillFields(data, offset, this);
|
||||
}
|
||||
|
||||
return escherRecord;
|
||||
return super.getConstructor(options, recordId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -726,31 +726,22 @@ public class TestDrawingAggregate {
|
|||
List<org.apache.poi.hssf.record.Record> dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes));
|
||||
assertEquals(20, dgRecords.size());
|
||||
|
||||
short[] expectedSids = {
|
||||
DrawingRecord.sid,
|
||||
ObjRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
TextObjectRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
ObjRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
TextObjectRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
ObjRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
TextObjectRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
ObjRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
TextObjectRecord.sid,
|
||||
ContinueRecord.sid,
|
||||
ObjRecord.sid,
|
||||
ContinueRecord.sid,
|
||||
TextObjectRecord.sid
|
||||
int[] expectedSids = {
|
||||
DrawingRecord.sid, ObjRecord.sid,
|
||||
DrawingRecord.sid, TextObjectRecord.sid,
|
||||
DrawingRecord.sid, ObjRecord.sid,
|
||||
DrawingRecord.sid, TextObjectRecord.sid,
|
||||
DrawingRecord.sid, ObjRecord.sid,
|
||||
DrawingRecord.sid, TextObjectRecord.sid,
|
||||
DrawingRecord.sid, ObjRecord.sid,
|
||||
DrawingRecord.sid, TextObjectRecord.sid,
|
||||
ContinueRecord.sid, ObjRecord.sid,
|
||||
ContinueRecord.sid, TextObjectRecord.sid
|
||||
};
|
||||
for (int i = 0; i < expectedSids.length; i++) {
|
||||
assertEquals("unexpected record.sid and index[" + i + "]", expectedSids[i], dgRecords.get(i).getSid());
|
||||
}
|
||||
|
||||
int[] actualSids = dgRecords.stream().mapToInt(Record::getSid).toArray();
|
||||
assertArrayEquals("unexpected record.sid", expectedSids, actualSids);
|
||||
|
||||
DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord());
|
||||
|
||||
// create a dummy sheet consisting of our test data
|
||||
|
@ -904,26 +895,19 @@ public class TestDrawingAggregate {
|
|||
List<org.apache.poi.hssf.record.Record> dgRecords = RecordFactory.createRecords(new ByteArrayInputStream(dgBytes));
|
||||
assertEquals(14, dgRecords.size());
|
||||
|
||||
short[] expectedSids = {
|
||||
DrawingRecord.sid,
|
||||
ObjRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
ObjRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
ObjRecord.sid,
|
||||
DrawingRecord.sid,
|
||||
ObjRecord.sid,
|
||||
ContinueRecord.sid,
|
||||
ObjRecord.sid,
|
||||
ContinueRecord.sid,
|
||||
ObjRecord.sid,
|
||||
ContinueRecord.sid,
|
||||
ObjRecord.sid
|
||||
int[] expectedSids = {
|
||||
DrawingRecord.sid, ObjRecord.sid,
|
||||
DrawingRecord.sid, ObjRecord.sid,
|
||||
DrawingRecord.sid, ObjRecord.sid,
|
||||
DrawingRecord.sid, ObjRecord.sid,
|
||||
ContinueRecord.sid, ObjRecord.sid,
|
||||
ContinueRecord.sid, ObjRecord.sid,
|
||||
ContinueRecord.sid, ObjRecord.sid
|
||||
};
|
||||
|
||||
for (int i = 0; i < expectedSids.length; i++) {
|
||||
assertEquals("unexpected record.sid and index[" + i + "]", expectedSids[i], dgRecords.get(i).getSid());
|
||||
}
|
||||
int[] actualSids = dgRecords.stream().mapToInt(Record::getSid).toArray();
|
||||
assertArrayEquals("unexpected record.sid", expectedSids, actualSids);
|
||||
|
||||
DrawingManager2 drawingManager = new DrawingManager2(new EscherDggRecord());
|
||||
|
||||
// create a dummy sheet consisting of our test data
|
||||
|
|
Loading…
Reference in New Issue