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 95d61faec0
commit b12828a65e
7 changed files with 334 additions and 127 deletions

View File

@ -34,6 +34,8 @@
<changes> <changes>
<release version="3.8-beta5" date="2011-??-??"> <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">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">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> <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; package org.apache.poi.hwpf.model;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays; 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.hwpf.model.io.HWPFOutputStream;
import org.apache.poi.util.Internal; import org.apache.poi.util.Internal;
@ -25,17 +31,45 @@ import org.apache.poi.util.Internal;
@Internal @Internal
public class BookmarksTables public class BookmarksTables
{ {
private static final POILogger logger = POILogFactory
.getLogger( BookmarksTables.class );
private PlexOfCps descriptorsFirst = new PlexOfCps( 4 ); private PlexOfCps descriptorsFirst = new PlexOfCps( 4 );
private PlexOfCps descriptorsLim = new PlexOfCps( 0 ); 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 ) public BookmarksTables( byte[] tableStream, FileInformationBlock fib )
{ {
read( tableStream, 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() public int getBookmarksCount()
{ {
return descriptorsFirst.length(); return descriptorsFirst.length();
@ -70,14 +104,14 @@ public class BookmarksTables
return descriptorsLim.length(); 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() public int getNamesCount()
{ {
return names.length; return names.size();
} }
private void read( byte[] tableStream, FileInformationBlock fib ) private void read( byte[] tableStream, FileInformationBlock fib )
@ -86,7 +120,8 @@ public class BookmarksTables
int namesLength = fib.getLcbSttbfbkmk(); int namesLength = fib.getLcbSttbfbkmk();
if ( namesStart != 0 && namesLength != 0 ) 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 firstDescriptorsStart = fib.getFcPlcfbkf();
int firstDescriptorsLength = fib.getLcbPlcfbkf(); int firstDescriptorsLength = fib.getLcbPlcfbkf();
@ -102,15 +137,16 @@ public class BookmarksTables
limDescriptorsLength, 0 ); limDescriptorsLength, 0 );
} }
public void remove( int index )
{
descriptorsFirst.remove( index );
descriptorsLim.remove( index );
names.remove( index );
}
public void setName( int index, String name ) public void setName( int index, String name )
{ {
if ( index < names.length ) names.set( index, name );
{
String[] newNames = new String[index + 1];
System.arraycopy( names, 0, newNames, 0, names.length );
names = newNames;
}
names[index] = name;
} }
public void writePlcfBkmkf( FileInformationBlock fib, public void writePlcfBkmkf( FileInformationBlock fib,
@ -152,7 +188,7 @@ public class BookmarksTables
public void writeSttbfBkmk( FileInformationBlock fib, public void writeSttbfBkmk( FileInformationBlock fib,
HWPFOutputStream tableStream ) throws IOException HWPFOutputStream tableStream ) throws IOException
{ {
if ( names == null || names.length == 0 ) if ( names == null || names.isEmpty() )
{ {
fib.setFcSttbfbkmk( 0 ); fib.setFcSttbfbkmk( 0 );
fib.setLcbSttbfbkmk( 0 ); fib.setLcbSttbfbkmk( 0 );
@ -160,7 +196,8 @@ public class BookmarksTables
} }
int start = tableStream.getOffset(); int start = tableStream.getOffset();
SttbfUtils.write( tableStream, names ); SttbfUtils
.write( tableStream, names.toArray( new String[names.size()] ) );
int end = tableStream.getOffset(); int end = tableStream.getOffset();
fib.setFcSttbfbkmk( start ); fib.setFcSttbfbkmk( start );

View File

@ -19,6 +19,7 @@ package org.apache.poi.hwpf.model;
import java.util.ArrayList; import java.util.ArrayList;
import org.apache.poi.util.Internal;
import org.apache.poi.util.LittleEndian; 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 ) public GenericPropertyNode getProperty( int index )
{ {
return _props.get( index ); return _props.get( index );
@ -74,6 +105,13 @@ public final class PlexOfCps
public void addProperty( GenericPropertyNode node ) public void addProperty( GenericPropertyNode node )
{ {
_props.add( node ); _props.add( node );
_iMac++;
}
void remove( int index )
{
_props.remove( index );
_iMac--;
} }
public byte[] toByteArray() public byte[] toByteArray()

View File

@ -47,4 +47,12 @@ public interface Bookmarks
*/ */
Map<Integer, List<Bookmark>> getBookmarksStartedBetween( Map<Integer, List<Bookmark>> getBookmarksStartedBetween(
int startInclusive, int endExclusive ); 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 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 class BookmarkImpl implements Bookmark
{ {
private final GenericPropertyNode first; 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.List;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import org.apache.poi.util.Internal;
import org.apache.poi.hwpf.model.BytePropertyNode; import org.apache.poi.hwpf.model.BytePropertyNode;
import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.HWPFDocument;
@ -330,6 +332,11 @@ public class Range { // TODO -instantiable superclass
_doc.getCharacterTable().adjustForInsert( _charStart, text.length() ); _doc.getCharacterTable().adjustForInsert( _charStart, text.length() );
_doc.getParagraphTable().adjustForInsert( _parStart, text.length() ); _doc.getParagraphTable().adjustForInsert( _parStart, text.length() );
_doc.getSectionTable().adjustForInsert( _sectionStart, text.length() ); _doc.getSectionTable().adjustForInsert( _sectionStart, text.length() );
if ( _doc instanceof HWPFDocument )
{
( (BookmarksImpl) ( (HWPFDocument) _doc ).getBookmarks() )
.afterInsert( _start, text.length() );
}
adjustForInsert( text.length() ); adjustForInsert( text.length() );
// update the FIB.CCPText + friends fields // update the FIB.CCPText + friends fields
@ -356,6 +363,11 @@ public class Range { // TODO -instantiable superclass
_doc.getCharacterTable().adjustForInsert( _charEnd - 1, text.length() ); _doc.getCharacterTable().adjustForInsert( _charEnd - 1, text.length() );
_doc.getParagraphTable().adjustForInsert( _parEnd - 1, text.length() ); _doc.getParagraphTable().adjustForInsert( _parEnd - 1, text.length() );
_doc.getSectionTable().adjustForInsert( _sectionEnd - 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() ); adjustForInsert( text.length() );
assert sanityCheck(); assert sanityCheck();
@ -558,6 +570,12 @@ public class Range { // TODO -instantiable superclass
// + " -> " + sepx.getEnd()); // + " -> " + sepx.getEnd());
} }
if ( _doc instanceof HWPFDocument )
{
( (BookmarksImpl) ( (HWPFDocument) _doc ).getBookmarks() )
.afterDelete( _start, ( _end - _start ) );
}
_text.delete( _start, _end ); _text.delete( _start, _end );
Range parent = _parent.get(); Range parent = _parent.get();
if ( parent != null ) if ( parent != null )
@ -703,6 +721,35 @@ public class Range { // TODO -instantiable superclass
return (ListEntry) insertAfter(props, styleIndex); 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... * 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 * The offset or index where the text to be replaced begins
* (relative to/within this <code>Range</code>) * (relative to/within this <code>Range</code>)
*/ */
@Internal
public void replaceText(String pPlaceHolder, String pValue, int pOffset) { public void replaceText(String pPlaceHolder, String pValue, int pOffset) {
int absPlaceHolderIndex = getStartOffset() + pOffset; int absPlaceHolderIndex = getStartOffset() + pOffset;

View File

@ -16,6 +16,8 @@
==================================================================== */ ==================================================================== */
package org.apache.poi.hwpf.model; package org.apache.poi.hwpf.model;
import org.apache.poi.hwpf.usermodel.Range;
import junit.framework.TestCase; import junit.framework.TestCase;
import org.apache.poi.hwpf.HWPFDocument; import org.apache.poi.hwpf.HWPFDocument;
@ -43,4 +45,52 @@ public class TestBookmarksTables extends TestCase
assertEquals( 27, bookmark.getStart() ); assertEquals( 27, bookmark.getStart() );
assertEquals( 38, bookmark.getEnd() ); 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() );
}
} }