From 00e43b57cebfcfe87732d61fd7569fbf5907550e Mon Sep 17 00:00:00 2001 From: Sergey Vladimirov Date: Wed, 14 Sep 2011 05:41:21 +0000 Subject: [PATCH] HWPF Bookmarks tables are correctly updated on text updates. Add HWPF API to update range text and delete bookmarks. git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1170437 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 2 + .../poi/hwpf/model/BookmarksTables.java | 65 ++++- .../org/apache/poi/hwpf/model/PlexOfCps.java | 38 +++ .../apache/poi/hwpf/usermodel/Bookmarks.java | 8 + .../poi/hwpf/usermodel/BookmarksImpl.java | 250 ++++++++++-------- .../org/apache/poi/hwpf/usermodel/Range.java | 48 ++++ .../poi/hwpf/model/TestBookmarksTables.java | 50 ++++ 7 files changed, 334 insertions(+), 127 deletions(-) diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index d8a08883d1..039edc3251 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,8 @@ + Add HWPF API to update range text and delete bookmarks + HWPF Bookmarks tables are correctly updated on text updates 51670 - avoid LeftoverDataException when reading .xls files with invalid LabelRecords 51196 - prevent NPE in XWPFPicture.getPictureData() 51771 - prevent NPE when getting object data from OLEShape in HSLF 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 127cbc3683..bc2d21f15c 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/BookmarksTables.java @@ -17,7 +17,13 @@ package org.apache.poi.hwpf.model; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; + +import org.apache.poi.util.POILogFactory; + +import org.apache.poi.util.POILogger; import org.apache.poi.hwpf.model.io.HWPFOutputStream; import org.apache.poi.util.Internal; @@ -25,17 +31,45 @@ import org.apache.poi.util.Internal; @Internal public class BookmarksTables { + private static final POILogger logger = POILogFactory + .getLogger( BookmarksTables.class ); + private PlexOfCps descriptorsFirst = new PlexOfCps( 4 ); private PlexOfCps descriptorsLim = new PlexOfCps( 0 ); - private String[] names = new String[0]; + private List names = new ArrayList( 0 ); public BookmarksTables( byte[] tableStream, FileInformationBlock fib ) { read( tableStream, fib ); } + public void afterDelete( int startCp, int length ) + { + descriptorsFirst.adjust( startCp, -length ); + descriptorsLim.adjust( startCp, -length ); + for ( int i = 0; i < descriptorsFirst.length(); i++ ) + { + GenericPropertyNode startNode = descriptorsFirst.getProperty( i ); + GenericPropertyNode endNode = descriptorsLim.getProperty( i ); + if ( startNode.getStart() == endNode.getStart() ) + { + logger.log( POILogger.DEBUG, "Removing bookmark #", + Integer.valueOf( i ), "..." ); + remove( i ); + i--; + continue; + } + } + } + + public void afterInsert( int startCp, int length ) + { + descriptorsFirst.adjust( startCp, length ); + descriptorsLim.adjust( startCp - 1, length ); + } + public int getBookmarksCount() { return descriptorsFirst.length(); @@ -70,14 +104,14 @@ public class BookmarksTables return descriptorsLim.length(); } - public String getName( int index ) throws ArrayIndexOutOfBoundsException + public String getName( int index ) { - return names[index]; + return names.get( index ); } public int getNamesCount() { - return names.length; + return names.size(); } private void read( byte[] tableStream, FileInformationBlock fib ) @@ -86,7 +120,8 @@ public class BookmarksTables int namesLength = fib.getLcbSttbfbkmk(); if ( namesStart != 0 && namesLength != 0 ) - this.names = SttbfUtils.read( tableStream, namesStart ); + this.names = new ArrayList( Arrays.asList( SttbfUtils.read( + tableStream, namesStart ) ) ); int firstDescriptorsStart = fib.getFcPlcfbkf(); int firstDescriptorsLength = fib.getLcbPlcfbkf(); @@ -102,15 +137,16 @@ public class BookmarksTables limDescriptorsLength, 0 ); } + public void remove( int index ) + { + descriptorsFirst.remove( index ); + descriptorsLim.remove( index ); + names.remove( index ); + } + 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; + names.set( index, name ); } public void writePlcfBkmkf( FileInformationBlock fib, @@ -152,7 +188,7 @@ public class BookmarksTables public void writeSttbfBkmk( FileInformationBlock fib, HWPFOutputStream tableStream ) throws IOException { - if ( names == null || names.length == 0 ) + if ( names == null || names.isEmpty() ) { fib.setFcSttbfbkmk( 0 ); fib.setLcbSttbfbkmk( 0 ); @@ -160,7 +196,8 @@ public class BookmarksTables } int start = tableStream.getOffset(); - SttbfUtils.write( tableStream, names ); + SttbfUtils + .write( tableStream, names.toArray( new String[names.size()] ) ); int end = tableStream.getOffset(); fib.setFcSttbfbkmk( start ); diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/PlexOfCps.java b/src/scratchpad/src/org/apache/poi/hwpf/model/PlexOfCps.java index 64b05097ec..81c20d1621 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/PlexOfCps.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/PlexOfCps.java @@ -19,6 +19,7 @@ package org.apache.poi.hwpf.model; import java.util.ArrayList; +import org.apache.poi.util.Internal; import org.apache.poi.util.LittleEndian; /** @@ -66,6 +67,36 @@ public final class PlexOfCps } } + @Internal + void adjust( int startCp, int shift ) + { + for ( GenericPropertyNode node : _props ) + { + if ( node.getStart() > startCp ) + { + if ( node.getStart() + shift < startCp ) + { + node.setStart( startCp ); + } + else + { + node.setStart( node.getStart() + shift ); + } + } + if ( node.getEnd() >= startCp ) + { + if ( node.getEnd() + shift < startCp ) + { + node.setEnd( startCp ); + } + else + { + node.setEnd( node.getEnd() + shift ); + } + } + } + } + public GenericPropertyNode getProperty( int index ) { return _props.get( index ); @@ -74,6 +105,13 @@ public final class PlexOfCps public void addProperty( GenericPropertyNode node ) { _props.add( node ); + _iMac++; + } + + void remove( int index ) + { + _props.remove( index ); + _iMac--; } public byte[] toByteArray() diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmarks.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmarks.java index 636746becf..c4dae5e9db 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmarks.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Bookmarks.java @@ -47,4 +47,12 @@ public interface Bookmarks */ Map> getBookmarksStartedBetween( int startInclusive, int endExclusive ); + + /** + * Remove bookmark from document (but not the bookmark text) + * + * @param index + * bookmark document index to be removed + */ + void remove( int index ); } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BookmarksImpl.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BookmarksImpl.java index 8a1c88af4c..ce4df164be 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BookmarksImpl.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/BookmarksImpl.java @@ -37,119 +37,6 @@ import org.apache.poi.hwpf.model.PropertyNode; 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 BookmarkImpl( first ); - } - - 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]; - if ( s < startInclusive ) - continue; - if ( s >= endExclusive ) - break; - - 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 ); - } - Arrays.sort( indices ); - - this.sortedDescriptors = result; - this.sortedStartPositions = indices; - } - private final class BookmarkImpl implements Bookmark { private final GenericPropertyNode first; @@ -232,4 +119,141 @@ public class BookmarksImpl implements Bookmarks } } + + private final BookmarksTables bookmarksTables; + + private Map> sortedDescriptors = null; + + private int[] sortedStartPositions = null; + + public BookmarksImpl( BookmarksTables bookmarksTables ) + { + this.bookmarksTables = bookmarksTables; + reset(); + } + + void afterDelete( int startCp, int length ) + { + bookmarksTables.afterDelete( startCp, length ); + reset(); + } + + void afterInsert( int startCp, int length ) + { + bookmarksTables.afterInsert( startCp, length ); + reset(); + } + + private Bookmark getBookmark( final GenericPropertyNode first ) + { + return new BookmarkImpl( first ); + } + + 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]; + if ( s < startInclusive ) + continue; + if ( s >= endExclusive ) + break; + + List startedAt = getBookmarksAt( s ); + if ( startedAt != null ) + result.put( Integer.valueOf( s ), startedAt ); + } + + return Collections.unmodifiableMap( result ); + } + + public void remove( int index ) + { + bookmarksTables.remove( index ); + } + + private void reset() + { + sortedDescriptors = null; + sortedStartPositions = null; + } + + 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 ); + } + Arrays.sort( indices ); + + this.sortedDescriptors = result; + this.sortedStartPositions = indices; + } } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java index 47cba3f950..7c662fae1a 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java @@ -21,6 +21,8 @@ import java.lang.ref.WeakReference; import java.util.List; import java.util.NoSuchElementException; +import org.apache.poi.util.Internal; + import org.apache.poi.hwpf.model.BytePropertyNode; import org.apache.poi.hwpf.HWPFDocument; @@ -330,6 +332,11 @@ public class Range { // TODO -instantiable superclass _doc.getCharacterTable().adjustForInsert( _charStart, text.length() ); _doc.getParagraphTable().adjustForInsert( _parStart, text.length() ); _doc.getSectionTable().adjustForInsert( _sectionStart, text.length() ); + if ( _doc instanceof HWPFDocument ) + { + ( (BookmarksImpl) ( (HWPFDocument) _doc ).getBookmarks() ) + .afterInsert( _start, text.length() ); + } adjustForInsert( text.length() ); // update the FIB.CCPText + friends fields @@ -356,6 +363,11 @@ public class Range { // TODO -instantiable superclass _doc.getCharacterTable().adjustForInsert( _charEnd - 1, text.length() ); _doc.getParagraphTable().adjustForInsert( _parEnd - 1, text.length() ); _doc.getSectionTable().adjustForInsert( _sectionEnd - 1, text.length() ); + if ( _doc instanceof HWPFDocument ) + { + ( (BookmarksImpl) ( (HWPFDocument) _doc ).getBookmarks() ) + .afterInsert( _end, text.length() ); + } adjustForInsert( text.length() ); assert sanityCheck(); @@ -558,6 +570,12 @@ public class Range { // TODO -instantiable superclass // + " -> " + sepx.getEnd()); } + if ( _doc instanceof HWPFDocument ) + { + ( (BookmarksImpl) ( (HWPFDocument) _doc ).getBookmarks() ) + .afterDelete( _start, ( _end - _start ) ); + } + _text.delete( _start, _end ); Range parent = _parent.get(); if ( parent != null ) @@ -703,6 +721,35 @@ public class Range { // TODO -instantiable superclass return (ListEntry) insertAfter(props, styleIndex); } + /** + * Replace range text with new one, adding it to the range and deleting + * original text from document + * + * @param newText + * The text to be replaced with + * @param addAfter + * if true the text will be added at the end of current + * range, otherwise to the beginning + */ + public void replaceText( String newText, boolean addAfter ) + { + if ( addAfter ) + { + int originalEnd = getEndOffset(); + insertAfter( newText ); + new Range( getStartOffset(), originalEnd, this ).delete(); + } + else + { + int originalStart = getStartOffset(); + int originalEnd = getEndOffset(); + + insertBefore( newText ); + new Range( originalStart + newText.length(), originalEnd + + newText.length(), this ).delete(); + } + } + /** * Replace (one instance of) a piece of text with another... * @@ -714,6 +761,7 @@ public class Range { // TODO -instantiable superclass * The offset or index where the text to be replaced begins * (relative to/within this Range) */ + @Internal public void replaceText(String pPlaceHolder, String pValue, int pOffset) { int absPlaceHolderIndex = getStartOffset() + pOffset; 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 ced953c7f3..4e013b3b7d 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestBookmarksTables.java @@ -16,6 +16,8 @@ ==================================================================== */ package org.apache.poi.hwpf.model; +import org.apache.poi.hwpf.usermodel.Range; + import junit.framework.TestCase; import org.apache.poi.hwpf.HWPFDocument; @@ -43,4 +45,52 @@ public class TestBookmarksTables extends TestCase assertEquals( 27, bookmark.getStart() ); assertEquals( 38, bookmark.getEnd() ); } + + public void testDeleteRange() + { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile( "pageref.doc" ); + Range range = new Range( 27, 41, doc ); + range.delete(); + + assertEquals( 0, doc.getBookmarks().getBookmarksCount() ); + } + + public void testReplaceTextAfter() + { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile( "pageref.doc" ); + Bookmark bookmark = doc.getBookmarks().getBookmark( 0 ); + Range range = new Range( bookmark.getStart(), bookmark.getEnd(), doc ); + range.replaceText( "1destin2ation3", true ); + + bookmark = doc.getBookmarks().getBookmark( 0 ); + assertEquals( "userref", bookmark.getName() ); + assertEquals( 27, bookmark.getStart() ); + assertEquals( 41, bookmark.getEnd() ); + } + + public void testReplaceTextBefore() + { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile( "pageref.doc" ); + Bookmark bookmark = doc.getBookmarks().getBookmark( 0 ); + Range range = new Range( bookmark.getStart(), bookmark.getEnd(), doc ); + range.replaceText( "1destin2ation3", false ); + + bookmark = doc.getBookmarks().getBookmark( 0 ); + assertEquals( "userref", bookmark.getName() ); + assertEquals( 27, bookmark.getStart() ); + assertEquals( 41, bookmark.getEnd() ); + } + + public void testUpdateText() + { + HWPFDocument doc = HWPFTestDataSamples.openSampleFile( "pageref.doc" ); + Bookmark bookmark = doc.getBookmarks().getBookmark( 0 ); + Range range = new Range( bookmark.getStart(), bookmark.getEnd(), doc ); + range.replaceText( "destination", "1destin2ation3" ); + + bookmark = doc.getBookmarks().getBookmark( 0 ); + assertEquals( "userref", bookmark.getName() ); + assertEquals( 27, bookmark.getStart() ); + assertEquals( 41, bookmark.getEnd() ); + } }