+ * 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 );
+ }
+}
diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/types/PAPAbstractType.java b/src/scratchpad/src/org/apache/poi/hwpf/model/types/PAPAbstractType.java
index 5415f18248..f9918fc4e3 100644
--- a/src/scratchpad/src/org/apache/poi/hwpf/model/types/PAPAbstractType.java
+++ b/src/scratchpad/src/org/apache/poi/hwpf/model/types/PAPAbstractType.java
@@ -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 )
diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/ListEntry.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/ListEntry.java
index 25b41da777..378c26c90e 100644
--- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/ListEntry.java
+++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/ListEntry.java
@@ -17,60 +17,27 @@
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
+ @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");
+ ListEntry( PAPX papx, Range parent, ListTables tables )
+ {
+ super( papx, parent );
}
- }
- @Deprecated
- public int type()
- {
- return TYPE_LISTENTRY;
- }
+ @Deprecated
+ public int type()
+ {
+ return TYPE_LISTENTRY;
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Paragraph.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Paragraph.java
index 6d99465abc..65f907691b 100644
--- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Paragraph.java
+++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Paragraph.java
@@ -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,20 +112,31 @@ 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() );
-
- if ( listLevel!=null && listLevel.getGrpprlPapx() != null )
+ LFO lfo = null;
+ try
{
- properties = ParagraphSprmUncompressor.uncompressPAP(
- properties, listLevel.getGrpprlPapx(), 0 );
- // reapply style and local PAPX properties
- properties = newParagraph_applyStyleProperties( styleSheet,
- papx, properties );
- properties = ParagraphSprmUncompressor.uncompressPAP(
- properties, papx.getGrpprl(), 2 );
+ 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 )
+ {
+ properties = ParagraphSprmUncompressor.uncompressPAP(
+ properties, listLevel.getGrpprlPapx(), 0 );
+ // reapply style and local PAPX properties
+ properties = newParagraph_applyStyleProperties( styleSheet,
+ papx, properties );
+ properties = ParagraphSprmUncompressor.uncompressPAP(
+ properties, papx.getGrpprl(), 2 );
+ }
}
}
@@ -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.
diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestListTables.java b/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestListTables.java
index 291fef3c8a..feb7a90c24 100644
--- a/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestListTables.java
+++ b/src/scratchpad/testcases/org/apache/poi/hwpf/model/TestListTables.java
@@ -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);
diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBug50075.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBug50075.java
index 24ac72fe40..a8302520c4 100644
--- a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBug50075.java
+++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBug50075.java
@@ -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
diff --git a/src/types/definitions/lfo_type.xml b/src/types/definitions/lfo_type.xml
index cc4d88df34..911f6ce6f1 100644
--- a/src/types/definitions/lfo_type.xml
+++ b/src/types/definitions/lfo_type.xml
@@ -21,19 +21,20 @@