refactor list format override structures (was marked with @Internal annotation)

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1389037 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Sergey Vladimirov 2012-09-23 12:56:09 +00:00
parent edae5cb060
commit 9f666b8aee
12 changed files with 509 additions and 363 deletions

View File

@ -17,6 +17,7 @@
package org.apache.poi.util;
/**
* Utility classes for dealing with arrays.
*
@ -129,6 +130,28 @@ public class ArrayUtil
return result;
}
/**
* Copies the specified array into specified result array, truncating or
* padding with zeros (if necessary) so the copy has the specified length.
* This method is temporary replace for Arrays.copyOf() until we start to
* require JDK 1.6.
*
* @param source
* the array to be copied
* @param result
* the array to be filled and returned
* @throws NegativeArraySizeException
* if <tt>newLength</tt> is negative
* @throws NullPointerException
* if <tt>original</tt> is null
*/
public static <T> T[] copyOf( T[] source, T[] result )
{
System.arraycopy( source, 0, result, 0,
Math.min( source.length, result.length ) );
return result;
}
/**
* Copies the specified range of the specified array into a new array.
* The initial index of the range (<tt>from</tt>) must lie between zero

View File

@ -30,13 +30,13 @@ import java.util.regex.Pattern;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.HWPFDocumentCore;
import org.apache.poi.hwpf.converter.AbstractWordUtils.NumberingState;
import org.apache.poi.hwpf.converter.FontReplacer.Triplet;
import org.apache.poi.hwpf.model.FieldsDocumentPart;
import org.apache.poi.hwpf.model.ListFormatOverride;
import org.apache.poi.hwpf.model.ListTables;
import org.apache.poi.hwpf.usermodel.Bookmark;
import org.apache.poi.hwpf.usermodel.CharacterRun;
import org.apache.poi.hwpf.usermodel.Field;
import org.apache.poi.hwpf.usermodel.HWPFList;
import org.apache.poi.hwpf.usermodel.Notes;
import org.apache.poi.hwpf.usermodel.OfficeDrawing;
import org.apache.poi.hwpf.usermodel.Paragraph;
@ -172,6 +172,8 @@ public abstract class AbstractWordConverter
private FontReplacer fontReplacer = new DefaultFontReplacer();
private NumberingState numberingState = new NumberingState();
private PicturesManager picturesManager;
/**
@ -1022,9 +1024,6 @@ public abstract class AbstractWordConverter
protected void processParagraphes( HWPFDocumentCore wordDocument,
Element flow, Range range, int currentTableLevel )
{
final ListTables listTables = wordDocument.getListTables();
int currentListInfo = 0;
final int paragraphs = range.numParagraphs();
for ( int p = 0; p < paragraphs; p++ )
{
@ -1054,38 +1053,17 @@ public abstract class AbstractWordConverter
processPageBreak( wordDocument, flow );
}
if ( paragraph.getIlfo() != currentListInfo )
if ( paragraph.isInList() )
{
currentListInfo = paragraph.getIlfo();
}
HWPFList hwpfList = paragraph.getList();
if ( currentListInfo != 0 )
{
if ( listTables != null )
{
final ListFormatOverride listFormatOverride = listTables
.getOverride( paragraph.getIlfo() );
String label = AbstractWordUtils.getBulletText( listTables,
paragraph, listFormatOverride.getLsid() );
String label = AbstractWordUtils.getBulletText( numberingState,
hwpfList, (char) paragraph.getIlvl() );
processParagraph( wordDocument, flow, currentTableLevel,
paragraph, label );
}
else
{
logger.log( POILogger.WARN,
"Paragraph #" + paragraph.getStartOffset() + "-"
+ paragraph.getEndOffset()
+ " has reference to list structure #"
+ currentListInfo
+ ", but listTables not defined in file" );
processParagraph( wordDocument, flow, currentTableLevel,
paragraph, AbstractWordUtils.EMPTY );
}
}
else
{
processParagraph( wordDocument, flow, currentTableLevel,
paragraph, AbstractWordUtils.EMPTY );

View File

@ -41,7 +41,8 @@ import org.apache.poi.hwpf.model.CHPX;
import org.apache.poi.hwpf.model.FieldsDocumentPart;
import org.apache.poi.hwpf.model.FileInformationBlock;
import org.apache.poi.hwpf.model.GenericPropertyNode;
import org.apache.poi.hwpf.model.ListFormatOverride;
import org.apache.poi.hwpf.model.LFO;
import org.apache.poi.hwpf.model.LFOData;
import org.apache.poi.hwpf.model.ListLevel;
import org.apache.poi.hwpf.model.ListTables;
import org.apache.poi.hwpf.model.PAPFormattedDiskPage;
@ -716,30 +717,36 @@ public final class HWPFLister
{
if ( paragraph.getIlfo() != 0 )
{
final ListFormatOverride listFormatOverride = listTables
.getOverride( paragraph.getIlfo() );
final LFO lfo = listTables.getLfo( paragraph.getIlfo() );
System.out.println( "PAP's LFO: " + lfo );
System.out.println( "PAP's LFO: " + listFormatOverride );
final LFOData lfoData = listTables.getLfoData( paragraph.getIlfo() );
System.out.println( "PAP's LFOData: " + lfoData );
final ListLevel listLevel = listTables.getLevel(
listFormatOverride.getLsid(), paragraph.getIlvl() );
if ( lfo != null )
{
final ListLevel listLevel = listTables.getLevel( lfo.getLsid(),
paragraph.getIlvl() );
System.out.println( "PAP's ListLevel: " + listLevel );
if ( listLevel.getGrpprlPapx() != null )
{
System.out.println( "PAP's ListLevel PAPX:" );
dumpSprms( new SprmIterator( listLevel.getGrpprlPapx(), 0 ),
dumpSprms(
new SprmIterator( listLevel.getGrpprlPapx(), 0 ),
"* " );
}
if ( listLevel.getGrpprlPapx() != null )
{
System.out.println( "PAP's ListLevel CHPX:" );
dumpSprms( new SprmIterator( listLevel.getGrpprlChpx(), 0 ),
dumpSprms(
new SprmIterator( listLevel.getGrpprlChpx(), 0 ),
"* " );
}
}
}
}
public void dumpTextPieces( boolean withText )
{

View File

@ -31,12 +31,18 @@ import org.apache.poi.util.LittleEndian;
* @author Sergey Vladimirov (vlsergey {at} gmail {dot} com)
*/
@Internal
class LFOData
public class LFOData
{
private int _cp;
private ListFormatOverrideLevel[] _rgLfoLvl;
public LFOData()
{
_cp = 0;
_rgLfoLvl = new ListFormatOverrideLevel[0];
}
LFOData( byte[] buf, int startOffset, int cLfolvl )
{
int offset = startOffset;
@ -52,17 +58,17 @@ class LFOData
}
}
int getCp()
public int getCp()
{
return _cp;
}
ListFormatOverrideLevel[] getRgLfoLvl()
public ListFormatOverrideLevel[] getRgLfoLvl()
{
return _rgLfoLvl;
}
int getSizeInBytes()
public int getSizeInBytes()
{
int result = 0;
result += LittleEndian.INT_SIZE;

View File

@ -19,15 +19,8 @@ package org.apache.poi.hwpf.model;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.NoSuchElementException;
import java.util.Set;
import org.apache.poi.hwpf.model.io.HWPFOutputStream;
import org.apache.poi.util.Internal;
@ -43,8 +36,11 @@ public final class ListTables
{
private static POILogger log = POILogFactory.getLogger(ListTables.class);
ListMap _listMap = new ListMap();
ArrayList<ListFormatOverride> _overrideList = new ArrayList<ListFormatOverride>();
/**
* Both PlfLst and the following LVLs
*/
private final LinkedHashMap<Integer, ListData> _listMap = new LinkedHashMap<Integer, ListData>();
private PlfLfo _plfLfo;
public ListTables()
{
@ -52,13 +48,13 @@ public final class ListTables
}
public ListTables( byte[] tableStream, final int lstOffset,
final int lfoOffset )
{
final int fcPlfLfo, final int lcbPlfLfo )
{
/*
* The PlfLst structure contains the list formatting information for
* the document. -- Page 425 of 621. [MS-DOC] -- v20110315 Word
* (.doc) Binary File Format
* The PlfLst structure contains the list formatting information for the
* document. -- Page 425 of 621. [MS-DOC] -- v20110315 Word (.doc)
* Binary File Format
*/
int offset = lstOffset;
@ -80,72 +76,15 @@ public final class ListTables
lst.setLevel( y, lvl );
}
}
}
{
/*
* The PlfLfo structure contains the list format override data for
* the document. -- Page 424 of 621. [MS-DOC] -- v20110315 Word
* (.doc) Binary File Format
*/
int offset = lfoOffset;
/*
* lfoMac (4 bytes): An unsigned integer that specifies the count of
* elements in both the rgLfo and rgLfoData arrays. -- Page 424 of
* 621. [MS-DOC] -- v20110315 Word (.doc) Binary File Format
*/
long lfoMac = LittleEndian.getUInt( tableStream, offset );
offset += LittleEndian.INT_SIZE;
/*
* An array of LFO structures. The number of elements in this array
* is specified by lfoMac. -- Page 424 of 621. [MS-DOC] -- v20110315
* Word (.doc) Binary File Format
*/
for ( int x = 0; x < lfoMac; x++ )
{
ListFormatOverride lfo = new ListFormatOverride( tableStream,
offset );
offset += LFO.getSize();
_overrideList.add( lfo );
}
/*
* An array of LFOData that is parallel to rgLfo. The number of
* elements that are contained in this array is specified by lfoMac.
* -- Page 424 of 621. [MS-DOC] -- v20110315 Word (.doc) Binary File
* Format
*/
for ( int x = 0; x < lfoMac; x++ )
{
ListFormatOverride lfo = _overrideList.get( x );
LFOData lfoData = new LFOData( tableStream, offset,
lfo.numOverrides() );
lfo.setLfoData( lfoData );
offset += lfoData.getSizeInBytes();
}
}
}
public int addList(ListData lst, ListFormatOverride override)
{
int lsid = lst.getLsid();
while (_listMap.get(Integer.valueOf(lsid)) != null)
{
lsid = lst.resetListID();
override.setLsid(lsid);
}
_listMap.put(Integer.valueOf(lsid), lst);
_overrideList.add(override);
return lsid;
this._plfLfo = new PlfLfo( tableStream, fcPlfLfo, lcbPlfLfo );
}
public void writeListDataTo( FileInformationBlock fib,
HWPFOutputStream tableStream ) throws IOException
{
final int startOffset = tableStream.getOffset();
fib.setFcPlcfLst( startOffset );
fib.setFcPlfLst( startOffset );
int listSize = _listMap.size();
@ -156,7 +95,7 @@ public final class ListTables
LittleEndian.putShort(shortHolder, (short)listSize);
tableStream.write(shortHolder);
for(Integer x : _listMap.sortedKeys()) {
for(Integer x : _listMap.keySet()) {
ListData lst = _listMap.get(x);
tableStream.write(lst.toByteArray());
ListLevel[] lvls = lst.getLevels();
@ -171,59 +110,35 @@ public final class ListTables
* account for the array of LVLs. -- Page 76 of 621 -- [MS-DOC] --
* v20110315 Word (.doc) Binary File Format
*/
fib.setLcbPlcfLst( tableStream.getOffset() - startOffset );
fib.setLcbPlfLst( tableStream.getOffset() - startOffset );
tableStream.write( levelBuf.toByteArray() );
}
public void writeListOverridesTo( HWPFOutputStream tableStream )
throws IOException
public void writeListOverridesTo( FileInformationBlock fib,
HWPFOutputStream tableStream ) throws IOException
{
LittleEndian.putUInt( _overrideList.size(), tableStream );
for ( ListFormatOverride lfo : _overrideList )
{
tableStream.write( lfo.getLfo().serialize() );
_plfLfo.writeTo( fib, tableStream );
}
for ( ListFormatOverride lfo : _overrideList )
public LFO getLfo( int ilfo ) throws NoSuchElementException
{
lfo.getLfoData().writeTo( tableStream );
}
return _plfLfo.getLfo( ilfo );
}
public ListFormatOverride getOverride(int lfoIndex)
public LFOData getLfoData( int ilfo ) throws NoSuchElementException
{
return _overrideList.get(lfoIndex - 1);
return _plfLfo.getLfoData( ilfo );
}
public int getOverrideCount() {
return _overrideList.size();
public int getOverrideIndexFromListID( int lsid )
throws NoSuchElementException
{
return _plfLfo.getIlfoByLsid( lsid );
}
public int getOverrideIndexFromListID(int lstid)
public ListLevel getLevel(int lsid, int level)
{
int returnVal = -1;
int size = _overrideList.size();
for (int x = 0; x < size; x++)
{
ListFormatOverride next = _overrideList.get(x);
if (next.getLsid() == lstid)
{
// 1-based index I think
returnVal = x+1;
break;
}
}
if (returnVal == -1)
{
throw new NoSuchElementException("No list found with the specified ID");
}
return returnVal;
}
public ListLevel getLevel(int listID, int level)
{
ListData lst = _listMap.get(Integer.valueOf(listID));
ListData lst = _listMap.get(Integer.valueOf(lsid));
if(level < lst.numLevels()) {
ListLevel lvl = lst.getLevels()[level];
return lvl;
@ -232,112 +147,69 @@ public final class ListTables
return null;
}
public ListData getListData(int listID)
public ListData getListData(int lsid)
{
return _listMap.get(Integer.valueOf(listID));
return _listMap.get(Integer.valueOf(lsid));
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result
+ ( ( _listMap == null ) ? 0 : _listMap.hashCode() );
result = prime * result
+ ( ( _plfLfo == null ) ? 0 : _plfLfo.hashCode() );
return result;
}
@Override
public boolean equals( Object obj )
{
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
ListTables other = (ListTables) obj;
if ( _listMap == null )
{
if ( other._listMap != null )
return false;
}
ListTables tables = (ListTables)obj;
if (_listMap.size() == tables._listMap.size())
{
Iterator<Integer> it = _listMap.keySet().iterator();
while (it.hasNext())
{
Integer key = it.next();
ListData lst1 = _listMap.get(key);
ListData lst2 = tables._listMap.get(key);
if (!lst1.equals(lst2))
else if ( !_listMap.equals( other._listMap ) )
return false;
if ( _plfLfo == null )
{
if ( other._plfLfo != null )
return false;
}
}
int size = _overrideList.size();
if (size == tables._overrideList.size())
{
for (int x = 0; x < size; x++)
{
if (!_overrideList.get(x).equals(tables._overrideList.get(x)))
{
else if ( !_plfLfo.equals( other._plfLfo ) )
return false;
}
}
return true;
}
}
return false;
}
private static class ListMap implements Map<Integer, ListData> {
private ArrayList<Integer> keyList = new ArrayList<Integer>();
private HashMap<Integer,ListData> parent = new HashMap<Integer,ListData>();
private ListMap() {}
public int addList( ListData lst, LFO lfo, LFOData lfoData )
{
int lsid = lst.getLsid();
while ( _listMap.get( Integer.valueOf( lsid ) ) != null )
{
lsid = lst.resetListID();
lfo.setLsid( lsid );
}
_listMap.put( Integer.valueOf( lsid ), lst );
public void clear() {
keyList.clear();
parent.clear();
if ( lfo == null && lfoData != null )
{
throw new IllegalArgumentException(
"LFO and LFOData should be specified both or noone" );
}
public boolean containsKey(Object key) {
return parent.containsKey(key);
}
public boolean containsValue(Object value) {
return parent.containsValue(value);
}
public ListData get(Object key) {
return parent.get(key);
}
public boolean isEmpty() {
return parent.isEmpty();
}
public ListData put(Integer key, ListData value) {
keyList.add(key);
return parent.put(key, value);
}
public void putAll(Map<? extends Integer, ? extends ListData> map) {
for(Entry<? extends Integer, ? extends ListData> entry : map.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
public ListData remove(Object key) {
keyList.remove(key);
return parent.remove(key);
}
public int size() {
return parent.size();
}
public Set<Entry<Integer, ListData>> entrySet() {
throw new IllegalStateException("Use sortedKeys() + get() instead");
}
public List<Integer> sortedKeys() {
return Collections.unmodifiableList(keyList);
}
public Set<Integer> keySet() {
throw new IllegalStateException("Use sortedKeys() instead");
}
public Collection<ListData> values() {
ArrayList<ListData> values = new ArrayList<ListData>();
for(Integer key : keyList) {
values.add(parent.get(key));
}
return values;
if ( lfo != null )
{
_plfLfo.add( lfo, lfoData );
}
return lsid;
}
}

View File

@ -0,0 +1,221 @@
/*
* ====================================================================
* 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 java.util.NoSuchElementException;
import org.apache.poi.hwpf.model.io.HWPFOutputStream;
import org.apache.poi.util.ArrayUtil;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
/**
* The PlfLfo structure contains the list format override data for the document.
* <p>
* Documentation quoted from Page 424 of 621. [MS-DOC] -- v20110315 Word (.doc)
* Binary File Format
*
* @author Sergey Vladimirov (vlsergey {at} gmail {dot} com)
*/
public class PlfLfo
{
private static POILogger log = POILogFactory.getLogger( PlfLfo.class );
/**
* An unsigned integer that specifies the count of elements in both the
* rgLfo and rgLfoData arrays.
*/
private int _lfoMac;
private LFO[] _rgLfo;
private LFOData[] _rgLfoData;
PlfLfo( byte[] tableStream, int fcPlfLfo, int lcbPlfLfo )
{
/*
* The PlfLfo structure contains the list format override data for the
* document. -- Page 424 of 621. [MS-DOC] -- v20110315 Word (.doc)
* Binary File Format
*/
int offset = fcPlfLfo;
/*
* lfoMac (4 bytes): An unsigned integer that specifies the count of
* elements in both the rgLfo and rgLfoData arrays. -- Page 424 of 621.
* [MS-DOC] -- v20110315 Word (.doc) Binary File Format
*/
long lfoMacLong = LittleEndian.getUInt( tableStream, offset );
offset += LittleEndian.INT_SIZE;
if ( lfoMacLong > Integer.MAX_VALUE )
{
throw new UnsupportedOperationException(
"Apache POI doesn't support rgLfo/rgLfoData size large than "
+ Integer.MAX_VALUE + " elements" );
}
this._lfoMac = (int) lfoMacLong;
_rgLfo = new LFO[_lfoMac];
_rgLfoData = new LFOData[_lfoMac];
/*
* An array of LFO structures. The number of elements in this array is
* specified by lfoMac. -- Page 424 of 621. [MS-DOC] -- v20110315 Word
* (.doc) Binary File Format
*/
for ( int x = 0; x < _lfoMac; x++ )
{
LFO lfo = new LFO( tableStream, offset );
offset += LFO.getSize();
_rgLfo[x] = lfo;
}
/*
* An array of LFOData that is parallel to rgLfo. The number of elements
* that are contained in this array is specified by lfoMac. -- Page 424
* of 621. [MS-DOC] -- v20110315 Word (.doc) Binary File Format
*/
for ( int x = 0; x < _lfoMac; x++ )
{
LFOData lfoData = new LFOData( tableStream, offset,
_rgLfo[x].getClfolvl() );
offset += lfoData.getSizeInBytes();
_rgLfoData[x] = lfoData;
}
if ( ( offset - fcPlfLfo ) != lcbPlfLfo )
{
log.log( POILogger.WARN, "Actual size of PlfLfo is "
+ ( offset - fcPlfLfo ) + " bytes, but expected "
+ lcbPlfLfo );
}
}
void add( LFO lfo, LFOData lfoData )
{
final int newLfoMac = _lfoMac + 1;
_rgLfo = ArrayUtil.copyOf( _rgLfo, new LFO[newLfoMac] );
_rgLfo[_lfoMac + 1] = lfo;
_rgLfoData = ArrayUtil.copyOf( _rgLfoData, new LFOData[_lfoMac + 1] );
_rgLfoData[_lfoMac + 1] = lfoData;
this._lfoMac = newLfoMac;
}
@Override
public boolean equals( Object obj )
{
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
PlfLfo other = (PlfLfo) obj;
if ( _lfoMac != other._lfoMac )
return false;
if ( !Arrays.equals( _rgLfo, other._rgLfo ) )
return false;
if ( !Arrays.equals( _rgLfoData, other._rgLfoData ) )
return false;
return true;
}
/**
* An unsigned integer that specifies the count of elements in both the
* rgLfo and rgLfoData arrays.
*/
public int getLfoMac()
{
return _lfoMac;
}
public int getIlfoByLsid( int lsid )
{
for ( int i = 0; i < _lfoMac; i++ )
{
if ( _rgLfo[i].getLsid() == lsid )
{
return i + 1;
}
}
throw new NoSuchElementException( "LFO with lsid " + lsid
+ " not found" );
}
public LFO getLfo( int ilfo ) throws NoSuchElementException
{
if ( ilfo <= 0 || ilfo > _lfoMac )
{
throw new NoSuchElementException( "LFO with ilfo " + ilfo
+ " not found. lfoMac is " + _lfoMac );
}
return _rgLfo[ilfo - 1];
}
public LFOData getLfoData( int ilfo ) throws NoSuchElementException
{
if ( ilfo <= 0 || ilfo > _lfoMac )
{
throw new NoSuchElementException( "LFOData with ilfo " + ilfo
+ " not found. lfoMac is " + _lfoMac );
}
return _rgLfoData[ilfo - 1];
}
@Override
public int hashCode()
{
final int prime = 31;
int result = 1;
result = prime * result + _lfoMac;
result = prime * result + Arrays.hashCode( _rgLfo );
result = prime * result + Arrays.hashCode( _rgLfoData );
return result;
}
void writeTo( FileInformationBlock fib, HWPFOutputStream outputStream )
throws IOException
{
final int offset = outputStream.getOffset();
fib.setFcPlfLfo( offset );
LittleEndian.putUInt( _lfoMac, outputStream );
byte[] bs = new byte[LFO.getSize() * _lfoMac];
for ( int i = 0; i < _lfoMac; i++ )
{
_rgLfo[i].serialize( bs, i * LFO.getSize() );
}
outputStream.write( bs, 0, LFO.getSize() * _lfoMac );
for ( int i = 0; i < _lfoMac; i++ )
{
_rgLfoData[i].writeTo( outputStream );
}
fib.setLcbPlfLfo( outputStream.getOffset() - offset );
}
}

View File

@ -508,7 +508,26 @@ public abstract class PAPAbstractType
}
/**
* 1-based index into the pllfo (lists structure), if non-zero.
* "A 16-bit signed integer value that is used to determine which list
* contains the paragraph. This value MUST be one of the following:
*
* 0x0000 -- This paragraph is not in a list, and any list formatting on the
* paragraph is removed.
*
* 0x0001 - 0x07FE -- The value is a 1-based index into PlfLfo.rgLfo. The
* LFO at this index defines the list that this paragraph is in.
*
* 0xF801 -- This paragraph is not in a list.
*
* 0xF802 - 0xFFFF -- The value is the negation of a 1-based index into
* PlfLfo.rgLfo. The LFO at this index defines the list that this paragraph
* is in. The logical left indentation (see sprmPDxaLeft) and the logical
* left first line indentation (see sprmPDxaLeft1) of the paragraph MUST be
* preserved despite any list formatting.
*
* By default, a paragraph is not in a list."
*
* Quote from [MS-DOC] -- v20110315, page 125
*/
@Internal
public int getIlfo()
@ -517,7 +536,25 @@ public abstract class PAPAbstractType
}
/**
* 1-based index into the pllfo (lists structure), if non-zero.
* "A 16-bit signed integer value that is used to determine which list
* contains the paragraph. This value MUST be one of the following:
*
* 0x0000 -- This paragraph is not in a list, and any list formatting on the
* paragraph is removed.
*
* 0x0001 - 0x07FE -- The value is a 1-based index into PlfLfo.rgLfo. The
* LFO at this index defines the list that this paragraph is in.
*
* 0xF801 -- This paragraph is not in a list.
*
* 0xF802 - 0xFFFF -- The value is the negation of a 1-based index into
* PlfLfo.rgLfo. The LFO at this index defines the list that this paragraph
* is in. The logical left indentation (see sprmPDxaLeft) and the logical
* left first line indentation (see sprmPDxaLeft1) of the paragraph MUST be
* preserved despite any list formatting. By default, a paragraph is not in
* a list."
*
* Quote from [MS-DOC] -- v20110315, page 125
*/
@Internal
public void setIlfo( int field_9_ilfo )

View File

@ -17,55 +17,22 @@
package org.apache.poi.hwpf.usermodel;
import org.apache.poi.hwpf.model.ListFormatOverride;
import org.apache.poi.hwpf.model.ListFormatOverrideLevel;
import org.apache.poi.hwpf.model.ListLevel;
import org.apache.poi.hwpf.model.ListTables;
import org.apache.poi.hwpf.model.PAPX;
import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
public final class ListEntry
extends Paragraph
public final class ListEntry extends Paragraph
{
private static POILogger log = POILogFactory.getLogger(ListEntry.class);
ListLevel _level;
ListFormatOverrideLevel _overrideLevel;
@Internal
ListEntry( PAPX papx, ParagraphProperties properties, Range parent )
{
super( papx, properties, parent );
final ListTables tables = parent._doc.getListTables();
if ( tables != null && _props.getIlfo() < tables.getOverrideCount() )
{
ListFormatOverride override = tables.getOverride( _props.getIlfo() );
_overrideLevel = override.getOverrideLevel( _props.getIlvl() );
_level = tables.getLevel( override.getLsid(), _props.getIlvl() );
}
else
{
log.log( POILogger.WARN,
"No ListTables found for ListEntry - document probably partly corrupt, "
+ "and you may experience problems" );
}
}
@Deprecated
ListEntry( PAPX papx, Range parent, ListTables tables )
{
super( papx, parent );
if(tables != null && _props.getIlfo() < tables.getOverrideCount()) {
ListFormatOverride override = tables.getOverride(_props.getIlfo());
_overrideLevel = override.getOverrideLevel(_props.getIlvl());
_level = tables.getLevel(override.getLsid(), _props.getIlvl());
} else {
log.log(POILogger.WARN, "No ListTables found for ListEntry - document probably partly corrupt, and you may experience problems");
}
}
@Deprecated

View File

@ -17,9 +17,10 @@
package org.apache.poi.hwpf.usermodel;
import org.apache.poi.hwpf.HWPFDocumentCore;
import java.util.NoSuchElementException;
import org.apache.poi.hwpf.model.ListFormatOverride;
import org.apache.poi.hwpf.HWPFDocumentCore;
import org.apache.poi.hwpf.model.LFO;
import org.apache.poi.hwpf.model.ListLevel;
import org.apache.poi.hwpf.model.ListTables;
import org.apache.poi.hwpf.model.PAPX;
@ -28,8 +29,14 @@ import org.apache.poi.hwpf.sprm.ParagraphSprmUncompressor;
import org.apache.poi.hwpf.sprm.SprmBuffer;
import org.apache.poi.hwpf.sprm.TableSprmCompressor;
import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
public class Paragraph extends Range implements Cloneable
{
private static POILogger log = POILogFactory.getLogger( Paragraph.class );
public class Paragraph extends Range implements Cloneable {
public final static short SPRM_JC = 0x2403;
public final static short SPRM_FSIDEBYSIDE = 0x2404;
public final static short SPRM_FKEEP = 0x2405;
@ -105,10 +112,20 @@ public class Paragraph extends Range implements Cloneable {
if ( properties.getIlfo() != 0 && listTables != null )
{
final ListFormatOverride listFormatOverride = listTables
.getOverride( properties.getIlfo() );
final ListLevel listLevel = listTables.getLevel(
listFormatOverride.getLsid(), properties.getIlvl() );
LFO lfo = null;
try
{
lfo = listTables.getLfo( properties.getIlfo() );
}
catch ( NoSuchElementException exc )
{
log.log( POILogger.WARN, "Paragraph refers to LFO #",
properties.getIlfo(), " that does not exists" );
}
if ( lfo != null )
{
final ListLevel listLevel = listTables.getLevel( lfo.getLsid(),
properties.getIlvl() );
if ( listLevel != null && listLevel.getGrpprlPapx() != null )
{
@ -121,6 +138,7 @@ public class Paragraph extends Range implements Cloneable {
properties, papx.getGrpprl(), 2 );
}
}
}
if ( properties.getIlfo() > 0 )
return new ListEntry( papx, properties, parent );
@ -576,6 +594,22 @@ public short getStyleIndex()
return _props.getRgdxaTab();
}
public HWPFList getList()
{
if ( getIlfo() == 0x000 || getIlfo() == 0xF801 )
{
throw new IllegalStateException( "Paragraph not in list" );
}
HWPFList hwpfList = new HWPFList( getDocument().getStyleSheet(),
getDocument().getListTables(), getIlfo() );
return hwpfList;
}
public boolean isInList()
{
return getIlfo() != 0x000 && getIlfo() != 0xF801;
}
/**
* clone the ParagraphProperties object associated with this Paragraph so
* that you can apply the same properties to another paragraph.

View File

@ -35,21 +35,21 @@ public final class TestListTables
FileInformationBlock fib = _hWPFDocFixture._fib;
byte[] tableStream = _hWPFDocFixture._tableStream;
int listOffset = fib.getFcPlcfLst();
int listOffset = fib.getFcPlfLst();
int lfoOffset = fib.getFcPlfLfo();
if (listOffset != 0 && fib.getLcbPlcfLst() != 0)
if (listOffset != 0 && fib.getLcbPlfLst() != 0)
{
ListTables listTables = new ListTables (tableStream, fib.getFcPlcfLst (),
fib.getFcPlfLfo ());
ListTables listTables = new ListTables (tableStream, fib.getFcPlfLst(),
fib.getFcPlfLfo (), fib.getLcbPlfLfo());
HWPFFileSystem fileSys = new HWPFFileSystem ();
HWPFOutputStream tableOut = fileSys.getStream ("1Table");
listTables.writeListDataTo (fib, tableOut);
int offset = tableOut.getOffset ();
listTables.writeListOverridesTo (tableOut);
listTables.writeListOverridesTo( fib, tableOut);
ListTables newTables = new ListTables (tableOut.toByteArray (), 0, offset);
ListTables newTables = new ListTables (tableOut.toByteArray (), fib.getFcPlfLst(),
fib.getFcPlfLfo (), fib.getLcbPlfLfo());
assertEquals(listTables, newTables);

View File

@ -16,13 +16,13 @@
==================================================================== */
package org.apache.poi.hwpf.usermodel;
import junit.framework.TestCase;
import org.apache.poi.hwpf.HWPFDocument;
import org.apache.poi.hwpf.HWPFTestDataSamples;
import org.apache.poi.hwpf.model.ListFormatOverride;
import org.apache.poi.hwpf.model.LFO;
import org.apache.poi.hwpf.model.ListLevel;
import junit.framework.TestCase;
public class TestBug50075 extends TestCase
{
@ -31,7 +31,7 @@ public class TestBug50075 extends TestCase
Range range = doc.getRange();
assertEquals(1, range.numParagraphs());
ListEntry entry = (ListEntry) range.getParagraph(0);
ListFormatOverride override = doc.getListTables().getOverride(entry.getIlfo());
LFO override = doc.getListTables().getLfo( entry.getIlfo());
ListLevel level = doc.getListTables().getLevel(override.getLsid(), entry.getIlvl());
// the bug reproduces, if this call fails with NullPointerException

View File

@ -21,19 +21,20 @@
<suffix>AbstractType</suffix>
<extends>HDFType</extends>
<description>List Format Override (LFO). &lt;p&gt;Class and fields descriptions are quoted from
Microsoft Office Word 97-2007 Binary File Format
[MS-DOC] --v20110315; Word (.doc) Binary File Format
</description>
<author>Sergey Vladimirov; according to Microsoft Office Word 97-2007 Binary File Format
Specification [*.doc]
<author>Sergey Vladimirov; according to [MS-DOC] --v20110315; Word (.doc) Binary File Format;
Copyright (c) Microsoft Corporation
</author>
<fields>
<field type="int" size="4" name="lsid" description="List ID of corresponding LSTF (see LSTF)"/>
<field type="int" size="4" name="reserved1" description="Reserved"/>
<field type="int" size="4" name="reserved2" description="Reserved"/>
<field type="int" size="4" name="lsid"
description="A signed integer that specifies the list identifier of an LSTF. This LFO corresponds to the LSTF in PlfLst.rgLstf that has an lsid whose value is equal to this value."/>
<field type="int" size="4" name="unused1" description="This field MUST be ignored"/>
<field type="int" size="4" name="unused2" description="This field MUST be ignored"/>
<field type="byte" size="1" name="clfolvl"
description="Count of levels whose format is overridden (see LFOLVL)"/>
description="An unsigned integer that specifies the field that this LFO represents."/>
<field type="byte" size="1" name="ibstFltAutoNum" description="Used for AUTONUM field emulation"/>
<field type="Grfhic" size="1" name="grfhic" description="HTML compatibility flags"/>
<field type="byte" size="1" name="reserved3" description="Reserved"/>
<field type="byte" size="1" name="unused3" description="This field MUST be ignored"/>
</fields>
</record>