252 lines
9.1 KiB
Java
252 lines
9.1 KiB
Java
/* ====================================================================
|
|
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.hslf.record;
|
|
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.io.OutputStream;
|
|
import java.util.Hashtable;
|
|
import java.util.Map;
|
|
import java.util.TreeMap;
|
|
|
|
import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException;
|
|
import org.apache.poi.util.BitField;
|
|
import org.apache.poi.util.LittleEndian;
|
|
import org.apache.poi.util.POILogger;
|
|
|
|
/**
|
|
* General holder for PersistPtrFullBlock and PersistPtrIncrementalBlock
|
|
* records. We need to handle them specially, since we have to go around
|
|
* updating UserEditAtoms if they shuffle about on disk
|
|
* These hold references to where slides "live". If the position of a slide
|
|
* moves, then we have update all of these. If we come up with a new version
|
|
* of a slide, then we have to add one of these to the end of the chain
|
|
* (via CurrentUserAtom and UserEditAtom) pointing to the new slide location
|
|
*
|
|
* @author Nick Burch
|
|
*/
|
|
|
|
public final class PersistPtrHolder extends PositionDependentRecordAtom
|
|
{
|
|
private byte[] _header;
|
|
private byte[] _ptrData; // Will need to update this once we allow updates to _slideLocations
|
|
private long _type;
|
|
|
|
/**
|
|
* Holds the lookup for slides to their position on disk.
|
|
* You always need to check the most recent PersistPtrHolder
|
|
* that knows about a given slide to find the right location
|
|
*/
|
|
private Hashtable<Integer,Integer> _slideLocations;
|
|
|
|
private static final BitField persistIdFld = new BitField(0X000FFFFF);
|
|
private static final BitField cntPersistFld = new BitField(0XFFF00000);
|
|
|
|
/**
|
|
* Return the value we were given at creation, be it 6001 or 6002
|
|
*/
|
|
public long getRecordType() { return _type; }
|
|
|
|
/**
|
|
* Get the list of slides that this PersistPtrHolder knows about.
|
|
* (They will be the keys in the hashtable for looking up the positions
|
|
* of these slides)
|
|
*/
|
|
public int[] getKnownSlideIDs() {
|
|
int[] ids = new int[_slideLocations.size()];
|
|
int i = 0;
|
|
for (Integer slideId : _slideLocations.keySet()) {
|
|
ids[i++] = slideId;
|
|
}
|
|
return ids;
|
|
}
|
|
|
|
/**
|
|
* Get the lookup from slide numbers to byte offsets, for the slides
|
|
* known about by this PersistPtrHolder.
|
|
*/
|
|
public Hashtable<Integer,Integer> getSlideLocationsLookup() {
|
|
return _slideLocations;
|
|
}
|
|
|
|
/**
|
|
* Get the lookup from slide numbers to their offsets inside
|
|
* _ptrData, used when adding or moving slides.
|
|
*
|
|
* @deprecated since POI 3.11, not supported anymore
|
|
*/
|
|
@Deprecated
|
|
public Hashtable<Integer,Integer> getSlideOffsetDataLocationsLookup() {
|
|
throw new UnsupportedOperationException("PersistPtrHolder.getSlideOffsetDataLocationsLookup() is not supported since 3.12-Beta1");
|
|
}
|
|
|
|
/**
|
|
* Create a new holder for a PersistPtr record
|
|
*/
|
|
protected PersistPtrHolder(byte[] source, int start, int len) {
|
|
// Sanity Checking - including whole header, so treat
|
|
// length as based of 0, not 8 (including header size based)
|
|
if(len < 8) { len = 8; }
|
|
|
|
// Treat as an atom, grab and hold everything
|
|
_header = new byte[8];
|
|
System.arraycopy(source,start,_header,0,8);
|
|
_type = LittleEndian.getUShort(_header,2);
|
|
|
|
// Try to make sense of the data part:
|
|
// Data part is made up of a number of these sets:
|
|
// 32 bit info value
|
|
// 12 bits count of # of entries
|
|
// base number for these entries
|
|
// count * 32 bit offsets
|
|
// Repeat as many times as you have data
|
|
_slideLocations = new Hashtable<Integer,Integer>();
|
|
_ptrData = new byte[len-8];
|
|
System.arraycopy(source,start+8,_ptrData,0,_ptrData.length);
|
|
|
|
int pos = 0;
|
|
while(pos < _ptrData.length) {
|
|
// Grab the info field
|
|
int info = LittleEndian.getInt(_ptrData,pos);
|
|
|
|
// First 20 bits = offset number
|
|
// Remaining 12 bits = offset count
|
|
int offset_no = persistIdFld.getValue(info);
|
|
int offset_count = cntPersistFld.getValue(info);
|
|
|
|
// Wind on by the 4 byte info header
|
|
pos += 4;
|
|
|
|
// Grab the offsets for each of the sheets
|
|
for(int i=0; i<offset_count; i++) {
|
|
int sheet_no = offset_no + i;
|
|
int sheet_offset = (int)LittleEndian.getUInt(_ptrData,pos);
|
|
_slideLocations.put(sheet_no, sheet_offset);
|
|
|
|
// Wind on by 4 bytes per sheet found
|
|
pos += 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* remove all slide references
|
|
*
|
|
* Convenience method provided, for easier reviewing of invocations
|
|
*/
|
|
public void clear() {
|
|
_slideLocations.clear();
|
|
}
|
|
|
|
/**
|
|
* Adds a new slide, notes or similar, to be looked up by this.
|
|
*/
|
|
public void addSlideLookup(int slideID, int posOnDisk) {
|
|
if (_slideLocations.containsKey(slideID)) {
|
|
throw new CorruptPowerPointFileException("A record with persistId "+slideID+" already exists.");
|
|
}
|
|
|
|
_slideLocations.put(slideID, posOnDisk);
|
|
}
|
|
|
|
/**
|
|
* At write-out time, update the references to the sheets to their
|
|
* new positions
|
|
*/
|
|
public void updateOtherRecordReferences(Hashtable<Integer,Integer> oldToNewReferencesLookup) {
|
|
// Loop over all the slides we know about
|
|
// Find where they used to live, and where they now live
|
|
for (Map.Entry<Integer,Integer> me : _slideLocations.entrySet()) {
|
|
Integer oldPos = me.getValue();
|
|
Integer newPos = oldToNewReferencesLookup.get(oldPos);
|
|
|
|
if (newPos == null) {
|
|
Integer id = me.getKey();
|
|
logger.log(POILogger.WARN, "Couldn't find the new location of the \"slide\" with id " + id + " that used to be at " + oldPos);
|
|
logger.log(POILogger.WARN, "Not updating the position of it, you probably won't be able to find it any more (if you ever could!)");
|
|
} else {
|
|
me.setValue(newPos);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void normalizePersistDirectory() {
|
|
TreeMap<Integer,Integer> orderedSlideLocations = new TreeMap<Integer,Integer>(_slideLocations);
|
|
|
|
@SuppressWarnings("resource")
|
|
BufAccessBAOS bos = new BufAccessBAOS();
|
|
byte intbuf[] = new byte[4];
|
|
int lastPersistEntry = -1;
|
|
int lastSlideId = -1;
|
|
for (Map.Entry<Integer,Integer> me : orderedSlideLocations.entrySet()) {
|
|
int nextSlideId = me.getKey();
|
|
int offset = me.getValue();
|
|
try {
|
|
// Building the info block
|
|
// First 20 bits = offset number = slide ID (persistIdFld, i.e. first slide ID of a continuous group)
|
|
// Remaining 12 bits = offset count = 1 (cntPersistFld, i.e. continuous entries in a group)
|
|
|
|
if (lastSlideId+1 == nextSlideId) {
|
|
// use existing PersistDirectoryEntry, need to increase entry count
|
|
assert(lastPersistEntry != -1);
|
|
int infoBlock = LittleEndian.getInt(bos.getBuf(), lastPersistEntry);
|
|
int entryCnt = cntPersistFld.getValue(infoBlock);
|
|
infoBlock = cntPersistFld.setValue(infoBlock, entryCnt+1);
|
|
LittleEndian.putInt(bos.getBuf(), lastPersistEntry, infoBlock);
|
|
} else {
|
|
// start new PersistDirectoryEntry
|
|
lastPersistEntry = bos.size();
|
|
int infoBlock = persistIdFld.setValue(0, nextSlideId);
|
|
infoBlock = cntPersistFld.setValue(infoBlock, 1);
|
|
LittleEndian.putInt(intbuf, 0, infoBlock);
|
|
bos.write(intbuf);
|
|
}
|
|
// Add to the ptrData offset lookup hash
|
|
LittleEndian.putInt(intbuf, 0, offset);
|
|
bos.write(intbuf);
|
|
lastSlideId = nextSlideId;
|
|
} catch (IOException e) {
|
|
// ByteArrayOutputStream is very unlikely throwing a IO exception (maybe because of OOM ...)
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
// Save the new ptr data
|
|
_ptrData = bos.toByteArray();
|
|
|
|
// Update the atom header
|
|
LittleEndian.putInt(_header,4,bos.size());
|
|
}
|
|
|
|
/**
|
|
* Write the contents of the record back, so it can be written
|
|
* to disk
|
|
*/
|
|
public void writeOut(OutputStream out) throws IOException {
|
|
normalizePersistDirectory();
|
|
out.write(_header);
|
|
out.write(_ptrData);
|
|
}
|
|
|
|
private static class BufAccessBAOS extends ByteArrayOutputStream {
|
|
public byte[] getBuf() {
|
|
return buf;
|
|
}
|
|
}
|
|
}
|