/********************************************************************* * * Copyright (C) 2002 Andrew Khan * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ***************************************************************************/ package jxl.read.biff; import java.util.ArrayList; import java.util.Iterator; import common.Logger; import jxl.WorkbookSettings; import jxl.biff.BaseCompoundFile; import jxl.biff.IntegerHelper; /** * Reads in and defrags an OLE compound compound file * (Made public only for the PropertySets demo) */ public final class CompoundFile extends BaseCompoundFile { /** * The logger */ private static Logger logger = Logger.getLogger(CompoundFile.class); /** * The original OLE stream, organized into blocks, which can * appear at any physical location in the file */ private byte[] data; /** * The number of blocks it takes to store the big block depot */ private int numBigBlockDepotBlocks; /** * The start block of the small block depot */ private int sbdStartBlock; /** * The start block of the root entry */ private int rootStartBlock; /** * The header extension block */ private int extensionBlock; /** * The number of header extension blocks */ private int numExtensionBlocks; /** * The root entry */ private byte[] rootEntry; /** * The sequence of blocks which comprise the big block chain */ private int[] bigBlockChain; /** * The sequence of blocks which comprise the small block chain */ private int[] smallBlockChain; /** * The chain of blocks which comprise the big block depot */ private int[] bigBlockDepotBlocks; /** * The list of property sets */ private ArrayList propertySets; /** * The workbook settings */ private WorkbookSettings settings; /** * The property storage root entry */ private PropertyStorage rootEntryPropertyStorage; /** * Initializes the compound file * * @param d the raw data of the ole stream * @param ws the workbook settings * @exception BiffException */ public CompoundFile(byte[] d, WorkbookSettings ws) throws BiffException { super(); data = d; settings = ws; // First verify the OLE identifier for (int i = 0; i < IDENTIFIER.length; i++) { if (data[i] != IDENTIFIER[i]) { throw new BiffException(BiffException.unrecognizedOLEFile); } } propertySets = new ArrayList(); numBigBlockDepotBlocks = IntegerHelper.getInt (data[NUM_BIG_BLOCK_DEPOT_BLOCKS_POS], data[NUM_BIG_BLOCK_DEPOT_BLOCKS_POS + 1], data[NUM_BIG_BLOCK_DEPOT_BLOCKS_POS + 2], data[NUM_BIG_BLOCK_DEPOT_BLOCKS_POS + 3]); sbdStartBlock = IntegerHelper.getInt (data[SMALL_BLOCK_DEPOT_BLOCK_POS], data[SMALL_BLOCK_DEPOT_BLOCK_POS + 1], data[SMALL_BLOCK_DEPOT_BLOCK_POS + 2], data[SMALL_BLOCK_DEPOT_BLOCK_POS + 3]); rootStartBlock = IntegerHelper.getInt (data[ROOT_START_BLOCK_POS], data[ROOT_START_BLOCK_POS + 1], data[ROOT_START_BLOCK_POS + 2], data[ROOT_START_BLOCK_POS + 3]); extensionBlock = IntegerHelper.getInt (data[EXTENSION_BLOCK_POS], data[EXTENSION_BLOCK_POS + 1], data[EXTENSION_BLOCK_POS + 2], data[EXTENSION_BLOCK_POS + 3]); numExtensionBlocks = IntegerHelper.getInt (data[NUM_EXTENSION_BLOCK_POS], data[NUM_EXTENSION_BLOCK_POS + 1], data[NUM_EXTENSION_BLOCK_POS + 2], data[NUM_EXTENSION_BLOCK_POS + 3]); bigBlockDepotBlocks = new int[numBigBlockDepotBlocks]; int pos = BIG_BLOCK_DEPOT_BLOCKS_POS; int bbdBlocks = numBigBlockDepotBlocks; if (numExtensionBlocks != 0) { bbdBlocks = (BIG_BLOCK_SIZE - BIG_BLOCK_DEPOT_BLOCKS_POS) / 4; } for (int i = 0; i < bbdBlocks; i++) { bigBlockDepotBlocks[i] = IntegerHelper.getInt (d[pos], d[pos + 1], d[pos + 2], d[pos + 3]); pos += 4; } for (int j = 0; j < numExtensionBlocks; j++) { pos = (extensionBlock + 1) * BIG_BLOCK_SIZE; int blocksToRead = Math.min(numBigBlockDepotBlocks - bbdBlocks, BIG_BLOCK_SIZE / 4 - 1); for (int i = bbdBlocks; i < bbdBlocks + blocksToRead; i++) { bigBlockDepotBlocks[i] = IntegerHelper.getInt (d[pos], d[pos + 1], d[pos + 2], d[pos + 3]); pos += 4; } bbdBlocks += blocksToRead; if (bbdBlocks < numBigBlockDepotBlocks) { extensionBlock = IntegerHelper.getInt (d[pos], d[pos + 1], d[pos + 2], d[pos + 3]); } } readBigBlockDepot(); readSmallBlockDepot(); rootEntry = readData(rootStartBlock); readPropertySets(); } /** * Reads the big block depot entries */ private void readBigBlockDepot() { int pos = 0; int index = 0; bigBlockChain = new int[numBigBlockDepotBlocks * BIG_BLOCK_SIZE / 4]; for (int i = 0; i < numBigBlockDepotBlocks; i++) { pos = (bigBlockDepotBlocks[i] + 1) * BIG_BLOCK_SIZE; for (int j = 0; j < BIG_BLOCK_SIZE / 4; j++) { bigBlockChain[index] = IntegerHelper.getInt (data[pos], data[pos + 1], data[pos + 2], data[pos + 3]); pos += 4; index++; } } } /** * Reads the small block chain's depot entries */ private void readSmallBlockDepot() { int pos = 0; int index = 0; int sbdBlock = sbdStartBlock; smallBlockChain = new int[0]; // Some non-excel generators specify -1 for an empty small block depot // simply warn and return if (sbdBlock == -1) { logger.warn("invalid small block depot number"); return; } while (sbdBlock != -2) { // Allocate some more space to the small block chain int[] oldChain = smallBlockChain; smallBlockChain = new int[smallBlockChain.length + BIG_BLOCK_SIZE / 4]; System.arraycopy(oldChain, 0, smallBlockChain, 0, oldChain.length); pos = (sbdBlock + 1) * BIG_BLOCK_SIZE; for (int j = 0; j < BIG_BLOCK_SIZE / 4; j++) { smallBlockChain[index] = IntegerHelper.getInt (data[pos], data[pos + 1], data[pos + 2], data[pos + 3]); pos += 4; index++; } sbdBlock = bigBlockChain[sbdBlock]; } } /** * Reads all the property sets */ private void readPropertySets() { int offset = 0; byte[] d = null; while (offset < rootEntry.length) { d = new byte[PROPERTY_STORAGE_BLOCK_SIZE]; System.arraycopy(rootEntry, offset, d, 0, d.length); PropertyStorage ps = new PropertyStorage(d); // sometimes the MAC Operating system leaves some property storage // names blank. Contributed by Jacky if (ps.name == null || ps.name.length() == 0) { if (ps.type == ROOT_ENTRY_PS_TYPE) { ps.name = ROOT_ENTRY_NAME; logger.warn("Property storage name for " + ps.type + " is empty - setting to " + ROOT_ENTRY_NAME); } else { if (ps.size != 0) { logger.warn("Property storage type " + ps.type + " is non-empty and has no associated name"); } } } propertySets.add(ps); if (ps.name.equalsIgnoreCase(ROOT_ENTRY_NAME)) { rootEntryPropertyStorage = ps; } offset += PROPERTY_STORAGE_BLOCK_SIZE; } if (rootEntryPropertyStorage == null) { rootEntryPropertyStorage = (PropertyStorage) propertySets.get(0); } } /** * Gets the defragmented stream from this ole compound file * * @param streamName the stream name to get * @return the defragmented ole stream * @exception BiffException */ public byte[] getStream(String streamName) throws BiffException { PropertyStorage ps = findPropertyStorage(streamName, rootEntryPropertyStorage); // Property set can't be found from the direct hierarchy, so just // search on the name if (ps == null) { ps = getPropertyStorage(streamName); } if (ps.size >= SMALL_BLOCK_THRESHOLD || streamName.equalsIgnoreCase(ROOT_ENTRY_NAME)) { return getBigBlockStream(ps); } else { return getSmallBlockStream(ps); } } /** * Gets the defragmented stream from this ole compound file. Used when * copying workbooks with macros * * @param psIndex the property storage index * @return the defragmented ole stream * @exception BiffException */ public byte[] getStream(int psIndex) throws BiffException { PropertyStorage ps = getPropertyStorage(psIndex); if (ps.size >= SMALL_BLOCK_THRESHOLD || ps.name.equalsIgnoreCase(ROOT_ENTRY_NAME)) { return getBigBlockStream(ps); } else { return getSmallBlockStream(ps); } } /** * Recursively searches the property storages in hierarchy order * for the appropriate name. This is the public version which is * invoked from the writable version * when copying a sheet with addition property sets. */ public PropertyStorage findPropertyStorage(String name) { return findPropertyStorage(name, rootEntryPropertyStorage); } /** * Recursively searches the property storages in hierarchy order * for the appropriate name. */ private PropertyStorage findPropertyStorage(String name, PropertyStorage base) { if (base.child == -1) { return null; } // Get the child PropertyStorage child = getPropertyStorage(base.child); if (child.name.equalsIgnoreCase(name)) { return child; } // Find the previous property storages on the same level PropertyStorage prev = child; while (prev.previous != -1) { prev = getPropertyStorage(prev.previous); if (prev.name.equalsIgnoreCase(name)) { return prev; } } // Find the next property storages on the same level PropertyStorage next = child; while (next.next != -1) { next = getPropertyStorage(next.next); if (next.name.equalsIgnoreCase(name)) { return next; } } return findPropertyStorage(name, child); } /** * Gets the property set with the specified name * @param name the property storage name * @return the property storage record * @exception BiffException * @deprecated remove me */ private PropertyStorage getPropertyStorage(String name) throws BiffException { // Find the workbook property Iterator i = propertySets.iterator(); boolean found = false; boolean multiple = false; PropertyStorage ps = null; while (i.hasNext()) { PropertyStorage ps2 = (PropertyStorage) i.next(); if (ps2.name.equalsIgnoreCase(name)) { multiple = found == true ? true : false; found = true; ps = ps2; } } if (multiple) { logger.warn("found multiple copies of property set " + name); } if (!found) { throw new BiffException(BiffException.streamNotFound); } return ps; } /** * Gets the property set with the specified name * @param index the index of the property storage * @return the property storage record */ private PropertyStorage getPropertyStorage(int index) { return (PropertyStorage) propertySets.get(index); } /** * Build up the resultant stream using the big blocks * * @param ps the property storage * @return the big block stream */ private byte[] getBigBlockStream(PropertyStorage ps) { int numBlocks = ps.size / BIG_BLOCK_SIZE; if (ps.size % BIG_BLOCK_SIZE != 0) { numBlocks++; } byte[] streamData = new byte[numBlocks * BIG_BLOCK_SIZE]; int block = ps.startBlock; int count = 0; int pos = 0; while (block != -2 && count < numBlocks) { pos = (block + 1) * BIG_BLOCK_SIZE; System.arraycopy(data, pos, streamData, count * BIG_BLOCK_SIZE, BIG_BLOCK_SIZE); count++; block = bigBlockChain[block]; } if (block != -2 && count == numBlocks) { logger.warn("Property storage size inconsistent with block chain."); } return streamData; } /** * Build up the resultant stream using the small blocks * @param ps the property storage * @return the data * @exception BiffException */ private byte[] getSmallBlockStream(PropertyStorage ps) throws BiffException { /* PropertyStorage rootps = null; try { rootps = getPropertyStorage(ROOT_ENTRY_NAME); } catch (BiffException e) { rootps = (PropertyStorage) propertySets.get(0); } */ byte[] rootdata = readData(rootEntryPropertyStorage.startBlock); byte[] sbdata = new byte[0]; int block = ps.startBlock; int pos = 0; while (block != -2) { // grow the array byte[] olddata = sbdata; sbdata = new byte[olddata.length + SMALL_BLOCK_SIZE]; System.arraycopy(olddata, 0, sbdata, 0, olddata.length); // Copy in the new data pos = block * SMALL_BLOCK_SIZE; System.arraycopy(rootdata, pos, sbdata, olddata.length, SMALL_BLOCK_SIZE); block = smallBlockChain[block]; if (block == -1) { logger.warn("Incorrect terminator for small block stream " + ps.name); block = -2; // kludge to force the loop termination } } return sbdata; } /** * Reads the block chain from the specified block and returns the * data as a continuous stream of bytes * * @param bl the block number * @return the data */ private byte[] readData(int bl) throws BiffException { int block = bl; int pos = 0; byte[] entry = new byte[0]; while (block != -2) { // Grow the array byte[] oldEntry = entry; entry = new byte[oldEntry.length + BIG_BLOCK_SIZE]; System.arraycopy(oldEntry, 0, entry, 0, oldEntry.length); pos = (block + 1) * BIG_BLOCK_SIZE; System.arraycopy(data, pos, entry, oldEntry.length, BIG_BLOCK_SIZE); if (bigBlockChain[block] == block) { throw new BiffException(BiffException.corruptFileFormat); } block = bigBlockChain[block]; } return entry; } /** * Gets the number of property sets * @return the number of property sets */ public int getNumberOfPropertySets() { return propertySets.size(); } /** * Gets the property set. Invoked when copying worksheets with macros. * Simply calls the private counterpart * * @param ps the property set name * @return the property set with the given name */ public PropertyStorage getPropertySet(int index) { return getPropertyStorage(index); } }