aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/java/org/apache/poi/util/ArrayUtil.java23
-rw-r--r--src/scratchpad/src/org/apache/poi/hwpf/converter/AbstractWordConverter.java42
-rw-r--r--src/scratchpad/src/org/apache/poi/hwpf/dev/HWPFLister.java43
-rw-r--r--src/scratchpad/src/org/apache/poi/hwpf/model/LFOData.java14
-rw-r--r--src/scratchpad/src/org/apache/poi/hwpf/model/ListTables.java312
-rw-r--r--src/scratchpad/src/org/apache/poi/hwpf/model/PlfLfo.java221
-rw-r--r--src/scratchpad/src/org/apache/poi/hwpf/model/types/PAPAbstractType.java41
-rw-r--r--src/scratchpad/src/org/apache/poi/hwpf/usermodel/ListEntry.java53
-rw-r--r--src/scratchpad/src/org/apache/poi/hwpf/usermodel/Paragraph.java66
-rw-r--r--src/scratchpad/testcases/org/apache/poi/hwpf/model/TestListTables.java14
-rw-r--r--src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestBug50075.java8
-rw-r--r--src/types/definitions/lfo_type.xml19
12 files changed, 501 insertions, 355 deletions
diff --git a/src/java/org/apache/poi/util/ArrayUtil.java b/src/java/org/apache/poi/util/ArrayUtil.java
index 3461ee7152..c8d46e1341 100644
--- a/src/java/org/apache/poi/util/ArrayUtil.java
+++ b/src/java/org/apache/poi/util/ArrayUtil.java
@@ -17,6 +17,7 @@
package org.apache.poi.util;
+
/**
* Utility classes for dealing with arrays.
*
@@ -130,6 +131,28 @@ public class ArrayUtil
}
/**
+ * 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
* and <tt>original.length</tt>, inclusive. The value at
diff --git a/src/scratchpad/src/org/apache/poi/hwpf/converter/AbstractWordConverter.java b/src/scratchpad/src/org/apache/poi/hwpf/converter/AbstractWordConverter.java
index c7ddf97518..a27eec046a 100644
--- a/src/scratchpad/src/org/apache/poi/hwpf/converter/AbstractWordConverter.java
+++ b/src/scratchpad/src/org/apache/poi/hwpf/converter/AbstractWordConverter.java
@@ -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,36 +1053,15 @@ public abstract class AbstractWordConverter
processPageBreak( wordDocument, flow );
}
- if ( paragraph.getIlfo() != currentListInfo )
- {
- currentListInfo = paragraph.getIlfo();
- }
-
- if ( currentListInfo != 0 )
+ if ( paragraph.isInList() )
{
- if ( listTables != null )
- {
- final ListFormatOverride listFormatOverride = listTables
- .getOverride( paragraph.getIlfo() );
+ HWPFList hwpfList = paragraph.getList();
- 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 );
- }
+ processParagraph( wordDocument, flow, currentTableLevel,
+ paragraph, label );
}
else
{
diff --git a/src/scratchpad/src/org/apache/poi/hwpf/dev/HWPFLister.java b/src/scratchpad/src/org/apache/poi/hwpf/dev/HWPFLister.java
index caed3ad41c..d008ec907c 100644
--- a/src/scratchpad/src/org/apache/poi/hwpf/dev/HWPFLister.java
+++ b/src/scratchpad/src/org/apache/poi/hwpf/dev/HWPFLister.java
@@ -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,27 +717,33 @@ 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() );
-
- System.out.println( "PAP's ListLevel: " + listLevel );
- if ( listLevel.getGrpprlPapx() != null )
+ if ( lfo != null )
{
- System.out.println( "PAP's ListLevel PAPX:" );
- dumpSprms( new SprmIterator( listLevel.getGrpprlPapx(), 0 ),
- "* " );
- }
+ final ListLevel listLevel = listTables.getLevel( lfo.getLsid(),
+ paragraph.getIlvl() );
- if ( listLevel.getGrpprlPapx() != null )
- {
- System.out.println( "PAP's ListLevel CHPX:" );
- dumpSprms( new SprmIterator( listLevel.getGrpprlChpx(), 0 ),
- "* " );
+ System.out.println( "PAP's ListLevel: " + listLevel );
+ if ( listLevel.getGrpprlPapx() != null )
+ {
+ System.out.println( "PAP's ListLevel PAPX:" );
+ dumpSprms(
+ new SprmIterator( listLevel.getGrpprlPapx(), 0 ),
+ "* " );
+ }
+
+ if ( listLevel.getGrpprlPapx() != null )
+ {
+ System.out.println( "PAP's ListLevel CHPX:" );
+ dumpSprms(
+ new SprmIterator( listLevel.getGrpprlChpx(), 0 ),
+ "* " );
+ }
}
}
}
diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/LFOData.java b/src/scratchpad/src/org/apache/poi/hwpf/model/LFOData.java
index 9910dcaef8..ff2e69c9c9 100644
--- a/src/scratchpad/src/org/apache/poi/hwpf/model/LFOData.java
+++ b/src/scratchpad/src/org/apache/poi/hwpf/model/LFOData.java
@@ -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;
diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/ListTables.java b/src/scratchpad/src/org/apache/poi/hwpf/model/ListTables.java
index 009f42f7b4..eaea30733d 100644
--- a/src/scratchpad/src/org/apache/poi/hwpf/model/ListTables.java
+++ b/src/scratchpad/src/org/apache/poi/hwpf/model/ListTables.java
@@ -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,100 +48,43 @@ 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
- */
- int offset = lstOffset;
-
- int cLst = LittleEndian.getShort( tableStream, offset );
- offset += LittleEndian.SHORT_SIZE;
- int levelOffset = offset + ( cLst * LSTF.getSize() );
- for ( int x = 0; x < cLst; x++ )
- {
- ListData lst = new ListData( tableStream, offset );
- _listMap.put( Integer.valueOf( lst.getLsid() ), lst );
- offset += LSTF.getSize();
+ /*
+ * 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;
- int num = lst.numLevels();
- for ( int y = 0; y < num; y++ )
- {
- ListLevel lvl = new ListLevel();
- levelOffset += lvl.read( tableStream, levelOffset );
- lst.setLevel( y, lvl );
- }
- }
- }
+ int cLst = LittleEndian.getShort( tableStream, offset );
+ offset += LittleEndian.SHORT_SIZE;
+ int levelOffset = offset + ( cLst * LSTF.getSize() );
+ for ( int x = 0; x < cLst; x++ )
{
- /*
- * 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;
+ ListData lst = new ListData( tableStream, offset );
+ _listMap.put( Integer.valueOf( lst.getLsid() ), lst );
+ offset += LSTF.getSize();
- /*
- * 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++ )
+ int num = lst.numLevels();
+ for ( int y = 0; y < num; y++ )
{
- 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();
+ ListLevel lvl = new ListLevel();
+ levelOffset += lvl.read( tableStream, levelOffset );
+ lst.setLevel( y, lvl );
}
}
- }
- public int addList(ListData lst, ListFormatOverride override)
- {
- int lsid = lst.getLsid();
- while (_listMap.get(Integer.valueOf(lsid)) != null)
- {
- lsid = lst.resetListID();
- override.setLsid(lsid);
+ this._plfLfo = new PlfLfo( tableStream, fcPlfLfo, lcbPlfLfo );
}
- _listMap.put(Integer.valueOf(lsid), lst);
- _overrideList.add(override);
- return lsid;
- }
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() );
- }
-
- for ( ListFormatOverride lfo : _overrideList )
- {
- lfo.getLfoData().writeTo( tableStream );
- }
+ _plfLfo.writeTo( fib, tableStream );
}
- public ListFormatOverride getOverride(int lfoIndex)
- {
- return _overrideList.get(lfoIndex - 1);
- }
-
- public int getOverrideCount() {
- return _overrideList.size();
- }
+ public LFO getLfo( int ilfo ) throws NoSuchElementException
+ {
+ return _plfLfo.getLfo( ilfo );
+ }
- public int getOverrideIndexFromListID(int lstid)
- {
- int returnVal = -1;
- int size = _overrideList.size();
- for (int x = 0; x < size; x++)
+ public LFOData getLfoData( int ilfo ) throws NoSuchElementException
{
- ListFormatOverride next = _overrideList.get(x);
- if (next.getLsid() == lstid)
- {
- // 1-based index I think
- returnVal = x+1;
- break;
- }
+ return _plfLfo.getLfoData( ilfo );
}
- if (returnVal == -1)
+
+ public int getOverrideIndexFromListID( int lsid )
+ throws NoSuchElementException
{
- throw new NoSuchElementException("No list found with the specified ID");
+ return _plfLfo.getIlfoByLsid( lsid );
}
- return returnVal;
- }
- public ListLevel getLevel(int listID, int level)
+ public ListLevel getLevel(int lsid, 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));
}
- public boolean equals(Object obj)
- {
- if (obj == null)
+ @Override
+ public int hashCode()
{
- return false;
+ 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;
}
- ListTables tables = (ListTables)obj;
-
- if (_listMap.size() == tables._listMap.size())
+ @Override
+ public boolean equals( Object obj )
{
- 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))
+ if ( this == obj )
+ return true;
+ if ( obj == null )
+ return false;
+ if ( getClass() != obj.getClass() )
+ return false;
+ ListTables other = (ListTables) obj;
+ if ( _listMap == null )
{
- return false;
+ if ( other._listMap != 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 ( !_listMap.equals( other._listMap ) )
return false;
- }
+ if ( _plfLfo == null )
+ {
+ if ( other._plfLfo != null )
+ return false;
}
+ 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 void clear() {
- keyList.clear();
- parent.clear();
- }
- 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 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 );
}
- }
-
- public ListData remove(Object key) {
- keyList.remove(key);
- return parent.remove(key);
- }
+ _listMap.put( Integer.valueOf( lsid ), lst );
- 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));
+ if ( lfo == null && lfoData != null )
+ {
+ throw new IllegalArgumentException(
+ "LFO and LFOData should be specified both or noone" );
}
- return values;
- }
- }
+ if ( lfo != null )
+ {
+ _plfLfo.add( lfo, lfoData );
+ }
+ return lsid;
+ }
}
diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/PlfLfo.java b/src/scratchpad/src/org/apache/poi/hwpf/model/PlfLfo.java
new file mode 100644
index 0000000000..4fd8cc2c35
--- /dev/null
+++ b/src/scratchpad/src/org/apache/poi/hwpf/model/PlfLfo.java
@@ -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 );
+ }
+}
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
+ {
+ 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 )
{
- 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 );
+ 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 @@
<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="Grfhic" size="1" name="grfhic" description="HTML compatibility flags"/>
+ <field type="byte" size="1" name="unused3" description="This field MUST be ignored"/>
</fields>
</record>