<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>
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;
/** Holds Office Art objects */
protected ShapesTable _officeArts;
+ /** Holds the bookmarks */
+ protected BookmarksTables _bookmarksTables;
+
/** Holds the fields PLCFs */
protected FieldsTables _fieldsTables;
{
_rmat = new RevisionMarkAuthorTable(_tableStream, rmarkOffset, rmarkLength);
}
-
+
+ _bookmarksTables = new BookmarksTables( _tableStream, _fib );
_fieldsTables = new FieldsTables(_tableStream, _fib);
}
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
*/
// 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);
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);
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)
{
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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 );
+ }
+}
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;
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;
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;
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++)
fillFields(mainDocument, 0);
}
- 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());
- _fieldHandler = new FIBFieldHandler(mainDocument,
- FIBShortHandler.START + _shortHandler.sizeInBytes() + _longHandler.sizeInBytes(),
- tableStream, fieldSet, true);
+ public void fillVariableFields( byte[] mainDocument, byte[] tableStream )
+ {
+ _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 ) );
+
+ // 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
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);
// return null;
// }
// }
+
}
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.
*/
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.
* @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;
-
- // 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);
+ {
+// // 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);
+// }
+
+ // first value is mark for extended STTBF ;) -- sergey
+ String[] strings = SttbfUtils.read( tableStream, offset );
+
+ 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.
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);
- }
}
-
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+\r
+package org.apache.poi.hwpf.model.types;\r
+\r
+import org.apache.poi.util.BitField;\r
+import org.apache.poi.util.LittleEndian;\r
+\r
+/**\r
+ * BooKmark First descriptor (BKF).\r
+ * <p>\r
+ * Class and fields descriptions are quoted from Microsoft Office Word 97-2007\r
+ * Binary File Format (.doc) Specification\r
+ * \r
+ * NOTE: This source is automatically generated please do not modify this file.\r
+ * Either subclass or remove the record in src/types/definitions.\r
+ * \r
+ * @author Sergey Vladimirov; according to Microsoft Office Word 97-2007 Binary\r
+ * File Format (.doc) Specification\r
+ */\r
+public abstract class BKFAbstractType\r
+{\r
+\r
+ protected short field_1_ibkl;\r
+ protected short field_2_bkf_flags;\r
+ /**/private static BitField itcFirst = new BitField( 0x007F );\r
+ /**/private static BitField fPub = new BitField( 0x0080 );\r
+ /**/private static BitField itcLim = new BitField( 0x7F00 );\r
+ /**/private static BitField fCol = new BitField( 0x8000 );\r
+\r
+ protected BKFAbstractType()\r
+ {\r
+ }\r
+\r
+ protected void fillFields( byte[] data, int offset )\r
+ {\r
+ field_1_ibkl = LittleEndian.getShort(data, 0x0 + offset);\r
+ field_2_bkf_flags = LittleEndian.getShort(data, 0x2 + offset);\r
+ }\r
+\r
+ public void serialize( byte[] data, int offset )\r
+ {\r
+ LittleEndian.putShort(data, 0x0 + offset, field_1_ibkl);\r
+ LittleEndian.putShort(data, 0x2 + offset, field_2_bkf_flags);\r
+ }\r
+\r
+ /**\r
+ * Size of record\r
+ */\r
+ public static int getSize()\r
+ {\r
+ return 0 + 2 + 2;\r
+ }\r
+\r
+ public String toString()\r
+ {\r
+ StringBuilder builder = new StringBuilder();\r
+ builder.append("[BKF]\n");\r
+ builder.append(" .ibkl = ");\r
+ builder.append(" (").append(getIbkl()).append(" )\n");\r
+ builder.append(" .bkf_flags = ");\r
+ builder.append(" (").append(getBkf_flags()).append(" )\n");\r
+ builder.append(" .itcFirst = ").append(getItcFirst()).append('\n');\r
+ builder.append(" .fPub = ").append(isFPub()).append('\n');\r
+ builder.append(" .itcLim = ").append(getItcLim()).append('\n');\r
+ builder.append(" .fCol = ").append(isFCol()).append('\n');\r
+\r
+ builder.append("[/BKF]\n");\r
+ return builder.toString();\r
+ }\r
+\r
+ /**\r
+ * Index to BKL entry in plcfbkl that describes the ending position of this bookmark in the CP stream.\r
+ */\r
+ public short getIbkl()\r
+ {\r
+ return field_1_ibkl;\r
+ }\r
+\r
+ /**\r
+ * Index to BKL entry in plcfbkl that describes the ending position of this bookmark in the CP stream.\r
+ */\r
+ public void setIbkl( short field_1_ibkl )\r
+ {\r
+ this.field_1_ibkl = field_1_ibkl;\r
+ }\r
+\r
+ /**\r
+ * Get the bkf_flags field for the BKF record.\r
+ */\r
+ public short getBkf_flags()\r
+ {\r
+ return field_2_bkf_flags;\r
+ }\r
+\r
+ /**\r
+ * Set the bkf_flags field for the BKF record.\r
+ */\r
+ public void setBkf_flags( short field_2_bkf_flags )\r
+ {\r
+ this.field_2_bkf_flags = field_2_bkf_flags;\r
+ }\r
+\r
+ /**\r
+ * Sets the itcFirst field value.\r
+ * When bkf.fCol==1, this is the index to the first column of a table column bookmark\r
+ */\r
+ public void setItcFirst( byte value )\r
+ {\r
+ field_2_bkf_flags = (short)itcFirst.setValue(field_2_bkf_flags, value);\r
+ }\r
+\r
+ /**\r
+ * When bkf.fCol==1, this is the index to the first column of a table column bookmark\r
+ * @return the itcFirst field value.\r
+ */\r
+ public byte getItcFirst()\r
+ {\r
+ return ( byte )itcFirst.getValue(field_2_bkf_flags);\r
+ }\r
+\r
+ /**\r
+ * Sets the fPub field value.\r
+ * When 1, this indicates that this bookmark is marking the range of a Macintosh Publisher section\r
+ */\r
+ public void setFPub( boolean value )\r
+ {\r
+ field_2_bkf_flags = (short)fPub.setBoolean(field_2_bkf_flags, value);\r
+ }\r
+\r
+ /**\r
+ * When 1, this indicates that this bookmark is marking the range of a Macintosh Publisher section\r
+ * @return the fPub field value.\r
+ */\r
+ public boolean isFPub()\r
+ {\r
+ return fPub.isSet(field_2_bkf_flags);\r
+ }\r
+\r
+ /**\r
+ * Sets the itcLim field value.\r
+ * When bkf.fCol==1, this is the index to limit column of a table column bookmark\r
+ */\r
+ public void setItcLim( byte value )\r
+ {\r
+ field_2_bkf_flags = (short)itcLim.setValue(field_2_bkf_flags, value);\r
+ }\r
+\r
+ /**\r
+ * When bkf.fCol==1, this is the index to limit column of a table column bookmark\r
+ * @return the itcLim field value.\r
+ */\r
+ public byte getItcLim()\r
+ {\r
+ return ( byte )itcLim.getValue(field_2_bkf_flags);\r
+ }\r
+\r
+ /**\r
+ * Sets the fCol field value.\r
+ * When 1, this bookmark marks a range of columns in a table specified by (bkf.itcFirst, bkf.itcLim)\r
+ */\r
+ public void setFCol( boolean value )\r
+ {\r
+ field_2_bkf_flags = (short)fCol.setBoolean(field_2_bkf_flags, value);\r
+ }\r
+\r
+ /**\r
+ * When 1, this bookmark marks a range of columns in a table specified by (bkf.itcFirst, bkf.itcLim)\r
+ * @return the fCol field value.\r
+ */\r
+ public boolean isFCol()\r
+ {\r
+ return fCol.isSet(field_2_bkf_flags);\r
+ }\r
+\r
+} // END OF CLASS\r
--- /dev/null
+package org.apache.poi.hwpf.usermodel;
+
+public interface Bookmark
+{
+ public int getEnd();
+
+ public String getName();
+
+ public int getStart();
+
+ public void setName( String name );
+}
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;
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 );
--- /dev/null
+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() );
+ }
+}
--- /dev/null
+<?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). <p>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>
<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"/>
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);-->
}
/**
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">