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
This commit is contained in:
Sergey Vladimirov 2011-09-14 05:41:21 +00:00
parent a5042ec868
commit 00e43b57ce
7 changed files with 334 additions and 127 deletions

View File

@ -34,6 +34,8 @@
<changes>
<release version="3.8-beta5" date="2011-??-??">
<action dev="poi-developers" type="add">Add HWPF API to update range text and delete bookmarks</action>
<action dev="poi-developers" type="add">HWPF Bookmarks tables are correctly updated on text updates</action>
<action dev="poi-developers" type="add">51670 - avoid LeftoverDataException when reading .xls files with invalid LabelRecords</action>
<action dev="poi-developers" type="add">51196 - prevent NPE in XWPFPicture.getPictureData() </action>
<action dev="poi-developers" type="add">51771 - prevent NPE when getting object data from OLEShape in HSLF</action>

View File

@ -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<String> names = new ArrayList<String>( 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<String>( 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 );

View File

@ -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()

View File

@ -47,4 +47,12 @@ public interface Bookmarks
*/
Map<Integer, List<Bookmark>> 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 );
}

View File

@ -37,119 +37,6 @@ import org.apache.poi.hwpf.model.PropertyNode;
public class BookmarksImpl implements Bookmarks
{
private final BookmarksTables bookmarksTables;
private Map<Integer, List<GenericPropertyNode>> 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<Bookmark> getBookmarksAt( int startCp )
{
updateSortedDescriptors();
List<GenericPropertyNode> nodes = sortedDescriptors.get( Integer
.valueOf( startCp ) );
if ( nodes == null || nodes.isEmpty() )
return Collections.emptyList();
List<Bookmark> result = new ArrayList<Bookmark>( nodes.size() );
for ( GenericPropertyNode node : nodes )
{
result.add( getBookmark( node ) );
}
return Collections.unmodifiableList( result );
}
public int getBookmarksCount()
{
return bookmarksTables.getDescriptorsFirstCount();
}
public Map<Integer, List<Bookmark>> 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<Integer, List<Bookmark>> result = new LinkedHashMap<Integer, List<Bookmark>>();
for ( int lookupIndex = startLookupIndex; lookupIndex < endLookupIndex; lookupIndex++ )
{
int s = sortedStartPositions[lookupIndex];
if ( s < startInclusive )
continue;
if ( s >= endExclusive )
break;
List<Bookmark> startedAt = getBookmarksAt( s );
if ( startedAt != null )
result.put( Integer.valueOf( s ), startedAt );
}
return Collections.unmodifiableMap( result );
}
private void updateSortedDescriptors()
{
if ( sortedDescriptors != null )
return;
Map<Integer, List<GenericPropertyNode>> result = new HashMap<Integer, List<GenericPropertyNode>>();
for ( int b = 0; b < bookmarksTables.getDescriptorsFirstCount(); b++ )
{
GenericPropertyNode property = bookmarksTables
.getDescriptorFirst( b );
Integer positionKey = Integer.valueOf( property.getStart() );
List<GenericPropertyNode> atPositionList = result.get( positionKey );
if ( atPositionList == null )
{
atPositionList = new LinkedList<GenericPropertyNode>();
result.put( positionKey, atPositionList );
}
atPositionList.add( property );
}
int counter = 0;
int[] indices = new int[result.size()];
for ( Map.Entry<Integer, List<GenericPropertyNode>> entry : result
.entrySet() )
{
indices[counter++] = entry.getKey().intValue();
List<GenericPropertyNode> updated = new ArrayList<GenericPropertyNode>(
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<Integer, List<GenericPropertyNode>> 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<Bookmark> getBookmarksAt( int startCp )
{
updateSortedDescriptors();
List<GenericPropertyNode> nodes = sortedDescriptors.get( Integer
.valueOf( startCp ) );
if ( nodes == null || nodes.isEmpty() )
return Collections.emptyList();
List<Bookmark> result = new ArrayList<Bookmark>( nodes.size() );
for ( GenericPropertyNode node : nodes )
{
result.add( getBookmark( node ) );
}
return Collections.unmodifiableList( result );
}
public int getBookmarksCount()
{
return bookmarksTables.getDescriptorsFirstCount();
}
public Map<Integer, List<Bookmark>> 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<Integer, List<Bookmark>> result = new LinkedHashMap<Integer, List<Bookmark>>();
for ( int lookupIndex = startLookupIndex; lookupIndex < endLookupIndex; lookupIndex++ )
{
int s = sortedStartPositions[lookupIndex];
if ( s < startInclusive )
continue;
if ( s >= endExclusive )
break;
List<Bookmark> 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<Integer, List<GenericPropertyNode>> result = new HashMap<Integer, List<GenericPropertyNode>>();
for ( int b = 0; b < bookmarksTables.getDescriptorsFirstCount(); b++ )
{
GenericPropertyNode property = bookmarksTables
.getDescriptorFirst( b );
Integer positionKey = Integer.valueOf( property.getStart() );
List<GenericPropertyNode> atPositionList = result.get( positionKey );
if ( atPositionList == null )
{
atPositionList = new LinkedList<GenericPropertyNode>();
result.put( positionKey, atPositionList );
}
atPositionList.add( property );
}
int counter = 0;
int[] indices = new int[result.size()];
for ( Map.Entry<Integer, List<GenericPropertyNode>> entry : result
.entrySet() )
{
indices[counter++] = entry.getKey().intValue();
List<GenericPropertyNode> updated = new ArrayList<GenericPropertyNode>(
entry.getValue() );
Collections.sort( updated, PropertyNode.EndComparator.instance );
entry.setValue( updated );
}
Arrays.sort( indices );
this.sortedDescriptors = result;
this.sortedStartPositions = indices;
}
}

View File

@ -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 <tt>true</tt> 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 <code>Range</code>)
*/
@Internal
public void replaceText(String pPlaceHolder, String pValue, int pOffset) {
int absPlaceHolderIndex = getStartOffset() + pOffset;

View File

@ -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() );
}
}