diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 6019d3c14..82000db29 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -38,7 +38,7 @@ 51481 - Fixed autofilters in HSSF to avoid warnings in Excel 2007 51533 - Avoid exception when changing name of a sheet containing shared formulas Support for appending images to existing drawings in HSSF - Added initial support for bookmarks in HWFP + Added initial support for bookmarks in HWPF 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 b8a2cd5f8..cc1b28ce0 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java @@ -48,6 +48,8 @@ import org.apache.poi.hwpf.model.TextPiece; import org.apache.poi.hwpf.model.TextPieceTable; import org.apache.poi.hwpf.model.io.HWPFFileSystem; import org.apache.poi.hwpf.model.io.HWPFOutputStream; +import org.apache.poi.hwpf.usermodel.Bookmarks; +import org.apache.poi.hwpf.usermodel.BookmarksImpl; import org.apache.poi.hwpf.usermodel.HWPFList; import org.apache.poi.hwpf.usermodel.Range; import org.apache.poi.poifs.common.POIFSConstants; @@ -102,9 +104,12 @@ public final class HWPFDocument extends HWPFDocumentCore /** Holds Office Art objects */ protected ShapesTable _officeArts; - /** Holds the bookmarks */ + /** Holds the bookmarks tables */ protected BookmarksTables _bookmarksTables; - + + /** Holds the bookmarks */ + protected Bookmarks _bookmarks; + /** Holds the fields PLCFs */ protected FieldsTables _fieldsTables; @@ -267,6 +272,7 @@ public final class HWPFDocument extends HWPFDocumentCore } _bookmarksTables = new BookmarksTables( _tableStream, _fib ); + _bookmarks = new BookmarksImpl( _bookmarksTables ); _fieldsTables = new FieldsTables(_tableStream, _fib); } @@ -444,12 +450,11 @@ public final class HWPFDocument extends HWPFDocumentCore } /** - * @return BookmarksTables object, that is able to extract bookmarks - * descriptors from this document + * @return user-friendly interface to access document bookmarks */ - public BookmarksTables getBookmarksTables() + public Bookmarks getBookmarks() { - return _bookmarksTables; + return _bookmarks; } /** diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java b/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java index 2dd19d42d..ead86a60d 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java @@ -1,10 +1,25 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ package org.apache.poi.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 { @@ -14,67 +29,55 @@ public class BookmarksTables 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(); } + public GenericPropertyNode getDescriptorFirst( int index ) + throws IndexOutOfBoundsException + { + return descriptorsFirst.getProperty( index ); + } + + public int getDescriptorFirstIndex( GenericPropertyNode descriptorFirst ) + { + // TODO: very non-optimal + return Arrays.asList( descriptorsFirst.toPropertiesArray() ).indexOf( + descriptorFirst ); + } + + public GenericPropertyNode getDescriptorLim( int index ) + throws IndexOutOfBoundsException + { + return descriptorsLim.getProperty( index ); + } + + public int getDescriptorsFirstCount() + { + return descriptorsFirst.length(); + } + + public int getDescriptorsLimCount() + { + return descriptorsLim.length(); + } + + public String getName( int index ) throws ArrayIndexOutOfBoundsException + { + return names[index]; + } + + public int getNamesCount() + { + return names.length; + } + private void read( byte[] tableStream, FileInformationBlock fib ) { int namesStart = fib.getFcSttbfbkmk(); @@ -97,6 +100,17 @@ public class BookmarksTables limDescriptorsLength, 0 ); } + public void setName( int index, String name ) + { + if ( index < names.length ) + { + String[] newNames = new String[index + 1]; + System.arraycopy( names, 0, newNames, 0, names.length ); + names = newNames; + } + names[index] = name; + } + public void writePlcfBkmkf( FileInformationBlock fib, HWPFOutputStream tableStream ) throws IOException { diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/PropertyNode.java b/src/scratchpad/src/org/apache/poi/hwpf/model/PropertyNode.java index 78f4b35bf..ed6c2ac38 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/PropertyNode.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/PropertyNode.java @@ -35,9 +35,10 @@ import org.apache.poi.util.POILogger; public abstract class PropertyNode> implements Comparable, Cloneable { - static final class EndComparator implements Comparator> + public static final class EndComparator implements + Comparator> { - static EndComparator instance = new EndComparator(); + public static EndComparator instance = new EndComparator(); public int compare( PropertyNode o1, PropertyNode o2 ) { @@ -48,9 +49,10 @@ public abstract class PropertyNode> implements Compar } } - static final class StartComparator implements Comparator> + public static final class StartComparator implements + Comparator> { - static StartComparator instance = new StartComparator(); + public static StartComparator instance = new StartComparator(); public int compare( PropertyNode o1, PropertyNode o2 ) { diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmarks.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmarks.java new file mode 100644 index 000000000..636746bec --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmarks.java @@ -0,0 +1,50 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hwpf.usermodel; + +import java.util.List; +import java.util.Map; + +/** + * User-friendly interface to access document bookmarks + * + * @author Sergey Vladimirov (vlsergey {at} gmail {dot} com) + */ +public interface Bookmarks +{ + /** + * @param index + * bookmark document index + * @return {@link Bookmark} with specified index + * @throws IndexOutOfBoundsException + * if bookmark with specified index not present in document + */ + Bookmark getBookmark( int index ) throws IndexOutOfBoundsException; + + /** + * @return count of {@link Bookmark}s in document + */ + int getBookmarksCount(); + + /** + * @return {@link Map} of bookmarks started in specified range, where key is + * start position and value is sorted {@link List} of + * {@link Bookmark} + */ + Map> getBookmarksStartedBetween( + int startInclusive, int endExclusive ); +} diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BookmarksImpl.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BookmarksImpl.java new file mode 100644 index 000000000..40b40644b --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BookmarksImpl.java @@ -0,0 +1,189 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hwpf.usermodel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.poi.hwpf.model.BookmarksTables; +import org.apache.poi.hwpf.model.GenericPropertyNode; +import org.apache.poi.hwpf.model.PropertyNode; + +/** + * Implementation of user-friendly interface for document bookmarks + * + * @author Sergey Vladimirov (vlsergey {at} gmail {doc} com) + */ +public class BookmarksImpl implements Bookmarks +{ + + private final BookmarksTables bookmarksTables; + + private Map> sortedDescriptors = null; + + private int[] sortedStartPositions = null; + + public BookmarksImpl( BookmarksTables bookmarksTables ) + { + this.bookmarksTables = bookmarksTables; + } + + private Bookmark getBookmark( final GenericPropertyNode first ) + { + return new Bookmark() + { + public int getEnd() + { + int currentIndex = bookmarksTables + .getDescriptorFirstIndex( first ); + try + { + GenericPropertyNode descriptorLim = bookmarksTables + .getDescriptorLim( currentIndex ); + return descriptorLim.getStart(); + } + catch ( IndexOutOfBoundsException exc ) + { + return first.getEnd(); + } + } + + public String getName() + { + int currentIndex = bookmarksTables + .getDescriptorFirstIndex( first ); + try + { + return bookmarksTables.getName( currentIndex ); + } + catch ( ArrayIndexOutOfBoundsException exc ) + { + return ""; + } + } + + public int getStart() + { + return first.getStart(); + } + + public void setName( String name ) + { + int currentIndex = bookmarksTables + .getDescriptorFirstIndex( first ); + bookmarksTables.setName( currentIndex, name ); + } + }; + } + + public Bookmark getBookmark( int index ) + { + final GenericPropertyNode first = bookmarksTables + .getDescriptorFirst( index ); + return getBookmark( first ); + } + + public List getBookmarksAt( int startCp ) + { + updateSortedDescriptors(); + + List nodes = sortedDescriptors.get( Integer + .valueOf( startCp ) ); + if ( nodes == null || nodes.isEmpty() ) + return Collections.emptyList(); + + List result = new ArrayList( nodes.size() ); + for ( GenericPropertyNode node : nodes ) + { + result.add( getBookmark( node ) ); + } + return Collections.unmodifiableList( result ); + } + + public int getBookmarksCount() + { + return bookmarksTables.getDescriptorsFirstCount(); + } + + public Map> getBookmarksStartedBetween( + int startInclusive, int endExclusive ) + { + updateSortedDescriptors(); + + int startLookupIndex = Arrays.binarySearch( this.sortedStartPositions, + startInclusive ); + if ( startLookupIndex < 0 ) + startLookupIndex = -( startLookupIndex + 1 ); + int endLookupIndex = Arrays.binarySearch( this.sortedStartPositions, + endExclusive ); + if ( endLookupIndex < 0 ) + endLookupIndex = -( endLookupIndex + 1 ); + + Map> result = new LinkedHashMap>(); + for ( int lookupIndex = startLookupIndex; lookupIndex < endLookupIndex; lookupIndex++ ) + { + int s = sortedStartPositions[lookupIndex]; + List startedAt = getBookmarksAt( s ); + if ( startedAt != null ) + result.put( Integer.valueOf( s ), startedAt ); + } + + return Collections.unmodifiableMap( result ); + } + + private void updateSortedDescriptors() + { + if ( sortedDescriptors != null ) + return; + + Map> result = new HashMap>(); + for ( int b = 0; b < bookmarksTables.getDescriptorsFirstCount(); b++ ) + { + GenericPropertyNode property = bookmarksTables + .getDescriptorFirst( b ); + Integer positionKey = Integer.valueOf( property.getStart() ); + List atPositionList = result.get( positionKey ); + if ( atPositionList == null ) + { + atPositionList = new LinkedList(); + result.put( positionKey, atPositionList ); + } + atPositionList.add( property ); + } + + int counter = 0; + int[] indices = new int[result.size()]; + for ( Map.Entry> entry : result + .entrySet() ) + { + indices[counter++] = entry.getKey().intValue(); + List updated = new ArrayList( + entry.getValue() ); + Collections.sort( updated, PropertyNode.EndComparator.instance ); + entry.setValue( updated ); + } + + this.sortedDescriptors = result; + this.sortedStartPositions = indices; + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java b/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java index 7b0b9b0f7..ced953c7f 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java @@ -1,3 +1,19 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ package org.apache.poi.hwpf.model; import junit.framework.TestCase; @@ -5,17 +21,24 @@ import junit.framework.TestCase; import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.HWPFTestDataSamples; import org.apache.poi.hwpf.usermodel.Bookmark; +import org.apache.poi.hwpf.usermodel.Bookmarks; +/** + * Test cases for {@link BookmarksTables} and default implementation of + * {@link Bookmarks} + * + * @author Sergey Vladimirov (vlsergey {at} gmail {dot} com) + */ public class TestBookmarksTables extends TestCase { public void test() { HWPFDocument doc = HWPFTestDataSamples.openSampleFile( "pageref.doc" ); - BookmarksTables bookmarksTables = doc.getBookmarksTables(); + Bookmarks bookmarks = doc.getBookmarks(); - assertEquals( 1, bookmarksTables.getBookmarksCount() ); + assertEquals( 1, bookmarks.getBookmarksCount() ); - Bookmark bookmark = bookmarksTables.getBookmark( 0 ); + Bookmark bookmark = bookmarks.getBookmark( 0 ); assertEquals( "userref", bookmark.getName() ); assertEquals( 27, bookmark.getStart() ); assertEquals( 38, bookmark.getEnd() );