added initial support for bookmark

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1148428 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Sergey Vladimirov 2011-07-19 16:18:27 +00:00
parent fbdcb0329d
commit af6641682a
14 changed files with 850 additions and 102 deletions

View File

@ -34,6 +34,7 @@
<changes>
<release version="3.8-beta4" date="2011-??-??">
<action dev="poi-developers" type="fix">Added initial support for bookmarks in HWFP</action>
<action dev="poi-developers" type="fix">46250 - Fixed cloning worksheets with images</action>
<action dev="poi-developers" type="fix">51524 - PapBinTable constructor is slow (regression)</action>
<action dev="poi-developers" type="fix">51514 - allow HSSFObjectData to work with both POIFS and NPOIFS</action>

View File

@ -26,6 +26,7 @@ import java.io.OutputStream;
import java.util.Iterator;
import java.util.List;
import org.apache.poi.hwpf.model.BookmarksTables;
import org.apache.poi.hwpf.model.CHPBinTable;
import org.apache.poi.hwpf.model.CPSplitCalculator;
import org.apache.poi.hwpf.model.ComplexFileTable;
@ -101,6 +102,9 @@ public final class HWPFDocument extends HWPFDocumentCore
/** Holds Office Art objects */
protected ShapesTable _officeArts;
/** Holds the bookmarks */
protected BookmarksTables _bookmarksTables;
/** Holds the fields PLCFs */
protected FieldsTables _fieldsTables;
@ -261,7 +265,8 @@ public final class HWPFDocument extends HWPFDocumentCore
{
_rmat = new RevisionMarkAuthorTable(_tableStream, rmarkOffset, rmarkLength);
}
_bookmarksTables = new BookmarksTables( _tableStream, _fib );
_fieldsTables = new FieldsTables(_tableStream, _fib);
}
@ -438,6 +443,15 @@ public final class HWPFDocument extends HWPFDocumentCore
return _officeArts;
}
/**
* @return BookmarksTables object, that is able to extract bookmarks
* descriptors from this document
*/
public BookmarksTables getBookmarksTables()
{
return _bookmarksTables;
}
/**
* @return FieldsTables object, that is able to extract fields descriptors from this document
*/
@ -487,6 +501,15 @@ public final class HWPFDocument extends HWPFDocumentCore
// complex table.
int fcMin = mainOffset;
/*
* clx (encoding of the sprm lists for a complex file and piece table
* for a any file) Written immediately after the end of the previously
* recorded structure. This is recorded in all Word documents
*
* Microsoft Office Word 97-2007 Binary File Format (.doc)
* Specification; Page 23 of 210
*/
// write out the Complex table, includes text.
_fib.setFcClx(tableOffset);
_cft.writeTo(docSys);
@ -494,12 +517,54 @@ public final class HWPFDocument extends HWPFDocumentCore
tableOffset = tableStream.getOffset();
int fcMac = mainStream.getOffset();
/*
* plcfBkmkf (table recording beginning CPs of bookmarks) Written
* immediately after the sttbfBkmk, if the document contains bookmarks.
*
* Microsoft Office Word 97-2007 Binary File Format (.doc)
* Specification; Page 24 of 210
*/
if ( _bookmarksTables != null )
{
_bookmarksTables.writePlcfBkmkf( _fib, tableStream );
tableOffset = tableStream.getOffset();
}
/*
* plcfBkmkl (table recording limit CPs of bookmarks) Written
* immediately after the plcfBkmkf, if the document contains bookmarks.
*
* Microsoft Office Word 97-2007 Binary File Format (.doc)
* Specification; Page 24 of 210
*/
if ( _bookmarksTables != null )
{
_bookmarksTables.writePlcfBkmkl( _fib, tableStream );
tableOffset = tableStream.getOffset();
}
/*
* plcfbteChpx (bin table for CHP FKPs) Written immediately after the
* previously recorded table. This is recorded in all Word documents.
*
* Microsoft Office Word 97-2007 Binary File Format (.doc)
* Specification; Page 24 of 210
*/
// write out the CHPBinTable.
_fib.setFcPlcfbteChpx(tableOffset);
_cbt.writeTo(docSys, fcMin);
_fib.setLcbPlcfbteChpx(tableStream.getOffset() - tableOffset);
tableOffset = tableStream.getOffset();
/*
* plcfbtePapx (bin table for PAP FKPs) Written immediately after the
* plcfbteChpx. This is recorded in all Word documents.
*
* Microsoft Office Word 97-2007 Binary File Format (.doc)
* Specification; Page 24 of 210
*/
// write out the PAPBinTable.
_fib.setFcPlcfbtePapx(tableOffset);
_pbt.writeTo(docSys, fcMin);
@ -531,6 +596,27 @@ public final class HWPFDocument extends HWPFDocumentCore
tableOffset = tableStream.getOffset();
}
/*
* sttbfBkmk (table of bookmark name strings) Written immediately after
* the previously recorded table, if the document contains bookmarks.
*
* Microsoft Office Word 97-2007 Binary File Format (.doc)
* Specification; Page 27 of 210
*/
if ( _bookmarksTables != null )
{
_bookmarksTables.writeSttbfBkmk( _fib, tableStream );
tableOffset = tableStream.getOffset();
}
/*
* sttbSavedBy (last saved by string table) Written immediately after
* the previously recorded table.
*
* Microsoft Office Word 97-2007 Binary File Format (.doc)
* Specification; Page 27 of 210
*/
// write out the saved-by table.
if (_sbt != null)
{

View File

@ -0,0 +1,70 @@
package org.apache.poi.hwpf.model;
import org.apache.poi.hwpf.model.types.BKFAbstractType;
public final class BookmarkFirstDescriptor extends BKFAbstractType implements
Cloneable
{
public BookmarkFirstDescriptor()
{
}
public BookmarkFirstDescriptor( byte[] data, int offset )
{
fillFields( data, offset );
}
@Override
protected BookmarkFirstDescriptor clone()
{
try
{
return (BookmarkFirstDescriptor) super.clone();
}
catch ( CloneNotSupportedException e )
{
throw new RuntimeException( e );
}
}
@Override
public boolean equals( Object obj )
{
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
BookmarkFirstDescriptor other = (BookmarkFirstDescriptor) obj;
if ( field_1_ibkl != other.field_1_ibkl )
return false;
if ( field_2_bkf_flags != other.field_2_bkf_flags )
return false;
return true;
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + field_1_ibkl;
result = prime * result + field_2_bkf_flags;
return result;
}
public boolean isEmpty()
{
return field_1_ibkl == 0 && field_2_bkf_flags == 0;
}
@Override
public String toString()
{
if ( isEmpty() )
return "[BKF] EMPTY";
return super.toString();
}
}

View File

@ -0,0 +1,153 @@
package org.apache.poi.hwpf.model;
import java.io.IOException;
import java.util.Arrays;
import org.apache.poi.hwpf.model.io.HWPFOutputStream;
import org.apache.poi.hwpf.usermodel.Bookmark;
public class BookmarksTables
{
private PlexOfCps descriptorsFirst = new PlexOfCps( 4 );
private PlexOfCps descriptorsLim = new PlexOfCps( 0 );
private String[] names = new String[0];
public BookmarksTables()
{
}
public BookmarksTables( byte[] tableStream, FileInformationBlock fib )
{
read( tableStream, fib );
}
public Bookmark getBookmark( int index )
{
final GenericPropertyNode first = descriptorsFirst.getProperty( index );
return new Bookmark()
{
public int getEnd()
{
int currentIndex = Arrays.asList(
descriptorsFirst.toPropertiesArray() ).indexOf( first );
if ( currentIndex >= descriptorsLim.length() )
return first.getEnd();
GenericPropertyNode lim = descriptorsLim
.getProperty( currentIndex );
return lim.getStart();
}
public String getName()
{
int currentIndex = Arrays.asList(
descriptorsFirst.toPropertiesArray() ).indexOf( first );
if ( currentIndex >= names.length )
return "";
return names[currentIndex];
}
public int getStart()
{
return first.getStart();
}
public void setName( String name )
{
int currentIndex = Arrays.asList(
descriptorsFirst.toPropertiesArray() ).indexOf( first );
if ( currentIndex < names.length )
{
String[] newNames = new String[currentIndex + 1];
System.arraycopy( names, 0, newNames, 0, names.length );
names = newNames;
}
names[currentIndex] = name;
}
};
}
public int getBookmarksCount()
{
return descriptorsFirst.length();
}
private void read( byte[] tableStream, FileInformationBlock fib )
{
int namesStart = fib.getFcSttbfbkmk();
int namesLength = fib.getLcbSttbfbkmk();
if ( namesStart != 0 && namesLength != 0 )
this.names = SttbfUtils.read( tableStream, namesStart );
int firstDescriptorsStart = fib.getFcPlcfbkf();
int firstDescriptorsLength = fib.getLcbPlcfbkf();
if ( firstDescriptorsStart != 0 && firstDescriptorsLength != 0 )
descriptorsFirst = new PlexOfCps( tableStream,
firstDescriptorsStart, firstDescriptorsLength,
BookmarkFirstDescriptor.getSize() );
int limDescriptorsStart = fib.getFcPlcfbkl();
int limDescriptorsLength = fib.getLcbPlcfbkl();
if ( limDescriptorsStart != 0 && limDescriptorsLength != 0 )
descriptorsLim = new PlexOfCps( tableStream, limDescriptorsStart,
limDescriptorsLength, 0 );
}
public void writePlcfBkmkf( FileInformationBlock fib,
HWPFOutputStream tableStream ) throws IOException
{
if ( descriptorsFirst == null || descriptorsFirst.length() == 0 )
{
fib.setFcPlcfbkf( 0 );
fib.setLcbPlcfbkf( 0 );
return;
}
int start = tableStream.getOffset();
tableStream.write( descriptorsFirst.toByteArray() );
int end = tableStream.getOffset();
fib.setFcPlcfbkf( start );
fib.setLcbPlcfbkf( end - start );
}
public void writePlcfBkmkl( FileInformationBlock fib,
HWPFOutputStream tableStream ) throws IOException
{
if ( descriptorsLim == null || descriptorsLim.length() == 0 )
{
fib.setFcPlcfbkl( 0 );
fib.setLcbPlcfbkl( 0 );
return;
}
int start = tableStream.getOffset();
tableStream.write( descriptorsLim.toByteArray() );
int end = tableStream.getOffset();
fib.setFcPlcfbkl( start );
fib.setLcbPlcfbkl( end - start );
}
public void writeSttbfBkmk( FileInformationBlock fib,
HWPFOutputStream tableStream ) throws IOException
{
if ( names == null || names.length == 0 )
{
fib.setFcSttbfbkmk( 0 );
fib.setLcbSttbfbkmk( 0 );
return;
}
int start = tableStream.getOffset();
SttbfUtils.write( tableStream, names );
int end = tableStream.getOffset();
fib.setFcSttbfbkmk( start );
fib.setLcbSttbfbkmk( end - start );
}
}

View File

@ -30,6 +30,7 @@ import org.apache.poi.util.POILogger;
public final class FIBFieldHandler
{
// 154 == 0x009A; 158 == 0x009E
public static final int STSHFORIG = 0;
public static final int STSHF = 1;
public static final int PLCFFNDREF = 2;
@ -48,10 +49,13 @@ public final class FIBFieldHandler
public static final int STTBFFFN = 15;
public static final int PLCFFLDMOM = 16;
public static final int PLCFFLDHDR = 17;
// 298 == 0x12A; 302 == 0x12E
public static final int PLCFFLDFTN = 18;
// 306 == 0x132; 310 == 0x0136
public static final int PLCFFLDATN = 19;
public static final int PLCFFLDMCR = 20;
public static final int STTBFBKMK = 21;
// 330 == 0x014A; 334 == 0x014E
public static final int PLCFBKF = 22;
public static final int PLCFBKL = 23;
public static final int CMDS = 24;
@ -70,24 +74,29 @@ public final class FIBFieldHandler
public static final int STTBFATNBKMK = 37;
public static final int PLCFDOAMOM = 38;
public static final int PLCDOAHDR = 39;
public static final int PLCSPAMOM = 40;
// 474 == 0x01DA; 478 == 0x01DE
public static final int PLCSPAMOM = 40;
public static final int PLCSPAHDR = 41;
public static final int PLCFATNBKF = 42;
public static final int PLCFATNBKL = 43;
public static final int PMS = 44;
public static final int PLCFATNBKF = 42;
// 498 == 0x01F2; 502 == 0x01F6
public static final int PLCFATNBKL = 43;
// 506 == 0x01FA; 510 == 0x01FE
public static final int PMS = 44;
public static final int FORMFLDSTTBS = 45;
public static final int PLCFENDREF = 46;
public static final int PLCFENDTXT = 47;
public static final int PLCFFLDEDN = 48;
public static final int PLCFPGDEDN = 49;
public static final int DGGINFO = 50;
// 554 == 0x022A; 558 == 0x022E -- long
public static final int DGGINFO = 50;
public static final int STTBFRMARK = 51;
public static final int STTBCAPTION = 52;
public static final int STTBAUTOCAPTION = 53;
public static final int PLCFWKB = 54;
public static final int PLCFSPL = 55;
public static final int PLCFTXBXTXT = 56;
public static final int PLCFFLDTXBX = 57;//validated
// 610 -- 0x0262; 614 == 0x0266
public static final int PLCFFLDTXBX = 57;// validated
public static final int PLCFHDRTXBXTXT = 58;
public static final int PLCFFLDHDRTXBX = 59;
public static final int STWUSER = 60;
@ -132,11 +141,11 @@ public final class FIBFieldHandler
private int[] _fields;
public FIBFieldHandler(byte[] mainStream, int offset, byte[] tableStream,
public FIBFieldHandler(byte[] mainStream, int startOffset, byte[] tableStream,
HashSet<Integer> offsetList, boolean areKnown)
{
int numFields = LittleEndian.getShort(mainStream, offset);
offset += LittleEndian.SHORT_SIZE;
int numFields = LittleEndian.getShort(mainStream, startOffset);
int offset = startOffset + LittleEndian.SHORT_SIZE;
_fields = new int[numFields * 2];
for (int x = 0; x < numFields; x++)

View File

@ -55,35 +55,45 @@ public final class FileInformationBlock extends FIBAbstractType
fillFields(mainDocument, 0);
}
public void fillVariableFields(byte[] mainDocument, byte[] tableStream)
public void fillVariableFields( byte[] mainDocument, byte[] tableStream )
{
HashSet<Integer> fieldSet = new HashSet<Integer>();
fieldSet.add(Integer.valueOf(FIBFieldHandler.STSHF));
fieldSet.add(Integer.valueOf(FIBFieldHandler.CLX));
fieldSet.add(Integer.valueOf(FIBFieldHandler.DOP));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFBTECHPX));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFBTEPAPX));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFSED));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFLST));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLFLFO));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFFLDATN));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFFLDEDN));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFFLDFTN));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFFLDHDR));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFFLDHDRTXBX));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFFLDMOM));
fieldSet.add(Integer.valueOf(FIBFieldHandler.PLCFFLDTXBX));
fieldSet.add(Integer.valueOf(FIBFieldHandler.STTBFFFN));
fieldSet.add(Integer.valueOf(FIBFieldHandler.STTBFRMARK));
fieldSet.add(Integer.valueOf(FIBFieldHandler.STTBSAVEDBY));
fieldSet.add(Integer.valueOf(FIBFieldHandler.MODIFIED));
_shortHandler = new FIBShortHandler( mainDocument );
_longHandler = new FIBLongHandler( mainDocument, FIBShortHandler.START
+ _shortHandler.sizeInBytes() );
/*
* Listed fields won't be treat as UnhandledDataStructure. For all other
* fields FIBFieldHandler will load it content into
* UnhandledDataStructure and save them on save.
*/
HashSet<Integer> knownFieldSet = new HashSet<Integer>();
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.STSHF ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.CLX ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.DOP ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.PLCFBTECHPX ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.PLCFBTEPAPX ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.PLCFSED ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.PLCFLST ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.PLFLFO ) );
_shortHandler = new FIBShortHandler(mainDocument);
_longHandler = new FIBLongHandler(mainDocument, FIBShortHandler.START + _shortHandler.sizeInBytes());
_fieldHandler = new FIBFieldHandler(mainDocument,
FIBShortHandler.START + _shortHandler.sizeInBytes() + _longHandler.sizeInBytes(),
tableStream, fieldSet, true);
// field info
for ( FieldsDocumentPart part : FieldsDocumentPart.values() )
knownFieldSet.add( Integer.valueOf( part.getFibFieldsField() ) );
// bookmarks
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.PLCFBKF ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.PLCFBKL ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.STTBFBKMK ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.STTBFFFN ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.STTBFRMARK ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.STTBSAVEDBY ) );
knownFieldSet.add( Integer.valueOf( FIBFieldHandler.MODIFIED ) );
_fieldHandler = new FIBFieldHandler( mainDocument,
FIBShortHandler.START + _shortHandler.sizeInBytes()
+ _longHandler.sizeInBytes(), tableStream,
knownFieldSet, true );
}
@Override
@ -286,6 +296,89 @@ public final class FileInformationBlock extends FIBAbstractType
return _fieldHandler.getFieldSize(FIBFieldHandler.PLFLFO);
}
/**
* @return Offset in table stream of the STTBF that records bookmark names
* in the main document
*/
public int getFcSttbfbkmk()
{
return _fieldHandler.getFieldOffset( FIBFieldHandler.STTBFBKMK );
}
public void setFcSttbfbkmk( int offset )
{
_fieldHandler.setFieldOffset( FIBFieldHandler.STTBFBKMK, offset );
}
/**
* @return Count of bytes in Sttbfbkmk
*/
public int getLcbSttbfbkmk()
{
return _fieldHandler.getFieldSize( FIBFieldHandler.STTBFBKMK );
}
public void setLcbSttbfbkmk( int length )
{
_fieldHandler.setFieldSize( FIBFieldHandler.STTBFBKMK, length );
}
/**
* @return Offset in table stream of the PLCF that records the beginning CP
* offsets of bookmarks in the main document. See BKF structure
* definition.
*/
public int getFcPlcfbkf()
{
return _fieldHandler.getFieldOffset( FIBFieldHandler.PLCFBKF );
}
public void setFcPlcfbkf( int offset )
{
_fieldHandler.setFieldOffset( FIBFieldHandler.PLCFBKF, offset );
}
/**
* @return Count of bytes in Plcfbkf
*/
public int getLcbPlcfbkf()
{
return _fieldHandler.getFieldSize( FIBFieldHandler.PLCFBKF );
}
public void setLcbPlcfbkf( int length )
{
_fieldHandler.setFieldSize( FIBFieldHandler.PLCFBKF, length );
}
/**
* @return Offset in table stream of the PLCF that records the ending CP
* offsets of bookmarks recorded in the main document. No structure
* is stored in this PLCF.
*/
public int getFcPlcfbkl()
{
return _fieldHandler.getFieldOffset( FIBFieldHandler.PLCFBKL );
}
public void setFcPlcfbkl( int offset )
{
_fieldHandler.setFieldOffset( FIBFieldHandler.PLCFBKL, offset );
}
/**
* @return Count of bytes in Plcfbkl
*/
public int getLcbPlcfbkl()
{
return _fieldHandler.getFieldSize( FIBFieldHandler.PLCFBKL );
}
public void setLcbPlcfbkl( int length )
{
_fieldHandler.setFieldSize( FIBFieldHandler.PLCFBKL, length );
}
public void setFcPlfLfo(int fcPlfLfo)
{
_fieldHandler.setFieldOffset(FIBFieldHandler.PLFLFO, fcPlfLfo);
@ -782,5 +875,6 @@ public final class FileInformationBlock extends FIBAbstractType
// return null;
// }
// }
}

View File

@ -23,8 +23,6 @@ import java.util.Collections;
import java.util.List;
import org.apache.poi.hwpf.model.io.HWPFOutputStream;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.StringUtil;
/**
* String table containing the history of the last few revisions ("saves") of the document.
@ -34,10 +32,6 @@ import org.apache.poi.util.StringUtil;
*/
public final class SavedByTable
{
/**
* A value that I don't know what it does, but is maintained for accuracy.
*/
private short unknownValue = -1;
/**
* Array of entries.
@ -52,30 +46,40 @@ public final class SavedByTable
* @param size the size of the table in the byte array.
*/
public SavedByTable(byte[] tableStream, int offset, int size)
{
// Read the value that I don't know what it does. :-)
unknownValue = LittleEndian.getShort(tableStream, offset);
offset += 2;
{
// // Read the value that I don't know what it does. :-)
// unknownValue = LittleEndian.getShort(tableStream, offset);
// offset += 2;
//
// // The stored int is the number of strings, and there are two strings per entry.
// int numEntries = LittleEndian.getInt(tableStream, offset) / 2;
// offset += 4;
//
// entries = new SavedByEntry[numEntries];
// for (int i = 0; i < numEntries; i++)
// {
// int len = LittleEndian.getShort(tableStream, offset);
// offset += 2;
// String userName = StringUtil.getFromUnicodeLE(tableStream, offset, len);
// offset += len * 2;
// len = LittleEndian.getShort(tableStream, offset);
// offset += 2;
// String saveLocation = StringUtil.getFromUnicodeLE(tableStream, offset, len);
// offset += len * 2;
//
// entries[i] = new SavedByEntry(userName, saveLocation);
// }
// The stored int is the number of strings, and there are two strings per entry.
int numEntries = LittleEndian.getInt(tableStream, offset) / 2;
offset += 4;
// first value is mark for extended STTBF ;) -- sergey
String[] strings = SttbfUtils.read( tableStream, offset );
entries = new SavedByEntry[numEntries];
for (int i = 0; i < numEntries; i++)
{
int len = LittleEndian.getShort(tableStream, offset);
offset += 2;
String userName = StringUtil.getFromUnicodeLE(tableStream, offset, len);
offset += len * 2;
len = LittleEndian.getShort(tableStream, offset);
offset += 2;
String saveLocation = StringUtil.getFromUnicodeLE(tableStream, offset, len);
offset += len * 2;
entries[i] = new SavedByEntry(userName, saveLocation);
int numEntries = strings.length / 2;
entries = new SavedByEntry[numEntries];
for ( int i = 0; i < numEntries; i++ )
{
entries[i] = new SavedByEntry( strings[i * 2], strings[i * 2 + 1] );
}
}
}
/**
* Gets the entries. The returned list cannot be modified.
@ -87,34 +91,24 @@ public final class SavedByTable
return Collections.unmodifiableList(Arrays.asList(entries));
}
/**
* Writes this table to the table stream.
*
* @param tableStream the table stream to write to.
* @throws IOException if an error occurs while writing.
*/
public void writeTo(HWPFOutputStream tableStream)
throws IOException
{
byte[] header = new byte[6];
LittleEndian.putShort(header, 0, unknownValue);
LittleEndian.putInt(header, 2, entries.length * 2);
tableStream.write(header);
for (int i = 0; i < entries.length; i++)
/**
* Writes this table to the table stream.
*
* @param tableStream
* the table stream to write to.
* @throws IOException
* if an error occurs while writing.
*/
public void writeTo( HWPFOutputStream tableStream ) throws IOException
{
writeStringValue(tableStream, entries[i].getUserName());
writeStringValue(tableStream, entries[i].getSaveLocation());
String[] toSave = new String[entries.length * 2];
int counter = 0;
for ( SavedByEntry entry : entries )
{
toSave[counter++] = entry.getUserName();
toSave[counter++] = entry.getSaveLocation();
}
SttbfUtils.write( tableStream, toSave );
}
}
private void writeStringValue(HWPFOutputStream tableStream, String value)
throws IOException
{
byte[] buf = new byte[value.length() * 2 + 2];
LittleEndian.putShort(buf, 0, (short) value.length());
StringUtil.putUnicodeLE(value, buf, 2);
tableStream.write(buf);
}
}

View File

@ -0,0 +1,74 @@
package org.apache.poi.hwpf.model;
import java.io.IOException;
import org.apache.poi.hwpf.model.io.HWPFOutputStream;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.StringUtil;
/**
* Utils for storing and reading "STring TaBle stored in File"
*
* @author Sergey Vladimirov (vlsergey {at} gmail {dot} com)
*/
class SttbfUtils
{
public static String[] read( byte[] data, int startOffset )
{
short ffff = LittleEndian.getShort( data, startOffset );
if ( ffff != (short) 0xffff )
{
// Non-extended character Pascal strings
throw new UnsupportedOperationException(
"Non-extended character Pascal strings are not supported right now. "
+ "Please, contact POI developers for update." );
}
// strings are extended character strings
int offset = startOffset + 2;
int numEntries = LittleEndian.getInt( data, offset );
offset += 4;
String[] entries = new String[numEntries];
for ( int i = 0; i < numEntries; i++ )
{
int len = LittleEndian.getShort( data, offset );
offset += 2;
String value = StringUtil.getFromUnicodeLE( data, offset, len );
offset += len * 2;
entries[i] = value;
}
return entries;
}
public static int write( HWPFOutputStream tableStream, String[] entries )
throws IOException
{
byte[] header = new byte[6];
LittleEndian.putShort( header, 0, (short) 0xffff );
if ( entries == null || entries.length == 0 )
{
LittleEndian.putInt( header, 2, 0 );
tableStream.write( header );
return 6;
}
LittleEndian.putInt( header, 2, entries.length );
tableStream.write( header );
int size = 6;
for ( String entry : entries )
{
byte[] buf = new byte[entry.length() * 2 + 2];
LittleEndian.putShort( buf, 0, (short) entry.length() );
StringUtil.putUnicodeLE( entry, buf, 2 );
tableStream.write( buf );
size += buf.length;
}
return size;
}
}

View File

@ -0,0 +1,174 @@
package org.apache.poi.hwpf.model.types;
import org.apache.poi.util.BitField;
import org.apache.poi.util.LittleEndian;
/**
* BooKmark First descriptor (BKF).
* <p>
* Class and fields descriptions are quoted from Microsoft Office Word 97-2007
* Binary File Format (.doc) Specification
*
* NOTE: This source is automatically generated please do not modify this file.
* Either subclass or remove the record in src/types/definitions.
*
* @author Sergey Vladimirov; according to Microsoft Office Word 97-2007 Binary
* File Format (.doc) Specification
*/
public abstract class BKFAbstractType
{
protected short field_1_ibkl;
protected short field_2_bkf_flags;
/**/private static BitField itcFirst = new BitField( 0x007F );
/**/private static BitField fPub = new BitField( 0x0080 );
/**/private static BitField itcLim = new BitField( 0x7F00 );
/**/private static BitField fCol = new BitField( 0x8000 );
protected BKFAbstractType()
{
}
protected void fillFields( byte[] data, int offset )
{
field_1_ibkl = LittleEndian.getShort(data, 0x0 + offset);
field_2_bkf_flags = LittleEndian.getShort(data, 0x2 + offset);
}
public void serialize( byte[] data, int offset )
{
LittleEndian.putShort(data, 0x0 + offset, field_1_ibkl);
LittleEndian.putShort(data, 0x2 + offset, field_2_bkf_flags);
}
/**
* Size of record
*/
public static int getSize()
{
return 0 + 2 + 2;
}
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("[BKF]\n");
builder.append(" .ibkl = ");
builder.append(" (").append(getIbkl()).append(" )\n");
builder.append(" .bkf_flags = ");
builder.append(" (").append(getBkf_flags()).append(" )\n");
builder.append(" .itcFirst = ").append(getItcFirst()).append('\n');
builder.append(" .fPub = ").append(isFPub()).append('\n');
builder.append(" .itcLim = ").append(getItcLim()).append('\n');
builder.append(" .fCol = ").append(isFCol()).append('\n');
builder.append("[/BKF]\n");
return builder.toString();
}
/**
* Index to BKL entry in plcfbkl that describes the ending position of this bookmark in the CP stream.
*/
public short getIbkl()
{
return field_1_ibkl;
}
/**
* Index to BKL entry in plcfbkl that describes the ending position of this bookmark in the CP stream.
*/
public void setIbkl( short field_1_ibkl )
{
this.field_1_ibkl = field_1_ibkl;
}
/**
* Get the bkf_flags field for the BKF record.
*/
public short getBkf_flags()
{
return field_2_bkf_flags;
}
/**
* Set the bkf_flags field for the BKF record.
*/
public void setBkf_flags( short field_2_bkf_flags )
{
this.field_2_bkf_flags = field_2_bkf_flags;
}
/**
* Sets the itcFirst field value.
* When bkf.fCol==1, this is the index to the first column of a table column bookmark
*/
public void setItcFirst( byte value )
{
field_2_bkf_flags = (short)itcFirst.setValue(field_2_bkf_flags, value);
}
/**
* When bkf.fCol==1, this is the index to the first column of a table column bookmark
* @return the itcFirst field value.
*/
public byte getItcFirst()
{
return ( byte )itcFirst.getValue(field_2_bkf_flags);
}
/**
* Sets the fPub field value.
* When 1, this indicates that this bookmark is marking the range of a Macintosh Publisher section
*/
public void setFPub( boolean value )
{
field_2_bkf_flags = (short)fPub.setBoolean(field_2_bkf_flags, value);
}
/**
* When 1, this indicates that this bookmark is marking the range of a Macintosh Publisher section
* @return the fPub field value.
*/
public boolean isFPub()
{
return fPub.isSet(field_2_bkf_flags);
}
/**
* Sets the itcLim field value.
* When bkf.fCol==1, this is the index to limit column of a table column bookmark
*/
public void setItcLim( byte value )
{
field_2_bkf_flags = (short)itcLim.setValue(field_2_bkf_flags, value);
}
/**
* When bkf.fCol==1, this is the index to limit column of a table column bookmark
* @return the itcLim field value.
*/
public byte getItcLim()
{
return ( byte )itcLim.getValue(field_2_bkf_flags);
}
/**
* Sets the fCol field value.
* When 1, this bookmark marks a range of columns in a table specified by (bkf.itcFirst, bkf.itcLim)
*/
public void setFCol( boolean value )
{
field_2_bkf_flags = (short)fCol.setBoolean(field_2_bkf_flags, value);
}
/**
* When 1, this bookmark marks a range of columns in a table specified by (bkf.itcFirst, bkf.itcLim)
* @return the fCol field value.
*/
public boolean isFCol()
{
return fCol.isSet(field_2_bkf_flags);
}
} // END OF CLASS

View File

@ -0,0 +1,12 @@
package org.apache.poi.hwpf.usermodel;
public interface Bookmark
{
public int getEnd();
public String getName();
public int getStart();
public void setName( String name );
}

View File

@ -25,6 +25,7 @@ import org.apache.poi.hwpf.converter.TestWordToHtmlConverter;
import org.apache.poi.hwpf.extractor.TestDifferentRoutes;
import org.apache.poi.hwpf.extractor.TestWordExtractor;
import org.apache.poi.hwpf.extractor.TestWordExtractorBugs;
import org.apache.poi.hwpf.model.TestBookmarksTables;
import org.apache.poi.hwpf.model.TestCHPBinTable;
import org.apache.poi.hwpf.model.TestDocumentProperties;
import org.apache.poi.hwpf.model.TestFileInformationBlock;
@ -79,6 +80,7 @@ public final class AllHWPFTests
suite.addTestSuite( TestWordExtractorBugs.class );
// org.apache.poi.hwpf.model
suite.addTestSuite( TestBookmarksTables.class );
suite.addTestSuite( TestCHPBinTable.class );
suite.addTestSuite( TestDocumentProperties.class );
suite.addTestSuite( TestFileInformationBlock.class );

View File

@ -0,0 +1,23 @@
package org.apache.poi.hwpf.model;
import junit.framework.TestCase;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.HWPFTestDataSamples;
import org.apache.poi.hwpf.usermodel.Bookmark;
public class TestBookmarksTables extends TestCase
{
public void test()
{
HWPFDocument doc = HWPFTestDataSamples.openSampleFile( "pageref.doc" );
BookmarksTables bookmarksTables = doc.getBookmarksTables();
assertEquals( 1, bookmarksTables.getBookmarksCount() );
Bookmark bookmark = bookmarksTables.getBookmark( 0 );
assertEquals( "userref", bookmark.getName() );
assertEquals( 27, bookmark.getStart() );
assertEquals( 38, bookmark.getEnd() );
}
}

View File

@ -0,0 +1,44 @@
<?xml version="1.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.
====================================================================
-->
<record fromfile="true" name="BKF" package="org.apache.poi.hwpf.model.types">
<suffix>AbstractType</suffix>
<extends>HDFType</extends>
<description>BooKmark First descriptor (BKF). &lt;p&gt;Class and fields descriptions are
quoted
from Microsoft Office Word 97-2007 Binary File Format (.doc) Specification
</description>
<author>Sergey Vladimirov; according to Microsoft Office Word 97-2007 Binary File Format (.doc)
Specification
</author>
<fields>
<field type="short" size="2" name="ibkl"
description="Index to BKL entry in plcfbkl that describes the ending position of this bookmark in the CP stream"/>
<field type="short" size="2" name="bkf_flags">
<bit number="0" mask="0x007F" name="itcFirst"
description="When bkf.fCol==1, this is the index to the first column of a table column bookmark"/>
<bit number="1" mask="0x0080" name="fPub"
description="When 1, this indicates that this bookmark is marking the range of a Macintosh Publisher section"/>
<bit number="2" mask="0x7F00" name="itcLim"
description="When bkf.fCol==1, this is the index to limit column of a table column bookmark"/>
<bit number="3" mask="0x8000" name="fCol"
description="When 1, this bookmark marks a range of columns in a table specified by (bkf.itcFirst, bkf.itcLim)"/>
</field>
</fields>
</record>

View File

@ -150,17 +150,25 @@ public abstract class </xsl:text><xsl:value-of select="@name"/><xsl:text>Abstrac
<xsl:call-template name="indent"/>
<xsl:text>}</xsl:text>
<xsl:call-template name="linebreak"/>
<xsl:text>
/**
* Size of record (exluding 4 byte header)
* Size of record
*/
public static int getSize()
{
<xsl:variable name="fieldIterator" select="field:new()"/>
<xsl:text> return 4 + </xsl:text>
<xsl:for-each select="//fields/field">
<xsl:value-of select="field:calcSize($fieldIterator,position(),@name,@size,@type)"/>
</xsl:for-each>;
}
</xsl:text>
<xsl:call-template name="indent"/>
<xsl:call-template name="indent"/>
<xsl:text>return 0</xsl:text>
<xsl:variable name="fieldIterator" select="field:new()"/>
<xsl:for-each select="//fields/field">
<xsl:value-of select="field:calcSize($fieldIterator,position(),@name,@size,@type)"/>
</xsl:for-each>
<xsl:text>;</xsl:text>
<xsl:call-template name="linebreak"/>
<xsl:call-template name="indent"/>
<xsl:text>}</xsl:text>
<xsl:call-template name="linebreak"/>
</xsl:if>
<xsl:call-template name="linebreak"/>
@ -203,8 +211,6 @@ public abstract class </xsl:text><xsl:value-of select="@name"/><xsl:text>Abstrac
public void set<xsl:value-of select="recutil:getFieldName1stCap(@name,0)"/>( <xsl:value-of select="recutil:getBitFieldType(@name, @mask, ../@type)"/> value )
{
<xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/> = <xsl:value-of select="recutil:getBitFieldSet(@name, @mask, ../@type, recutil:getFieldName($fieldNum,../@name,0))"/>;
<!--<xsl:value-of select="recutil:getFieldName(@name,0)"/>.setValue(<xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/>, value);-->
}
/**
@ -214,13 +220,19 @@ public abstract class </xsl:text><xsl:value-of select="@name"/><xsl:text>Abstrac
public <xsl:value-of select="recutil:getBitFieldFunction(@name,@mask,../@type, 'true')"/>()
{
return <xsl:value-of select="recutil:getBitFieldGet(@name, @mask,../@type, recutil:getFieldName($fieldNum,../@name,0))"/>
<!--return <xsl:value-of select="recutil:getFieldName(@name,0)"/>.isSet(<xsl:value-of select="recutil:getFieldName($fieldNum,../@name,0)"/>);-->
}
</xsl:for-each>
</xsl:template>
<xsl:template match = "bit" > private static BitField <xsl:value-of select="@name"/> = new BitField(<xsl:value-of select="@mask"/>);
</xsl:template>
<xsl:template match="bit">
<xsl:call-template name="indent"/>
<xsl:text>/**/private static BitField </xsl:text>
<xsl:value-of select="@name"/>
<xsl:text> = new BitField(</xsl:text>
<xsl:value-of select="@mask"/>
<xsl:text>);</xsl:text>
<xsl:call-template name="linebreak"/>
</xsl:template>
<xsl:template match="const">
<xsl:if test="@description">