From af6641682a317069f7121722a5d252a8becb4157 Mon Sep 17 00:00:00 2001 From: Sergey Vladimirov Date: Tue, 19 Jul 2011 16:18:27 +0000 Subject: [PATCH] added initial support for bookmark git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1148428 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../src/org/apache/poi/hwpf/HWPFDocument.java | 88 ++++++++- .../hwpf/model/BookmarkFirstDescriptor.java | 70 +++++++ .../poi/hwpf/model/BookmarksTables.java | 153 +++++++++++++++ .../poi/hwpf/model/FIBFieldHandler.java | 27 ++- .../poi/hwpf/model/FileInformationBlock.java | 146 ++++++++++++--- .../apache/poi/hwpf/model/SavedByTable.java | 102 +++++----- .../org/apache/poi/hwpf/model/SttbfUtils.java | 74 ++++++++ .../poi/hwpf/model/types/BKFAbstractType.java | 174 ++++++++++++++++++ .../apache/poi/hwpf/usermodel/Bookmark.java | 12 ++ .../org/apache/poi/hwpf/AllHWPFTests.java | 2 + .../poi/hwpf/model/TestBookmarksTables.java | 23 +++ src/types/definitions/bkf_type.xml | 44 +++++ src/types/styles/hdftype.xsl | 36 ++-- 14 files changed, 850 insertions(+), 102 deletions(-) create mode 100644 src/scratchpad/src/org/apache/poi/hwpf/model/BookmarkFirstDescriptor.java create mode 100644 src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java create mode 100644 src/scratchpad/src/org/apache/poi/hwpf/model/SttbfUtils.java create mode 100644 src/scratchpad/src/org/apache/poi/hwpf/model/types/BKFAbstractType.java create mode 100644 src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmark.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java create mode 100644 src/types/definitions/bkf_type.xml diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 613c304dc4..23fe366889 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + Added initial support for bookmarks in HWFP 46250 - Fixed cloning worksheets with images 51524 - PapBinTable constructor is slow (regression) 51514 - allow HSSFObjectData to work with both POIFS and NPOIFS diff --git a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java index 7bcfd98328..b8a2cd5f8b 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java @@ -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) { diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarkFirstDescriptor.java b/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarkFirstDescriptor.java new file mode 100644 index 0000000000..d6493f2397 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarkFirstDescriptor.java @@ -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(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java b/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java new file mode 100644 index 0000000000..2dd19d42d9 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java @@ -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 ); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/FIBFieldHandler.java b/src/scratchpad/src/org/apache/poi/hwpf/model/FIBFieldHandler.java index 8ec2f79fca..d5613a6948 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/FIBFieldHandler.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/FIBFieldHandler.java @@ -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 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++) diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java b/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java index d5d9c5046d..a15847d6ef 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/FileInformationBlock.java @@ -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 fieldSet = new HashSet(); - 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 knownFieldSet = new HashSet(); + 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; // } // } + } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/SavedByTable.java b/src/scratchpad/src/org/apache/poi/hwpf/model/SavedByTable.java index 91f8c5fb3c..88d3256ab2 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/SavedByTable.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/SavedByTable.java @@ -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); - } } - diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/SttbfUtils.java b/src/scratchpad/src/org/apache/poi/hwpf/model/SttbfUtils.java new file mode 100644 index 0000000000..de19edb956 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/SttbfUtils.java @@ -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; + } + +} diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/types/BKFAbstractType.java b/src/scratchpad/src/org/apache/poi/hwpf/model/types/BKFAbstractType.java new file mode 100644 index 0000000000..684797f879 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/types/BKFAbstractType.java @@ -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). + *

+ * 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 diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmark.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmark.java new file mode 100644 index 0000000000..9dfd6b6908 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmark.java @@ -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 ); +} diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/AllHWPFTests.java b/src/scratchpad/testcases/org/apache/poi/hwpf/AllHWPFTests.java index b75ba423c8..0a72d76031 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/AllHWPFTests.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/AllHWPFTests.java @@ -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 ); diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java b/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java new file mode 100644 index 0000000000..7b0b9b0f76 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java @@ -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() ); + } +} diff --git a/src/types/definitions/bkf_type.xml b/src/types/definitions/bkf_type.xml new file mode 100644 index 0000000000..32a4b1ca23 --- /dev/null +++ b/src/types/definitions/bkf_type.xml @@ -0,0 +1,44 @@ + + + + AbstractType + HDFType + BooKmark First descriptor (BKF). <p>Class and fields descriptions are + quoted + from Microsoft Office Word 97-2007 Binary File Format (.doc) Specification + + Sergey Vladimirov; according to Microsoft Office Word 97-2007 Binary File Format (.doc) + Specification + + + + + + + + + + + diff --git a/src/types/styles/hdftype.xsl b/src/types/styles/hdftype.xsl index 5a235e6234..fa21922840 100644 --- a/src/types/styles/hdftype.xsl +++ b/src/types/styles/hdftype.xsl @@ -150,17 +150,25 @@ public abstract class Abstrac } + /** - * Size of record (exluding 4 byte header) + * Size of record */ public static int getSize() { - - return 4 + - - -; - } + + + + return 0 + + + + + ; + + + } + @@ -203,8 +211,6 @@ public abstract class Abstrac public void set( value ) { = ; - - } /** @@ -214,13 +220,19 @@ public abstract class Abstrac public () { return - } - private static BitField = new BitField(); - + + + /**/private static BitField + + = new BitField( + + ); + +